mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 19:47:10 +08:00
v0.3.8
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"cursor/internal/ads"
|
||||
|
||||
"github.com/pkg/browser"
|
||||
)
|
||||
|
||||
type AdRuntime = ads.Runtime
|
||||
type AdWindowConfig = ads.WindowConfig
|
||||
|
||||
type AdService struct {
|
||||
core *ads.Service
|
||||
}
|
||||
|
||||
func NewAdService(core *ads.Service) *AdService {
|
||||
return &AdService{core: core}
|
||||
}
|
||||
|
||||
func (service *AdService) GetAdRuntime() (AdRuntime, error) {
|
||||
if service == nil || service.core == nil {
|
||||
return AdRuntime{}, fmt.Errorf("广告服务未初始化")
|
||||
}
|
||||
return service.core.GetRuntime(context.Background())
|
||||
}
|
||||
|
||||
func (service *AdService) OpenExternalURL(rawURL string) error {
|
||||
parsed, err := url.Parse(strings.TrimSpace(rawURL))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scheme := strings.ToLower(strings.TrimSpace(parsed.Scheme))
|
||||
if scheme != "http" && scheme != "https" {
|
||||
return fmt.Errorf("仅支持打开 http/https 地址")
|
||||
}
|
||||
if strings.TrimSpace(parsed.Host) == "" {
|
||||
return fmt.Errorf("地址缺少主机名")
|
||||
}
|
||||
return browser.OpenURL(parsed.String())
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Package bridge 负责向 Wails 暴露稳定的桌面桥接服务与 DTO。
|
||||
package bridge
|
||||
@@ -0,0 +1,50 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"cursor/internal/appdata"
|
||||
"cursor/internal/historymetrics"
|
||||
)
|
||||
|
||||
// HomeMetricsSummary 定义首页展示的历史统计摘要。
|
||||
type HomeMetricsSummary struct {
|
||||
ProviderCallsTotal int `json:"providerCallsTotal"`
|
||||
TurnsTotal int `json:"turnsTotal"`
|
||||
ValidTurnsTotal int `json:"validTurnsTotal"`
|
||||
InvalidTurnsTotal int `json:"invalidTurnsTotal"`
|
||||
RequestTokensTotal int64 `json:"requestTokensTotal"`
|
||||
PromptTokensTotal int64 `json:"promptTokensTotal"`
|
||||
CacheReadTokens int64 `json:"cacheReadTokens"`
|
||||
CacheWriteTokens int64 `json:"cacheWriteTokens"`
|
||||
CacheHitRate *float64 `json:"cacheHitRate"`
|
||||
}
|
||||
|
||||
// MetricsService 定义首页统计相关的 Wails service。
|
||||
type MetricsService struct{}
|
||||
|
||||
// NewMetricsService 创建首页统计 service。
|
||||
func NewMetricsService() *MetricsService {
|
||||
return &MetricsService{}
|
||||
}
|
||||
|
||||
// GetHomeMetricsSummary 返回首页展示的全量历史统计摘要。
|
||||
func (service *MetricsService) GetHomeMetricsSummary() (HomeMetricsSummary, error) {
|
||||
if err := appdata.EnsureAssistantHome(); err != nil {
|
||||
return HomeMetricsSummary{}, err
|
||||
}
|
||||
|
||||
summary, err := historymetrics.LoadUsageSummary(appdata.UsageFilePath())
|
||||
if err != nil {
|
||||
return HomeMetricsSummary{}, err
|
||||
}
|
||||
return HomeMetricsSummary{
|
||||
ProviderCallsTotal: summary.ProviderCallsTotal,
|
||||
TurnsTotal: summary.TurnsTotal,
|
||||
ValidTurnsTotal: summary.ValidTurnsTotal,
|
||||
InvalidTurnsTotal: summary.InvalidTurnsTotal,
|
||||
RequestTokensTotal: summary.RequestTokensTotal,
|
||||
PromptTokensTotal: summary.PromptTokensTotal,
|
||||
CacheReadTokens: summary.CacheReadTokens,
|
||||
CacheWriteTokens: summary.CacheWriteTokens,
|
||||
CacheHitRate: summary.CacheHitRate,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
serverconfig "cursor/internal/backend/server/config"
|
||||
"cursor/internal/certs"
|
||||
"cursor/internal/client"
|
||||
"cursor/internal/mitm"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Public DTOs remain in package main for Wails service compatibility.
|
||||
// ProxyState 定义了当前模块中的 ProxyState 类型。
|
||||
type ProxyState = client.ProxyState
|
||||
|
||||
// UserConfig 定义了当前模块中的 UserConfig 类型。
|
||||
type UserConfig = client.UserConfig
|
||||
|
||||
// ModelAdapterConfig 定义模型测速使用的模型配置结构。
|
||||
type ModelAdapterConfig = serverconfig.ModelAdapterConfig
|
||||
|
||||
// ModelAdapterTestResult 定义一次模型测速结果。
|
||||
type ModelAdapterTestResult = client.ModelAdapterTestResult
|
||||
|
||||
// ModelAdapterTestResultsPayload 定义测速结果事件载荷。
|
||||
type ModelAdapterTestResultsPayload = client.ModelAdapterTestResultsPayload
|
||||
|
||||
// LicenseActionRequest 定义了当前模块中的 LicenseActionRequest 类型。
|
||||
type LicenseActionRequest = client.LicenseActionRequest
|
||||
|
||||
// LicenseSwitchDeviceRequest 定义了当前模块中的 LicenseSwitchDeviceRequest 类型。
|
||||
type LicenseSwitchDeviceRequest = client.LicenseSwitchDeviceRequest
|
||||
|
||||
// LicenseAPIResult 定义了当前模块中的 LicenseAPIResult 类型。
|
||||
type LicenseAPIResult = client.LicenseAPIResult
|
||||
|
||||
// UsageRecordsRequest 定义了当前模块中的 UsageRecordsRequest 类型。
|
||||
type UsageRecordsRequest = client.UsageRecordsRequest
|
||||
|
||||
// UsageRecord 定义了当前模块中的 UsageRecord 类型。
|
||||
type UsageRecord = client.UsageRecord
|
||||
|
||||
// UsageRecordsData 定义了当前模块中的 UsageRecordsData 类型。
|
||||
type UsageRecordsData = client.UsageRecordsData
|
||||
|
||||
// UsageRecordsResult 定义了当前模块中的 UsageRecordsResult 类型。
|
||||
type UsageRecordsResult = client.UsageRecordsResult
|
||||
|
||||
// ProxyService 定义了当前模块中的 ProxyService 类型。
|
||||
type ProxyService struct {
|
||||
// core 表示当前声明中的 core。
|
||||
core *client.ProxyService
|
||||
}
|
||||
|
||||
// NewProxyService 用于处理与 NewProxyService 相关的逻辑。
|
||||
func NewProxyService(proxy *mitm.ProxyServer, certManager *certs.Manager, caCertPEM []byte) *ProxyService {
|
||||
return &ProxyService{core: client.NewProxyService(proxy, certManager, caCertPEM)}
|
||||
}
|
||||
|
||||
// StartProxy 用于处理与 StartProxy 相关的逻辑。
|
||||
func (s *ProxyService) StartProxy() (ProxyState, error) {
|
||||
return s.core.StartProxy()
|
||||
}
|
||||
|
||||
// StopProxy 用于处理与 StopProxy 相关的逻辑。
|
||||
func (s *ProxyService) StopProxy() (ProxyState, error) {
|
||||
return s.core.StopProxy()
|
||||
}
|
||||
|
||||
// GetState 用于处理与 GetState 相关的逻辑。
|
||||
func (s *ProxyService) GetState() ProxyState {
|
||||
return s.core.GetState()
|
||||
}
|
||||
|
||||
// ClearLastError 用于处理与 ClearLastError 相关的逻辑。
|
||||
func (s *ProxyService) ClearLastError() ProxyState {
|
||||
return s.core.ClearLastError()
|
||||
}
|
||||
|
||||
// SetBaseURL 用于处理与 SetBaseURL 相关的逻辑。
|
||||
func (s *ProxyService) SetBaseURL(baseURL string) (ProxyState, error) {
|
||||
return s.core.SetBaseURL(baseURL)
|
||||
}
|
||||
|
||||
// LoadUserConfig 用于处理与 LoadUserConfig 相关的逻辑。
|
||||
func (s *ProxyService) LoadUserConfig() (UserConfig, error) {
|
||||
return s.core.LoadUserConfig()
|
||||
}
|
||||
|
||||
// SaveUserConfig 用于处理与 SaveUserConfig 相关的逻辑。
|
||||
func (s *ProxyService) SaveUserConfig(cfg UserConfig) error {
|
||||
return s.core.SaveUserConfig(cfg)
|
||||
}
|
||||
|
||||
// TestModelAdapter 用于处理与 TestModelAdapter 相关的逻辑。
|
||||
func (s *ProxyService) TestModelAdapter(adapter ModelAdapterConfig) (ModelAdapterTestResult, error) {
|
||||
return s.core.TestModelAdapter(adapter)
|
||||
}
|
||||
|
||||
// GetModelAdapterTestResults 用于处理与 GetModelAdapterTestResults 相关的逻辑。
|
||||
func (s *ProxyService) GetModelAdapterTestResults() []ModelAdapterTestResult {
|
||||
return s.core.GetModelAdapterTestResults()
|
||||
}
|
||||
|
||||
// GetDeviceID 用于处理与 GetDeviceID 相关的逻辑。
|
||||
func (s *ProxyService) GetDeviceID() (string, error) {
|
||||
return s.core.GetDeviceID()
|
||||
}
|
||||
|
||||
// ActivateLicense 用于处理与 ActivateLicense 相关的逻辑。
|
||||
func (s *ProxyService) ActivateLicense(req LicenseActionRequest) (LicenseAPIResult, error) {
|
||||
return s.core.ActivateLicense(req)
|
||||
}
|
||||
|
||||
// BindLicenseDevice 用于处理与 BindLicenseDevice 相关的逻辑。
|
||||
func (s *ProxyService) BindLicenseDevice(req LicenseActionRequest) (LicenseAPIResult, error) {
|
||||
return s.core.BindLicenseDevice(req)
|
||||
}
|
||||
|
||||
// SwitchLicenseDevice 用于处理与 SwitchLicenseDevice 相关的逻辑。
|
||||
func (s *ProxyService) SwitchLicenseDevice(req LicenseSwitchDeviceRequest) (LicenseAPIResult, error) {
|
||||
return s.core.SwitchLicenseDevice(req)
|
||||
}
|
||||
|
||||
// QueryUsageRecords 用于处理与 QueryUsageRecords 相关的逻辑。
|
||||
func (s *ProxyService) QueryUsageRecords(req UsageRecordsRequest) (UsageRecordsResult, error) {
|
||||
return s.core.QueryUsageRecords(req)
|
||||
}
|
||||
|
||||
// ApplyCursorSettings 用于处理与 ApplyCursorSettings 相关的逻辑。
|
||||
func (s *ProxyService) ApplyCursorSettings() error {
|
||||
return s.core.ApplyCursorSettings()
|
||||
}
|
||||
|
||||
// ClearCursorSettings 用于处理与 ClearCursorSettings 相关的逻辑。
|
||||
func (s *ProxyService) ClearCursorSettings() error {
|
||||
return s.core.ClearCursorSettings()
|
||||
}
|
||||
|
||||
// ShutdownForQuit 用于处理与 ShutdownForQuit 相关的逻辑。
|
||||
func (s *ProxyService) ShutdownForQuit() {
|
||||
s.core.ShutdownForQuit()
|
||||
}
|
||||
|
||||
// IsWindows 用于处理与 IsWindows 相关的逻辑。
|
||||
func (s *ProxyService) IsWindows() bool {
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"cursor/internal/buildinfo"
|
||||
"cursor/internal/client"
|
||||
"cursor/internal/updater"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
goruntime "runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/leaanthony/u"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
// modelEditorContext 保存当前模型编辑器窗口的初始化上下文。
|
||||
type modelEditorContext struct {
|
||||
Index int `json:"index"`
|
||||
AdapterJSON string `json:"adapterJSON"`
|
||||
}
|
||||
|
||||
// WindowService 定义了当前模块中的 WindowService 类型。
|
||||
type WindowService struct {
|
||||
app *application.App
|
||||
updater *updater.Manager
|
||||
modelConfigWindow *application.WebviewWindow
|
||||
modelEditorWindow *application.WebviewWindow
|
||||
editorCtx *modelEditorContext
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewWindowService 用于处理与 NewWindowService 相关的逻辑。
|
||||
func NewWindowService() *WindowService {
|
||||
return &WindowService{}
|
||||
}
|
||||
|
||||
// SetApp 用于处理与 SetApp 相关的逻辑。
|
||||
func (s *WindowService) SetApp(app *application.App) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.app = app
|
||||
}
|
||||
|
||||
// SetUpdater 关联更新管理器,供前端手动触发检查更新。
|
||||
func (s *WindowService) SetUpdater(manager *updater.Manager) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.updater = manager
|
||||
}
|
||||
|
||||
// GetAppVersion 返回当前应用版本号。
|
||||
func (s *WindowService) GetAppVersion() string {
|
||||
return buildinfo.CurrentVersion()
|
||||
}
|
||||
|
||||
// CheckForUpdates 触发一次手动检查更新。
|
||||
func (s *WindowService) CheckForUpdates() {
|
||||
s.mu.RLock()
|
||||
manager := s.updater
|
||||
s.mu.RUnlock()
|
||||
if manager == nil {
|
||||
return
|
||||
}
|
||||
manager.CheckNow(true)
|
||||
}
|
||||
|
||||
// InstallReadyUpdate 安装当前已下载完成的更新。
|
||||
func (s *WindowService) InstallReadyUpdate() error {
|
||||
s.mu.RLock()
|
||||
manager := s.updater
|
||||
s.mu.RUnlock()
|
||||
if manager == nil {
|
||||
return fmt.Errorf("更新管理器未初始化")
|
||||
}
|
||||
return manager.InstallReadyUpdate()
|
||||
}
|
||||
|
||||
// OpenConfigWindow 打开本地设置目录。
|
||||
func (s *WindowService) OpenConfigWindow() {
|
||||
_ = os.MkdirAll(client.ResolveSettingsRootPath(), 0o755)
|
||||
openDirectory(client.ResolveSettingsRootPath())
|
||||
}
|
||||
|
||||
// OpenModelConfigWindow 打开模型配置独立窗口。如果窗口已存在则聚焦。
|
||||
func (s *WindowService) OpenModelConfigWindow() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.app == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.modelConfigWindow != nil {
|
||||
s.modelConfigWindow.Show()
|
||||
s.modelConfigWindow.Focus()
|
||||
return
|
||||
}
|
||||
|
||||
win := s.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "模型配置",
|
||||
Width: 980,
|
||||
Height: 700,
|
||||
MinWidth: 820,
|
||||
MinHeight: 560,
|
||||
DisableResize: false,
|
||||
Frameless: goruntime.GOOS == "windows",
|
||||
URL: "/#/model-config",
|
||||
Hidden: false,
|
||||
HideOnEscape: false,
|
||||
MinimiseButtonState: application.ButtonEnabled,
|
||||
MaximiseButtonState: application.ButtonEnabled,
|
||||
CloseButtonState: application.ButtonEnabled,
|
||||
BackgroundColour: application.RGBA{Red: 25, Green: 25, Blue: 25, Alpha: 255},
|
||||
Mac: application.MacWindow{
|
||||
Backdrop: application.MacBackdropLiquidGlass,
|
||||
DisableShadow: false,
|
||||
TitleBar: application.MacTitleBar{
|
||||
AppearsTransparent: true,
|
||||
Hide: false,
|
||||
HideTitle: true,
|
||||
FullSizeContent: true,
|
||||
UseToolbar: false,
|
||||
HideToolbarSeparator: true,
|
||||
},
|
||||
WebviewPreferences: application.MacWebviewPreferences{
|
||||
FullscreenEnabled: u.True,
|
||||
TextInteractionEnabled: u.True,
|
||||
AllowsBackForwardNavigationGestures: u.False,
|
||||
},
|
||||
},
|
||||
Windows: application.WindowsWindow{
|
||||
HiddenOnTaskbar: false,
|
||||
},
|
||||
})
|
||||
|
||||
win.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.modelConfigWindow = nil
|
||||
})
|
||||
|
||||
s.modelConfigWindow = win
|
||||
}
|
||||
|
||||
// OpenModelEditorWindow 打开模型编辑器独立窗口。
|
||||
// index < 0 表示新增,>= 0 表示编辑对应索引的适配器。
|
||||
// adapterJSON 为编辑器初始数据的 JSON 字符串。
|
||||
func (s *WindowService) OpenModelEditorWindow(index int, adapterJSON string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.app == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.editorCtx = &modelEditorContext{
|
||||
Index: index,
|
||||
AdapterJSON: adapterJSON,
|
||||
}
|
||||
|
||||
if s.modelEditorWindow != nil {
|
||||
s.modelEditorWindow.Show()
|
||||
s.modelEditorWindow.Focus()
|
||||
return
|
||||
}
|
||||
|
||||
title := "新增模型配置"
|
||||
if index >= 0 {
|
||||
title = "编辑模型配置"
|
||||
}
|
||||
|
||||
win := s.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: title,
|
||||
Width: 840,
|
||||
Height: 680,
|
||||
MinWidth: 740,
|
||||
MinHeight: 600,
|
||||
DisableResize: false,
|
||||
Frameless: goruntime.GOOS == "windows",
|
||||
URL: fmt.Sprintf("/#/model-editor?index=%d", index),
|
||||
Hidden: false,
|
||||
HideOnEscape: false,
|
||||
MinimiseButtonState: application.ButtonEnabled,
|
||||
MaximiseButtonState: application.ButtonHidden,
|
||||
CloseButtonState: application.ButtonEnabled,
|
||||
BackgroundColour: application.RGBA{Red: 25, Green: 25, Blue: 25, Alpha: 255},
|
||||
Mac: application.MacWindow{
|
||||
Backdrop: application.MacBackdropLiquidGlass,
|
||||
DisableShadow: false,
|
||||
TitleBar: application.MacTitleBar{
|
||||
AppearsTransparent: true,
|
||||
Hide: false,
|
||||
HideTitle: true,
|
||||
FullSizeContent: true,
|
||||
UseToolbar: false,
|
||||
HideToolbarSeparator: true,
|
||||
},
|
||||
WebviewPreferences: application.MacWebviewPreferences{
|
||||
FullscreenEnabled: u.False,
|
||||
TextInteractionEnabled: u.True,
|
||||
AllowsBackForwardNavigationGestures: u.False,
|
||||
},
|
||||
},
|
||||
Windows: application.WindowsWindow{
|
||||
HiddenOnTaskbar: false,
|
||||
},
|
||||
})
|
||||
|
||||
win.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.modelEditorWindow = nil
|
||||
s.editorCtx = nil
|
||||
})
|
||||
|
||||
s.modelEditorWindow = win
|
||||
}
|
||||
|
||||
// GetModelEditorContext 返回当前编辑器窗口的初始化上下文。
|
||||
func (s *WindowService) GetModelEditorContext() map[string]any {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
if s.editorCtx == nil {
|
||||
return map[string]any{
|
||||
"index": -1,
|
||||
"adapterJSON": "{}",
|
||||
}
|
||||
}
|
||||
return map[string]any{
|
||||
"index": s.editorCtx.Index,
|
||||
"adapterJSON": s.editorCtx.AdapterJSON,
|
||||
}
|
||||
}
|
||||
|
||||
// OpenHistoryWindow 用于处理与 OpenHistoryWindow 相关的逻辑。
|
||||
func (s *WindowService) OpenHistoryWindow() {
|
||||
_ = os.MkdirAll(client.ResolveLogsRootPath(), 0o755)
|
||||
openDirectory(client.ResolveLogsRootPath())
|
||||
}
|
||||
|
||||
// openDirectory 用于处理与 openDirectory 相关的逻辑。
|
||||
func openDirectory(path string) {
|
||||
if path == "" {
|
||||
return
|
||||
}
|
||||
switch goruntime.GOOS {
|
||||
case "darwin":
|
||||
_ = exec.Command("open", path).Start()
|
||||
case "windows":
|
||||
_ = exec.Command("explorer", path).Start()
|
||||
default:
|
||||
_ = exec.Command("xdg-open", path).Start()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package bridge
|
||||
|
||||
import "github.com/pkg/browser"
|
||||
|
||||
const footerAuthorHomeURL = "https://space.bilibili.com/311706663/upload/video"
|
||||
|
||||
var footerAuthorInfo = FooterAuthorInfo{
|
||||
ButtonText: "作者 leookun",
|
||||
DialogTitle: "作者寄语",
|
||||
DialogContent: "本软件是纯免费软件,如果你被收费,那大概率就是被骗了。\n欢迎点击访问作者主页 https://space.bilibili.com/311706663/upload/video\n查看更多更新动态、使用分享和后续内容。",
|
||||
DialogConfirmText: "访问主页",
|
||||
DialogCancelText: "关闭",
|
||||
}
|
||||
|
||||
// FooterAuthorInfo 定义首页底部作者入口的展示信息。
|
||||
type FooterAuthorInfo struct {
|
||||
ButtonText string `json:"buttonText"`
|
||||
DialogTitle string `json:"dialogTitle"`
|
||||
DialogContent string `json:"dialogContent"`
|
||||
DialogConfirmText string `json:"dialogConfirmText"`
|
||||
DialogCancelText string `json:"dialogCancelText"`
|
||||
}
|
||||
|
||||
// GetFooterAuthorInfo 返回首页底部作者入口的展示信息。
|
||||
func (s *WindowService) GetFooterAuthorInfo() FooterAuthorInfo {
|
||||
return footerAuthorInfo
|
||||
}
|
||||
|
||||
// OpenFooterAuthorHome 打开作者主页。
|
||||
func (s *WindowService) OpenFooterAuthorHome() error {
|
||||
return browser.OpenURL(footerAuthorHomeURL)
|
||||
}
|
||||
Reference in New Issue
Block a user