feat: you can summary screen shot directly
This commit is contained in:
+174
-18
@@ -10,12 +10,12 @@ package domain
|
||||
// endpoint, etc.).
|
||||
//
|
||||
// Field design notes:
|
||||
// - PathPrefix supports object name templating so users can group screenshots
|
||||
// by date.
|
||||
// - PublicURLBase is optional to support CDN-fronted buckets; otherwise the
|
||||
// adapter falls back to "{Endpoint}/{Bucket}/{Key}" (path-style).
|
||||
// - UsePathStyle defaults to true because many self-hosted MinIO / R2
|
||||
// deployments do not support virtual-hosted-style addressing.
|
||||
// - PathPrefix supports object name templating so users can group screenshots
|
||||
// by date.
|
||||
// - PublicURLBase is optional to support CDN-fronted buckets; otherwise the
|
||||
// adapter falls back to "{Endpoint}/{Bucket}/{Key}" (path-style).
|
||||
// - UsePathStyle defaults to true because many self-hosted MinIO / R2
|
||||
// deployments do not support virtual-hosted-style addressing.
|
||||
type S3Config struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Region string `json:"region"`
|
||||
@@ -34,17 +34,17 @@ type S3Config struct {
|
||||
// - Host / Port form the destination endpoint. Port defaults to 22 when 0.
|
||||
// - User is the SSH login name.
|
||||
// - AuthMethod selects how the connection authenticates:
|
||||
// "password" → password only, via the in-process Go SSH client.
|
||||
// "key" → ssh-agent + ~/.ssh/id_* key files (no password),
|
||||
// via the in-process Go SSH client.
|
||||
// "kerberos" → delegate to the system /usr/bin/ssh binary so the
|
||||
// existing Kerberos (GSSAPI) credential cache from
|
||||
// `kinit` is reused. Native GSSAPI is required here
|
||||
// because macOS stores tickets in an API: ccache
|
||||
// that pure-Go SSH libraries cannot read.
|
||||
// "" / "builtin" → legacy combined behaviour (password → agent → key
|
||||
// files) kept only for backward compatibility with
|
||||
// configs written before the method was split.
|
||||
// "password" → password only, via the in-process Go SSH client.
|
||||
// "key" → ssh-agent + ~/.ssh/id_* key files (no password),
|
||||
// via the in-process Go SSH client.
|
||||
// "kerberos" → delegate to the system /usr/bin/ssh binary so the
|
||||
// existing Kerberos (GSSAPI) credential cache from
|
||||
// `kinit` is reused. Native GSSAPI is required here
|
||||
// because macOS stores tickets in an API: ccache
|
||||
// that pure-Go SSH libraries cannot read.
|
||||
// "" / "builtin" → legacy combined behaviour (password → agent → key
|
||||
// files) kept only for backward compatibility with
|
||||
// configs written before the method was split.
|
||||
// - Password is optional; when empty the implementation falls back to the
|
||||
// local SSH agent or ~/.ssh/id_* keys (i.e. the user is responsible for
|
||||
// having password-less access already configured on this machine).
|
||||
@@ -72,6 +72,29 @@ type SSHConfig struct {
|
||||
ConnectTimeoutSecs int `json:"connectTimeoutSecs"`
|
||||
}
|
||||
|
||||
// LLMProviderConfig stores one OpenAI-compatible multimodal chat endpoint.
|
||||
//
|
||||
// The three built-in providers (Qwen, Doubao/Ark, OpenAI-compatible) all use
|
||||
// the same request shape: chat/completions with a user content array that
|
||||
// contains text plus an image_url item. Keeping them as provider records lets
|
||||
// users switch between credentials/models without retyping every field.
|
||||
type LLMProviderConfig struct {
|
||||
Label string `json:"label"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIKey string `json:"apiKey"`
|
||||
Model string `json:"model"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
TimeoutSecs int `json:"timeoutSecs"`
|
||||
}
|
||||
|
||||
// LLMConfig controls screenshot summarisation.
|
||||
type LLMConfig struct {
|
||||
ActiveProvider string `json:"activeProvider"`
|
||||
Prompt string `json:"prompt"`
|
||||
Providers map[string]LLMProviderConfig `json:"providers"`
|
||||
}
|
||||
|
||||
// SSH authentication method identifiers stored in SSHConfig.AuthMethod.
|
||||
const (
|
||||
// SSHAuthBuiltin is the legacy combined method (password → agent → key
|
||||
@@ -87,6 +110,22 @@ const (
|
||||
SSHAuthKerberos = "kerberos"
|
||||
)
|
||||
|
||||
// Built-in LLM provider identifiers.
|
||||
const (
|
||||
LLMProviderQwen = "qwen"
|
||||
LLMProviderDoubao = "doubao"
|
||||
LLMProviderOpenAI = "openai"
|
||||
)
|
||||
|
||||
const DefaultSummaryPrompt = `你是一个截图内容总结助手。请阅读截图,并用中文输出一段适合直接粘贴到聊天、工单、Issue 或 PR 中的说明。
|
||||
|
||||
要求:
|
||||
- 先说明截图中最重要的信息和用户可能想表达的意图。
|
||||
- 如果截图包含报错、异常状态、表格、代码、界面控件或关键数字,请准确提取。
|
||||
- 如果截图内容不足以判断,不要编造;直接说明不确定点。
|
||||
- 输出尽量简洁,默认 3 到 6 条要点。
|
||||
- 不要输出 Markdown 标题,不要寒暄。`
|
||||
|
||||
// IsKerberos reports whether this config requests Kerberos/GSSAPI auth.
|
||||
func (c SSHConfig) IsKerberos() bool {
|
||||
return c.AuthMethod == SSHAuthKerberos
|
||||
@@ -108,11 +147,14 @@ type AppConfig struct {
|
||||
// SSH holds the configuration for the optional "save to remote via scp"
|
||||
// destination triggered by the save-remote toolbar button.
|
||||
SSH SSHConfig `json:"ssh"`
|
||||
|
||||
// LLM holds provider settings for the "copy summary" screenshot action.
|
||||
LLM LLMConfig `json:"llm"`
|
||||
}
|
||||
|
||||
// DefaultAppConfig returns sane zero-value defaults used on first launch.
|
||||
func DefaultAppConfig() AppConfig {
|
||||
return AppConfig{
|
||||
cfg := AppConfig{
|
||||
Hotkey: "cmd+shift+a",
|
||||
S3: S3Config{
|
||||
PathPrefix: "snapgo/",
|
||||
@@ -124,6 +166,102 @@ func DefaultAppConfig() AppConfig {
|
||||
ConnectTimeoutSecs: 10,
|
||||
StrictHostKey: false,
|
||||
},
|
||||
LLM: DefaultLLMConfig(),
|
||||
}
|
||||
cfg.Normalize()
|
||||
return cfg
|
||||
}
|
||||
|
||||
// DefaultLLMConfig returns the built-in provider presets. API keys and exact
|
||||
// models remain user-editable because each provider account may expose
|
||||
// different model names / endpoint IDs.
|
||||
func DefaultLLMConfig() LLMConfig {
|
||||
return LLMConfig{
|
||||
ActiveProvider: LLMProviderOpenAI,
|
||||
Prompt: DefaultSummaryPrompt,
|
||||
Providers: map[string]LLMProviderConfig{
|
||||
LLMProviderQwen: {
|
||||
Label: "阿里千问多模态",
|
||||
BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
Model: "qwen-vl-plus",
|
||||
MaxTokens: 600,
|
||||
Temperature: 0.2,
|
||||
TimeoutSecs: 60,
|
||||
},
|
||||
LLMProviderDoubao: {
|
||||
Label: "火山方舟豆包多模态",
|
||||
BaseURL: "https://ark.cn-beijing.volces.com/api/v3",
|
||||
Model: "",
|
||||
MaxTokens: 600,
|
||||
Temperature: 0.2,
|
||||
TimeoutSecs: 60,
|
||||
},
|
||||
LLMProviderOpenAI: {
|
||||
Label: "ChatGPT / OpenAI-compatible",
|
||||
BaseURL: "https://api.openai.com/v1",
|
||||
Model: "gpt-5.5",
|
||||
MaxTokens: 600,
|
||||
Temperature: 0.2,
|
||||
TimeoutSecs: 60,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize fills defaults into configs written by older app versions while
|
||||
// preserving any user-provided provider fields.
|
||||
func (c *AppConfig) Normalize() {
|
||||
if c.Hotkey == "" {
|
||||
c.Hotkey = "cmd+shift+a"
|
||||
}
|
||||
if c.S3.PathPrefix == "" {
|
||||
c.S3.PathPrefix = "snapgo/"
|
||||
}
|
||||
if c.SSH.Port == 0 {
|
||||
c.SSH.Port = 22
|
||||
}
|
||||
if c.SSH.PathPrefix == "" {
|
||||
c.SSH.PathPrefix = "snapgo/"
|
||||
}
|
||||
if c.SSH.ConnectTimeoutSecs == 0 {
|
||||
c.SSH.ConnectTimeoutSecs = 10
|
||||
}
|
||||
|
||||
defaultLLM := DefaultLLMConfig()
|
||||
if c.LLM.ActiveProvider == "" {
|
||||
c.LLM.ActiveProvider = defaultLLM.ActiveProvider
|
||||
}
|
||||
if c.LLM.Prompt == "" {
|
||||
c.LLM.Prompt = defaultLLM.Prompt
|
||||
}
|
||||
if c.LLM.Providers == nil {
|
||||
c.LLM.Providers = map[string]LLMProviderConfig{}
|
||||
}
|
||||
for id, def := range defaultLLM.Providers {
|
||||
current, ok := c.LLM.Providers[id]
|
||||
if !ok {
|
||||
c.LLM.Providers[id] = def
|
||||
continue
|
||||
}
|
||||
if current.Label == "" {
|
||||
current.Label = def.Label
|
||||
}
|
||||
if current.BaseURL == "" {
|
||||
current.BaseURL = def.BaseURL
|
||||
}
|
||||
if current.Model == "" && id != LLMProviderDoubao {
|
||||
current.Model = def.Model
|
||||
}
|
||||
if current.MaxTokens == 0 {
|
||||
current.MaxTokens = def.MaxTokens
|
||||
}
|
||||
if current.Temperature < 0 {
|
||||
current.Temperature = def.Temperature
|
||||
}
|
||||
if current.TimeoutSecs == 0 {
|
||||
current.TimeoutSecs = def.TimeoutSecs
|
||||
}
|
||||
c.LLM.Providers[id] = current
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,3 +279,21 @@ func (c AppConfig) IsS3Configured() bool {
|
||||
func (c AppConfig) IsSSHConfigured() bool {
|
||||
return c.SSH.Host != "" && c.SSH.User != ""
|
||||
}
|
||||
|
||||
// ActiveLLMProvider returns the selected provider config plus a boolean
|
||||
// indicating whether the selection exists.
|
||||
func (c AppConfig) ActiveLLMProvider() (string, LLMProviderConfig, bool) {
|
||||
id := c.LLM.ActiveProvider
|
||||
if id == "" {
|
||||
id = LLMProviderOpenAI
|
||||
}
|
||||
provider, ok := c.LLM.Providers[id]
|
||||
return id, provider, ok
|
||||
}
|
||||
|
||||
// IsLLMConfigured reports whether the selected multimodal provider has the
|
||||
// fields required to make a request.
|
||||
func (c AppConfig) IsLLMConfigured() bool {
|
||||
_, provider, ok := c.ActiveLLMProvider()
|
||||
return ok && provider.BaseURL != "" && provider.APIKey != "" && provider.Model != ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user