Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ func (a *App) initBackend() error {
// ── 创建跨服务的用量跟踪器 ──
a.usageTracker = services.NewUsageTracker(s.DataDir())

// ── Windsurf → Devin 账号数据迁移 ──
// 自动检测 Devin 安装并将 windsurf_auth.json 平滑迁移为 devin_auth.json
if migrated, err := services.MigrateWindsurfAuthToDevin(); err == nil && migrated {
log.Printf("[WindsurfTools] Windsurf → Devin auth 数据迁移完成")
}

// ── MITM Proxy & OpenAI Relay(回调钩子统一在 wireProxyCallbacks 注册)──
a.mitmProxy = services.NewMitmProxy(a.windsurfSvc, func(msg string) {
utils.DLog("%s", msg)
Expand Down
52 changes: 52 additions & 0 deletions app_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,58 @@ func (a *App) DeleteProviderAccount(id string) error {
return a.providerStore.DeleteProvider(id)
}

// TestProviderAPIResult API 测试结果。
type TestProviderAPIResult struct {
OK bool `json:"ok"`
Error string `json:"error,omitempty"`
ModelCount int `json:"model_count"`
ModelSample []string `json:"model_sample,omitempty"`
LatencyMs int64 `json:"latency_ms"`
}

// TestProviderAPI 测试提供商 API 配置是否正确。
// 不需要 ID,直接用传入的 provider/baseURL/token 进行测试。
func (a *App) TestProviderAPI(provider, baseURL, token string) TestProviderAPIResult {
provider = strings.TrimSpace(strings.ToLower(provider))
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
token = strings.TrimSpace(token)

if provider == "" || baseURL == "" || token == "" {
return TestProviderAPIResult{Error: "provider / base_url / token 不能为空"}
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

var httpClient *http.Client
if a.transportPool != nil {
httpClient = a.transportPool.Client()
}

start := time.Now()
list, err := services.FetchProviderModels(ctx, httpClient, provider, baseURL, token)
latency := time.Since(start).Milliseconds()

if err != nil {
return TestProviderAPIResult{
Error: err.Error(),
LatencyMs: latency,
}
}

sample := list
if len(sample) > 5 {
sample = sample[:5]
}

return TestProviderAPIResult{
OK: true,
ModelCount: len(list),
ModelSample: sample,
LatencyMs: latency,
}
}

// providerAccountDisplayName 给 ImportResult.Email 拼一个人类可读的占位名,
// 风格同 ImportByAPIKey(token 前 12 + 后 6)。
func providerAccountDisplayName(provider, token string) string {
Expand Down
10 changes: 10 additions & 0 deletions app_runtime.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package main

import (
"windsurf-tools-wails/backend/services"
)

func (a *App) supportsTray() bool {
if a != nil && a.traySupportedFn != nil {
return a.traySupportedFn()
}
return traySupported()
}

// GetIDEInfo 返回当前检测到的 IDE 信息(Devin 或 Windsurf)。
// 前端可据此调整 UI 标签和行为。
func (a *App) GetIDEInfo() map[string]string {
return services.GetIDEInfo()
}
39 changes: 36 additions & 3 deletions backend/app/diagnose/diagnose.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,18 +231,51 @@ func checkAppDataDirWritable(dir string) Check {
}

func checkWindsurfInstalled() Check {
c := Check{ID: "windsurf", Title: "Windsurf IDE 安装"}
c := Check{ID: "windsurf", Title: "Devin / Windsurf IDE 安装"}

// 先检查 Devin(新产品,优先)
for _, p := range devinCandidatesByOS() {
if _, err := os.Stat(p); err == nil {
c.Status, c.Detail = "ok", "Devin 已安装: "+p
return c
}
}
// 再检查 Windsurf(旧产品,兼容)
for _, p := range windsurfCandidatesByOS() {
if _, err := os.Stat(p); err == nil {
c.Status, c.Detail = "ok", "Windsurf 已安装: "+p
return c
}
}
c.Status, c.Detail = "warn", "未在常见路径找到 Windsurf(仍可手动指定)"
c.FixHint = "下载安装 https://windsurf.com/"

c.Status, c.Detail = "warn", "未在常见路径找到 Devin 或 Windsurf(仍可手动指定)"
c.FixHint = "下载安装 https://devin.ai/desktop"
return c
}

func devinCandidatesByOS() []string {
home, _ := os.UserHomeDir()
switch runtime.GOOS {
case "darwin":
return []string{
"/Applications/Devin.app",
home + "/Applications/Devin.app",
}
case "windows":
appdata := os.Getenv("LOCALAPPDATA")
return []string{
appdata + `\Programs\Devin\Devin.exe`,
`C:\Program Files\Devin\Devin.exe`,
}
default:
return []string{
"/usr/share/devin",
"/opt/Devin",
home + "/.devin",
}
}
}

func windsurfCandidatesByOS() []string {
home, _ := os.UserHomeDir()
switch runtime.GOOS {
Expand Down
Loading