From 52bdfd388ac8bd19b9c315b81e07c482b0cb5757 Mon Sep 17 00:00:00 2001 From: wizardchen Date: Sat, 11 Apr 2026 12:35:25 +0800 Subject: [PATCH] feat: implement desktop app structure with API base URL handling and bindings for Wails integration --- cmd/desktop/app.go | 38 ++++++++++ cmd/desktop/main.go | 40 +++++------ cmd/desktop/main_bindings.go | 17 +++++ frontend/src/i18n/locales/en-US.ts | 4 ++ frontend/src/i18n/locales/ko-KR.ts | 4 ++ frontend/src/i18n/locales/ru-RU.ts | 4 ++ frontend/src/i18n/locales/zh-CN.ts | 4 ++ frontend/src/views/settings/ApiInfo.vue | 93 +++++++++++++++++++++++++ frontend/src/wailsjs/go/main/App.d.ts | 4 ++ frontend/src/wailsjs/go/main/App.js | 7 ++ scripts/package-mac-app.sh | 17 ++++- 11 files changed, 206 insertions(+), 26 deletions(-) create mode 100644 cmd/desktop/app.go create mode 100644 cmd/desktop/main_bindings.go create mode 100755 frontend/src/wailsjs/go/main/App.d.ts create mode 100755 frontend/src/wailsjs/go/main/App.js diff --git a/cmd/desktop/app.go b/cmd/desktop/app.go new file mode 100644 index 00000000..70c168d1 --- /dev/null +++ b/cmd/desktop/app.go @@ -0,0 +1,38 @@ +package main + +import ( + "context" + "strings" +) + +// App holds Wails-bound state for the desktop shell. +type App struct { + ctx context.Context + backendURL string + shutdownCh chan struct{} +} + +// NewApp creates a new App application struct. +func NewApp() *App { + return &App{ + shutdownCh: make(chan struct{}, 1), + } +} + +// startup is called when the application starts. +func (a *App) startup(ctx context.Context) { + a.ctx = ctx +} + +func (a *App) shutdown(ctx context.Context) { + a.shutdownCh <- struct{}{} +} + +// GetAPIBaseURL returns the local HTTP base URL for REST API calls (e.g. http://127.0.0.1:PORT/api/v1). +// The desktop shell proxies the webview to this address; window.location.origin is not the API host. +func (a *App) GetAPIBaseURL() string { + if a.backendURL == "" { + return "" + } + return strings.TrimRight(a.backendURL, "/") + "/api/v1" +} diff --git a/cmd/desktop/main.go b/cmd/desktop/main.go index a9be2a42..3ad8588b 100644 --- a/cmd/desktop/main.go +++ b/cmd/desktop/main.go @@ -1,3 +1,5 @@ +//go:build !bindings + package main import ( @@ -10,6 +12,7 @@ import ( "os" "os/signal" "path/filepath" + "strconv" "strings" "syscall" "time" @@ -113,6 +116,13 @@ func main() { if errPath == nil && strings.Contains(execPath, ".app/Contents/MacOS") { resPath := filepath.Join(filepath.Dir(filepath.Dir(execPath)), "Resources") _ = os.Chdir(resPath) + } else if _, err := os.Stat(filepath.Join("config", "config.yaml")); os.IsNotExist(err) { + // wails build 生成绑定时 cwd 多为 cmd/desktop,LoadConfig 默认找 ./config/config.yaml; + // 仓库实际配置在 /config/,向上两级即可。 + repoRoot := filepath.Clean(filepath.Join("..", "..")) + if _, err := os.Stat(filepath.Join(repoRoot, "config", "config.yaml")); err == nil { + _ = os.Chdir(repoRoot) + } } // Load .env explicitly for the desktop app so DB_DRIVER gets loaded @@ -246,6 +256,12 @@ func main() { OnStartup: app.startup, OnDomReady: func(ctx context.Context) { wailsruntime.WindowExecJS(ctx, dragHandlerJS) + // 注入真实 API 根路径(与 window.location.origin 不同);无 Go 绑定时仍可显示。 + if u := strings.TrimSpace(app.backendURL); u != "" { + apiRoot := strings.TrimRight(u, "/") + "/api/v1" + inject := fmt.Sprintf(`try{window.__WEKNORA_API_BASE__=%s}catch(e){}`, strconv.Quote(apiRoot)) + wailsruntime.WindowExecJS(ctx, inject) + } }, OnShutdown: app.shutdown, Bind: []interface{}{ @@ -264,30 +280,6 @@ func main() { } } -// App struct -type App struct { - ctx context.Context - backendURL string - shutdownCh chan struct{} -} - -// NewApp creates a new App application struct -func NewApp() *App { - return &App{ - shutdownCh: make(chan struct{}, 1), - } -} - -// startup is called when the app starts. The context is saved -// so we can call the runtime methods -func (a *App) startup(ctx context.Context) { - a.ctx = ctx -} - -func (a *App) shutdown(ctx context.Context) { - a.shutdownCh <- struct{}{} -} - func configureDesktopStorage(execPath string) { if execPath == "" || !strings.Contains(execPath, ".app/Contents/MacOS") { return diff --git a/cmd/desktop/main_bindings.go b/cmd/desktop/main_bindings.go new file mode 100644 index 00000000..650d377d --- /dev/null +++ b/cmd/desktop/main_bindings.go @@ -0,0 +1,17 @@ +//go:build bindings + +package main + +import ( + "github.com/wailsapp/wails/v2" + "github.com/wailsapp/wails/v2/pkg/options" +) + +// Wails「生成绑定」阶段使用 -tags bindings 单独编译本文件,不启动 Gin/数据库,避免依赖本机 Postgres。 +func main() { + app := NewApp() + _ = wails.Run(&options.App{ + Title: "WeKnora Lite", + Bind: []interface{}{app}, + }) +} diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 82034586..3b02a9f7 100755 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -1955,6 +1955,10 @@ export default { description: 'View and manage your API key', keyLabel: 'API Key', keyDescription: 'Secret used for API requests. Keep it safe.', + urlLabel: 'API URL', + urlDescription: 'Base path for REST API requests; append the specific endpoint when calling.', + copyUrlTitle: 'Copy API URL', + urlCopySuccess: 'API URL copied to clipboard', copyTitle: 'Copy API Key', docLabel: 'API Documentation', docDescription: 'View complete API documentation and examples,', diff --git a/frontend/src/i18n/locales/ko-KR.ts b/frontend/src/i18n/locales/ko-KR.ts index 80d14db4..b9d07009 100755 --- a/frontend/src/i18n/locales/ko-KR.ts +++ b/frontend/src/i18n/locales/ko-KR.ts @@ -1403,6 +1403,10 @@ export default { description: "API 키 보기 및 관리", keyLabel: "API 키", keyDescription: "API 호출에 사용되는 키, 안전하게 보관하세요", + urlLabel: "API URL", + urlDescription: "REST API 기본 경로입니다. 호출 시 구체적인 엔드포인트 경로를 이어서 사용하세요.", + copyUrlTitle: "API URL 복사", + urlCopySuccess: "API URL이 클립보드에 복사되었습니다", copyTitle: "API 키 복사", docLabel: "API 문서", docDescription: "전체 API 호출 문서 및 예시 보기, ", diff --git a/frontend/src/i18n/locales/ru-RU.ts b/frontend/src/i18n/locales/ru-RU.ts index 94908497..851056fd 100755 --- a/frontend/src/i18n/locales/ru-RU.ts +++ b/frontend/src/i18n/locales/ru-RU.ts @@ -1250,6 +1250,10 @@ export default { description: 'Просматривайте и управляйте своим API-ключом', keyLabel: 'API Key', keyDescription: 'Ключ для API-запросов. Храните его в безопасности.', + urlLabel: 'URL API', + urlDescription: 'Базовый путь REST API; при вызове добавляйте конкретный путь эндпоинта.', + copyUrlTitle: 'Скопировать URL API', + urlCopySuccess: 'URL API скопирован в буфер обмена', copyTitle: 'Скопировать API Key', docLabel: 'Документация API', docDescription: 'Ознакомьтесь с полной документацией и примерами API,', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index d6e6b813..f27e7235 100755 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -1397,6 +1397,10 @@ export default { description: "查看和管理您的 API 密钥", keyLabel: "API Key", keyDescription: "用于 API 调用的密钥,请妥善保管", + urlLabel: "API 地址", + urlDescription: "REST API 的基础路径,请求时在末尾拼接具体接口路径", + copyUrlTitle: "复制 API 地址", + urlCopySuccess: "API 地址已复制到剪贴板", copyTitle: "复制 API Key", docLabel: "API 文档", docDescription: "查看完整的 API 调用文档和示例,", diff --git a/frontend/src/views/settings/ApiInfo.vue b/frontend/src/views/settings/ApiInfo.vue index 7b40c5f8..e97e078e 100644 --- a/frontend/src/views/settings/ApiInfo.vue +++ b/frontend/src/views/settings/ApiInfo.vue @@ -55,6 +55,32 @@ + +
+
+ +

{{ $t('tenant.api.urlDescription') }}

+
+
+
+ + + + +
+
+
+
@@ -123,6 +149,7 @@ diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts new file mode 100755 index 00000000..daaea49d --- /dev/null +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -0,0 +1,4 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function GetAPIBaseURL():Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js new file mode 100755 index 00000000..7f89c080 --- /dev/null +++ b/frontend/src/wailsjs/go/main/App.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function GetAPIBaseURL() { + return window['go']['main']['App']['GetAPIBaseURL'](); +} diff --git a/scripts/package-mac-app.sh b/scripts/package-mac-app.sh index 7f4971c2..b7944ebd 100755 --- a/scripts/package-mac-app.sh +++ b/scripts/package-mac-app.sh @@ -26,11 +26,19 @@ if [ "${SKIP_FRONTEND:-}" != "1" ]; then if [ -f frontend/package.json ]; then echo ">> Building frontend..." (cd frontend && npm ci --prefer-offline && npm run build) + # Lite 后端从 ./web 提供 SPA(见 internal/router serveFrontendStatic),与 package-lite.sh 一致须用 dist 更新 web + echo ">> Sync frontend/dist -> web/" + rm -rf web + cp -r frontend/dist web else echo ">> No frontend/package.json found, skipping frontend build" fi fi +if [ ! -f web/index.html ]; then + echo "WARNING: web/index.html not found. Lite 桌面会从 Resources/web 提供前端;请先完整构建前端(勿使用 SKIP_FRONTEND),或手动: cp -r frontend/dist web" +fi + # ── Step 2: Build with Wails ── echo ">> Building Wails Desktop App..." @@ -48,12 +56,17 @@ export CGO_CFLAGS="-Wno-deprecated-declarations" export CGO_LDFLAGS="-Wl,-no_warn_duplicate_libraries" export EDITION=lite +# Milvus 与 Qdrant 的 gRPC 生成代码均注册名为 "common.proto" 的描述符,同一进程内会冲突。 +# -ldflags -X conflictPolicy=warn 只作用于最终链接,无法覆盖 Wails「生成绑定」时单独启动的 go 子进程。 +# 官方做法:见 https://protobuf.dev/reference/go/faq#namespace-conflict +export GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn + # 获取版本号并配置 LDFLAGS eval "$(./scripts/get_version.sh env)" LDFLAGS="$(./scripts/get_version.sh ldflags) -X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" -# 我们实际上只用 Wails 构建外壳,前端仍然由后台服务提供 -(cd cmd/desktop && wails build -skipbindings -clean -tags "sqlite_fts5" -ldflags="$LDFLAGS" -o "${APP_NAME}") +# 默认生成 Wails 绑定(供 window.go.main.App.* 等);若加 -skipbindings 则需在 OnDomReady 注入 __WEKNORA_API_BASE__ +(cd cmd/desktop && wails build -clean -tags "sqlite_fts5" -ldflags="$LDFLAGS" -o "${APP_NAME}") # ── Step 3: Copy generated .app to dist ── echo ">> Assembling package..."