feat: implement automatic update checking and downloading functionality in the desktop app, enhancing user experience with seamless updates and corresponding UI settings

This commit is contained in:
wizardchen
2026-04-11 18:27:08 +08:00
committed by lyingbug
parent c4ebc1f60e
commit 9874fcee7c
13 changed files with 527 additions and 23 deletions

View File

@@ -68,3 +68,17 @@ func (a *App) GetAPILanBaseURL() string {
func (a *App) GetDesktopListenPublicActive() bool {
return a.listenPublic
}
// CheckForUpdates manually triggers the update check from the frontend.
func (a *App) CheckForUpdates() {
if a.ctx != nil {
checkUpdate(a.ctx, desktopAboutVersion(), true, false)
}
}
// AutoCheckForUpdates silently checks for updates and downloads them.
func (a *App) AutoCheckForUpdates() {
if a.ctx != nil {
checkUpdate(a.ctx, desktopAboutVersion(), false, true)
}
}

View File

@@ -27,7 +27,6 @@ import (
"github.com/Tencent/WeKnora/internal/config"
"github.com/Tencent/WeKnora/internal/container"
"github.com/Tencent/WeKnora/internal/handler"
"github.com/Tencent/WeKnora/internal/logger"
"github.com/Tencent/WeKnora/internal/runtime"
"github.com/Tencent/WeKnora/internal/tracing"
@@ -146,27 +145,6 @@ const wailsThemeSyncJS = `(function(){try{var t=localStorage.getItem('WeKnora_th
const weknoraGitHubRepoURL = "https://github.com/Tencent/WeKnora"
// desktopAboutVersion 优先使用构建脚本注入的 handler.Version否则尝试读取仓库根目录 VERSION本地 wails dev 等未带 ldflags 时)。
func desktopAboutVersion() string {
if v := strings.TrimSpace(handler.Version); v != "" && v != "unknown" {
return v
}
for _, p := range []string{
"VERSION",
filepath.Join("..", "..", "VERSION"),
filepath.Join("..", "..", "..", "VERSION"),
} {
b, err := os.ReadFile(p)
if err != nil {
continue
}
if v := strings.TrimSpace(string(b)); v != "" {
return v
}
}
return "unknown"
}
func main() {
// For macOS .app bundle, the working directory is usually "/" or the MacOS folder.
// We need to change the working directory to the Resources folder where our configs are.
@@ -300,6 +278,12 @@ func main() {
wailsruntime.BrowserOpenURL(app.ctx, weknoraGitHubRepoURL)
}
})
FileMenu.AddText("Check for Updates...", nil, func(_ *menu.CallbackData) {
if app.ctx == nil {
return
}
checkUpdate(app.ctx, desktopAboutVersion(), true, false)
})
FileMenu.AddSeparator()
FileMenu.AddText("Quit", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) {
app.shutdown(context.Background())

413
cmd/desktop/update.go Normal file
View File

@@ -0,0 +1,413 @@
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/Tencent/WeKnora/internal/handler"
"github.com/Tencent/WeKnora/internal/logger"
wailsruntime "github.com/wailsapp/wails/v2/pkg/runtime"
"golang.org/x/mod/semver"
)
type githubAsset struct {
Name string `json:"name"`
BrowserDownloadURL string `json:"browser_download_url"`
}
type githubRelease struct {
TagName string `json:"tag_name"`
HTMLURL string `json:"html_url"`
Assets []githubAsset `json:"assets"`
}
func checkUpdate(ctx context.Context, currentVersion string, showUpToDate bool, autoDownload bool) {
go func() {
if currentVersion == "unknown" || currentVersion == "" {
if showUpToDate {
wailsruntime.MessageDialog(ctx, wailsruntime.MessageDialogOptions{
Type: wailsruntime.InfoDialog,
Title: "Check for Updates",
Message: "Unable to determine the current version. Cannot check for updates.",
Buttons: []string{"OK"},
DefaultButton: "OK",
})
}
return
}
if !strings.HasPrefix(currentVersion, "v") {
currentVersion = "v" + currentVersion
}
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequest("GET", "https://api.github.com/repos/Tencent/WeKnora/releases/latest", nil)
if err != nil {
logger.Warnf(context.Background(), "Check update failed: %v", err)
return
}
// Add User-Agent header which is required/recommended by GitHub API
req.Header.Set("User-Agent", "WeKnora-Lite-Desktop-App")
// Add Authorization header if GITHUB_TOKEN is present to increase rate limit
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
resp, err := client.Do(req)
if err != nil {
logger.Warnf(context.Background(), "Check update failed: %v", err)
if showUpToDate {
wailsruntime.MessageDialog(ctx, wailsruntime.MessageDialogOptions{
Type: wailsruntime.ErrorDialog,
Title: "Check Update Failed",
Message: fmt.Sprintf("Failed to connect to GitHub to check for updates:\n%v", err),
Buttons: []string{"OK"},
DefaultButton: "OK",
})
}
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
logger.Warnf(context.Background(), "GitHub API returned status code: %d", resp.StatusCode)
if showUpToDate {
msg := fmt.Sprintf("GitHub API returned an unexpected status code: %d", resp.StatusCode)
if resp.StatusCode == 403 {
msg = "GitHub API rate limit exceeded. Please try again later."
}
wailsruntime.MessageDialog(ctx, wailsruntime.MessageDialogOptions{
Type: wailsruntime.ErrorDialog,
Title: "Check Update Failed",
Message: msg,
Buttons: []string{"OK"},
DefaultButton: "OK",
})
}
return
}
var release githubRelease
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
logger.Warnf(context.Background(), "Failed to parse release info: %v", err)
return
}
latestVersion := release.TagName
if !strings.HasPrefix(latestVersion, "v") {
latestVersion = "v" + latestVersion
}
if semver.IsValid(latestVersion) && semver.IsValid(currentVersion) {
if semver.Compare(latestVersion, currentVersion) > 0 {
assetURL, assetName := findBestAsset(release.Assets, runtime.GOOS, runtime.GOARCH)
if autoDownload && assetURL != "" {
// Silent download in background
downloadAndInstall(ctx, assetURL, assetName, currentVersion, latestVersion, true)
return
}
msg := fmt.Sprintf("A new version of WeKnora Lite is available!\n\nCurrent version: %s\nLatest version: %s\n\nWould you like to download it now?", currentVersion, latestVersion)
choice, _ := wailsruntime.MessageDialog(ctx, wailsruntime.MessageDialogOptions{
Type: wailsruntime.InfoDialog,
Title: "Update Available",
Message: msg,
Buttons: []string{"Download", "Cancel"},
DefaultButton: "Download",
})
if choice == "Download" {
if assetURL != "" {
downloadAndInstall(ctx, assetURL, assetName, currentVersion, latestVersion, false)
} else {
// Fallback to opening the release page if no specific asset is found
wailsruntime.BrowserOpenURL(ctx, release.HTMLURL)
}
}
return
}
}
if showUpToDate {
wailsruntime.MessageDialog(ctx, wailsruntime.MessageDialogOptions{
Type: wailsruntime.InfoDialog,
Title: "Up to Date",
Message: fmt.Sprintf("You are using the latest version of WeKnora Lite.\n\nCurrent version: %s", currentVersion),
Buttons: []string{"OK"},
DefaultButton: "OK",
})
}
}()
}
func findBestAsset(assets []githubAsset, goos, goarch string) (string, string) {
osKeyword := ""
switch goos {
case "darwin":
osKeyword = "mac"
case "windows":
osKeyword = "win"
case "linux":
osKeyword = "linux"
}
archKeyword := ""
switch goarch {
case "amd64":
archKeyword = "amd64"
case "arm64":
archKeyword = "arm64"
}
// 1. Try to match both OS and Arch
for _, asset := range assets {
name := strings.ToLower(asset.Name)
if strings.Contains(name, osKeyword) && (strings.Contains(name, archKeyword) || strings.Contains(name, "universal") || strings.Contains(name, "aarch64")) {
return asset.BrowserDownloadURL, asset.Name
}
}
// 2. Try to match OS only (e.g. universal binaries without arch in name)
for _, asset := range assets {
name := strings.ToLower(asset.Name)
if strings.Contains(name, osKeyword) {
return asset.BrowserDownloadURL, asset.Name
}
}
// 3. MacOS specific fallback (e.g. .dmg)
if goos == "darwin" {
for _, asset := range assets {
if strings.HasSuffix(strings.ToLower(asset.Name), ".dmg") {
return asset.BrowserDownloadURL, asset.Name
}
}
}
// 4. Windows specific fallback (e.g. .exe)
if goos == "windows" {
for _, asset := range assets {
if strings.HasSuffix(strings.ToLower(asset.Name), ".exe") {
return asset.BrowserDownloadURL, asset.Name
}
}
}
return "", ""
}
func downloadAndInstall(ctx context.Context, url string, filename string, currentVersion string, latestVersion string, silent bool) {
tempDir := os.TempDir()
savePath := filepath.Join(tempDir, filename)
go func() {
logger.Infof(context.Background(), "Starting download from %s to %s", url, savePath)
resp, err := http.Get(url)
if err != nil {
logger.Warnf(context.Background(), "Download failed: %v", err)
if !silent {
wailsruntime.MessageDialog(ctx, wailsruntime.MessageDialogOptions{
Type: wailsruntime.ErrorDialog,
Title: "Download Failed",
Message: fmt.Sprintf("Failed to download the update:\n%v", err),
Buttons: []string{"OK"},
DefaultButton: "OK",
})
}
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
logger.Warnf(context.Background(), "Download failed, server returned status: %d", resp.StatusCode)
if !silent {
wailsruntime.MessageDialog(ctx, wailsruntime.MessageDialogOptions{
Type: wailsruntime.ErrorDialog,
Title: "Download Failed",
Message: fmt.Sprintf("Server returned status: %d", resp.StatusCode),
Buttons: []string{"OK"},
DefaultButton: "OK",
})
}
return
}
out, err := os.Create(savePath)
if err != nil {
logger.Warnf(context.Background(), "Failed to create file for download: %v", err)
if !silent {
wailsruntime.MessageDialog(ctx, wailsruntime.MessageDialogOptions{
Type: wailsruntime.ErrorDialog,
Title: "Save Failed",
Message: fmt.Sprintf("Failed to save the update file:\n%v", err),
Buttons: []string{"OK"},
DefaultButton: "OK",
})
}
return
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
logger.Warnf(context.Background(), "Error occurred during download copying: %v", err)
if !silent {
wailsruntime.MessageDialog(ctx, wailsruntime.MessageDialogOptions{
Type: wailsruntime.ErrorDialog,
Title: "Download Error",
Message: fmt.Sprintf("An error occurred while downloading:\n%v", err),
Buttons: []string{"OK"},
DefaultButton: "OK",
})
}
return
}
logger.Infof(context.Background(), "Download completed successfully: %s", savePath)
// Prompt user to restart
choice, _ := wailsruntime.MessageDialog(ctx, wailsruntime.MessageDialogOptions{
Type: wailsruntime.InfoDialog,
Title: "Update Ready",
Message: fmt.Sprintf("WeKnora Lite %s has been downloaded successfully.\n\nWould you like to restart and install the new version now?", latestVersion),
Buttons: []string{"Restart Now", "Later"},
DefaultButton: "Restart Now",
})
if choice == "Restart Now" {
applyUpdateAndRestart(ctx, savePath)
}
}()
}
// desktopAboutVersion 优先使用构建脚本注入的 handler.Version否则尝试读取仓库根目录 VERSION本地 wails dev 等未带 ldflags 时)。
func desktopAboutVersion() string {
if v := strings.TrimSpace(handler.Version); v != "" && v != "unknown" {
return v
}
for _, p := range []string{
"VERSION",
filepath.Join("..", "..", "VERSION"),
filepath.Join("..", "..", "..", "VERSION"),
} {
b, err := os.ReadFile(p)
if err != nil {
continue
}
if v := strings.TrimSpace(string(b)); v != "" {
return v
}
}
return "unknown"
}
// applyUpdateAndRestart applies the downloaded update and restarts the application
func applyUpdateAndRestart(ctx context.Context, savePath string) {
if runtime.GOOS == "windows" {
scriptPath := filepath.Join(os.TempDir(), "weknora_update.bat")
execPath, err := os.Executable()
if err != nil {
logger.Warnf(context.Background(), "Failed to get executable path: %v", err)
return
}
scriptContent := fmt.Sprintf(`@echo off
timeout /t 2 /nobreak
start /wait "" "%s" /S
start "" "%s"
del "%%~f0"
`, savePath, execPath)
os.WriteFile(scriptPath, []byte(scriptContent), 0755)
cmd := exec.Command("cmd.exe", "/C", "start", "/b", scriptPath)
cmd.Start()
wailsruntime.Quit(ctx)
} else if runtime.GOOS == "darwin" {
if strings.HasSuffix(strings.ToLower(savePath), ".dmg") {
go func() {
execPath, err := os.Executable()
if err != nil {
logger.Warnf(context.Background(), "Failed to get executable path: %v", err)
exec.Command("open", savePath).Start()
wailsruntime.Quit(ctx)
return
}
appBundlePath := filepath.Dir(filepath.Dir(filepath.Dir(execPath)))
if !strings.HasSuffix(appBundlePath, ".app") {
exec.Command("open", savePath).Start()
wailsruntime.Quit(ctx)
return
}
mountPoint := filepath.Join(os.TempDir(), "WeKnoraUpdateMount")
os.MkdirAll(mountPoint, 0755)
cmdMount := exec.Command("hdiutil", "attach", savePath, "-mountpoint", mountPoint, "-nobrowse", "-quiet")
if err := cmdMount.Run(); err != nil {
logger.Warnf(context.Background(), "Failed to mount dmg: %v", err)
exec.Command("open", savePath).Start()
wailsruntime.Quit(ctx)
return
}
entries, err := os.ReadDir(mountPoint)
if err != nil {
exec.Command("hdiutil", "detach", mountPoint, "-force").Run()
exec.Command("open", savePath).Start()
wailsruntime.Quit(ctx)
return
}
var newAppPath string
for _, entry := range entries {
if strings.HasSuffix(entry.Name(), ".app") {
newAppPath = filepath.Join(mountPoint, entry.Name())
break
}
}
if newAppPath == "" {
exec.Command("hdiutil", "detach", mountPoint, "-force").Run()
exec.Command("open", savePath).Start()
wailsruntime.Quit(ctx)
return
}
appDir := filepath.Dir(appBundlePath)
scriptPath := filepath.Join(os.TempDir(), "weknora_update.sh")
scriptContent := fmt.Sprintf(`#!/bin/bash
sleep 2
if ! (rm -rf "%s" && cp -a "%s" "%s"); then
osascript -e "do shell script \"rm -rf \\\"%s\\\" && cp -a \\\"%s\\\" \\\"%s\\\"\" with administrator privileges"
fi
hdiutil detach "%s" -force
open "%s"
rm "$0"
`, appBundlePath, newAppPath, appDir, appBundlePath, newAppPath, appDir, mountPoint, appBundlePath)
os.WriteFile(scriptPath, []byte(scriptContent), 0755)
exec.Command("bash", scriptPath).Start()
wailsruntime.Quit(ctx)
}()
} else {
exec.Command("open", savePath).Start()
wailsruntime.Quit(ctx)
}
} else {
exec.Command("xdg-open", savePath).Start()
wailsruntime.Quit(ctx)
}
}

View File

@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n'
import { MessagePlugin } from 'tdesign-vue-next'
import ManualKnowledgeEditor from '@/components/manual-knowledge-editor.vue'
import { useAuthStore } from '@/stores/auth'
import { useSettingsStore } from '@/stores/settings'
import { getCurrentUser } from '@/api/auth'
// TDesign locale configs
@@ -16,6 +17,7 @@ import ruRUConfig from 'tdesign-vue-next/esm/locale/ru_RU'
const { locale } = useI18n()
const router = useRouter()
const authStore = useAuthStore()
const settingsStore = useSettingsStore()
const tdLocaleMap: Record<string, object> = {
'en-US': enUSConfig,
@@ -132,8 +134,38 @@ const handleGlobalOIDCCallback = async () => {
}
}
let updateCheckTimer: ReturnType<typeof setInterval> | null = null
onMounted(() => {
handleGlobalOIDCCallback()
// Auto check for updates on startup
setTimeout(() => {
if (settingsStore.isAutoCheckUpdateEnabled) {
// @ts-ignore
if (window.go && window.go.main && window.go.main.App && window.go.main.App.AutoCheckForUpdates) {
// @ts-ignore
window.go.main.App.AutoCheckForUpdates()
}
}
}, 2000)
// Periodically check for updates (every 4 hours)
updateCheckTimer = setInterval(() => {
if (settingsStore.isAutoCheckUpdateEnabled) {
// @ts-ignore
if (window.go && window.go.main && window.go.main.App && window.go.main.App.AutoCheckForUpdates) {
// @ts-ignore
window.go.main.App.AutoCheckForUpdates()
}
}
}, 4 * 60 * 60 * 1000)
})
onUnmounted(() => {
if (updateCheckTimer) {
clearInterval(updateCheckTimer)
}
})
</script>

View File

@@ -569,6 +569,8 @@ export default {
webSearchConfig: 'Web Search',
enableMemory: 'Enable Memory',
enableMemoryDesc: 'When enabled, the system will record your conversation history and automatically recall relevant content in future conversations to provide more personalized answers.',
autoCheckUpdate: 'Auto Download Updates',
autoCheckUpdateDesc: 'When enabled, automatically check and download the latest version in the background.',
memoryRequiresNeo4j: 'Memory feature requires Neo4j graph database. Please configure and enable Neo4j (set NEO4J_ENABLE=true) before enabling this feature.',
memoryHowToEnable: 'View Neo4j Configuration Guide',
parserEngine: 'Parser Engine',

View File

@@ -426,6 +426,8 @@ export default {
webSearchConfig: "웹 검색",
enableMemory: "기억 기능 활성화",
enableMemoryDesc: "활성화하면 시스템이 대화 기록을 저장하고 향후 대화에서 관련 내용을 자동으로 회상하여 더 개인화된 답변을 제공합니다.",
autoCheckUpdate: '업데이트 자동 다운로드',
autoCheckUpdateDesc: '활성화하면 시작 시 최신 버전을 자동으로 확인하고 백그라운드에서 다운로드합니다.',
memoryRequiresNeo4j: "기억 기능은 Neo4j 그래프 데이터베이스가 필요합니다. 이 기능을 활성화하기 전에 Neo4j를 구성하고 활성화해 주세요 (NEO4J_ENABLE=true 설정).",
memoryHowToEnable: "Neo4j 구성 가이드 보기",
parserEngine: "파싱 엔진",

View File

@@ -548,6 +548,8 @@ export default {
webSearchConfig: 'Сетевой поиск',
enableMemory: 'Включить память',
enableMemoryDesc: 'При включении система будет записывать историю ваших разговоров и автоматически вспоминать соответствующий контент в будущих беседах для более персонализированных ответов.',
autoCheckUpdate: 'Автоматическая загрузка обновлений',
autoCheckUpdateDesc: 'При включении автоматически проверять и скачивать последнюю версию в фоновом режиме при запуске.',
memoryRequiresNeo4j: 'Функция памяти требует графовую базу данных Neo4j. Пожалуйста, настройте и включите Neo4j (установите NEO4J_ENABLE=true) перед активацией этой функции.',
memoryHowToEnable: 'Руководство по настройке Neo4j',
parserEngine: 'Движок парсинга',

View File

@@ -423,6 +423,8 @@ export default {
webSearchConfig: "网络搜索",
enableMemory: "开启记忆功能",
enableMemoryDesc: "开启后,系统将记录您的对话历史,并在后续对话中自动回忆相关内容,提供更个性化的回答。",
autoCheckUpdate: '自动下载更新',
autoCheckUpdateDesc: '开启后自动检查并在后台下载最新版本安装包。',
memoryRequiresNeo4j: "记忆功能依赖 Neo4j 图数据库,请先配置并启用 Neo4j设置环境变量 NEO4J_ENABLE=true后再开启此功能。",
memoryHowToEnable: "查看 Neo4j 配置指南",
parserEngine: "解析引擎",

View File

@@ -19,6 +19,7 @@ interface Settings {
conversationModels: ConversationModels;
selectedAgentId: string; // 当前选中的智能体ID
selectedAgentSourceTenantId: string | null; // 当使用共享智能体时,来源租户 ID用于后端 model/KB/MCP 解析)
autoCheckUpdate?: boolean; // 是否自动检查并下载更新
}
// Agent 配置接口
@@ -96,6 +97,7 @@ const defaultSettings: Settings = {
},
selectedAgentId: BUILTIN_QUICK_ANSWER_ID, // 默认选中快速问答模式
selectedAgentSourceTenantId: null as string | null, // 共享智能体来源租户 ID
autoCheckUpdate: true,
};
export const useSettingsStore = defineStore("settings", {
@@ -144,6 +146,9 @@ export const useSettingsStore = defineStore("settings", {
// 记忆功能是否启用
isMemoryEnabled: (state) => state.settings.enableMemory || false,
// 是否自动检查并下载更新
isAutoCheckUpdateEnabled: (state) => state.settings.autoCheckUpdate ?? true,
// 当前选中的智能体ID
selectedAgentId: (state) => state.settings.selectedAgentId || BUILTIN_QUICK_ANSWER_ID,
// 共享智能体来源租户 ID可选
@@ -308,6 +313,12 @@ export const useSettingsStore = defineStore("settings", {
localStorage.setItem("WeKnora_settings", JSON.stringify(this.settings));
},
// 启用/禁用自动检查更新
toggleAutoCheckUpdate(enabled: boolean) {
this.settings.autoCheckUpdate = enabled;
localStorage.setItem("WeKnora_settings", JSON.stringify(this.settings));
},
// File selection actions
addFile(fileId: string) {
if (!this.settings.selectedFiles) this.settings.selectedFiles = [];

View File

@@ -73,6 +73,19 @@
</t-link>
</template>
</t-alert>
<!-- 自动下载更新开关 (Lite edition only) -->
<div class="setting-row" v-if="authStore.isLiteMode">
<div class="setting-info">
<label>{{ $t('settings.autoCheckUpdate') }}</label>
<p class="desc">{{ $t('settings.autoCheckUpdateDesc') }}</p>
</div>
<div class="setting-control">
<t-switch
v-model="isAutoCheckUpdateEnabled"
/>
</div>
</div>
</div>
</div>
</template>
@@ -82,11 +95,13 @@ import { ref, onMounted, computed } from 'vue'
import { MessagePlugin } from 'tdesign-vue-next'
import { useI18n } from 'vue-i18n'
import { useSettingsStore } from '@/stores/settings'
import { useAuthStore } from '@/stores/auth'
import { getSystemInfo } from '@/api/system'
import { useTheme, type ThemeMode } from '@/composables/useTheme'
const { t, locale } = useI18n()
const settingsStore = useSettingsStore()
const authStore = useAuthStore()
const { currentTheme, setTheme } = useTheme()
// 本地状态
@@ -106,6 +121,21 @@ const isMemoryEnabled = computed({
set: (val) => settingsStore.toggleMemory(val)
})
// 自动检查更新状态
const isAutoCheckUpdateEnabled = computed({
get: () => settingsStore.isAutoCheckUpdateEnabled,
set: (val) => {
settingsStore.toggleAutoCheckUpdate(val)
if (val) {
// @ts-ignore
if (window.go && window.go.main && window.go.main.App && window.go.main.App.AutoCheckForUpdates) {
// @ts-ignore
window.go.main.App.AutoCheckForUpdates()
}
}
}
})
// 初始化加载
onMounted(async () => {
// 从 localStorage 加载语言设置

View File

@@ -1,6 +1,10 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function AutoCheckForUpdates():Promise<void>;
export function CheckForUpdates():Promise<void>;
export function GetAPIBaseURL():Promise<string>;
export function GetAPILanBaseURL():Promise<string>;

View File

@@ -2,6 +2,14 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function AutoCheckForUpdates() {
return window['go']['main']['App']['AutoCheckForUpdates']();
}
export function CheckForUpdates() {
return window['go']['main']['App']['CheckForUpdates']();
}
export function GetAPIBaseURL() {
return window['go']['main']['App']['GetAPIBaseURL']();
}

2
go.mod
View File

@@ -63,6 +63,7 @@ require (
go.opentelemetry.io/otel/trace v1.38.0
go.uber.org/dig v1.18.1
golang.org/x/crypto v0.46.0
golang.org/x/mod v0.31.0
golang.org/x/net v0.48.0
golang.org/x/sync v0.19.0
golang.org/x/time v0.14.0
@@ -297,7 +298,6 @@ require (
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/telemetry v0.0.0-20251208220230-2638a1023523 // indirect