优化代码
This commit is contained in:
1 parent
3553790475
commit
eda7e8bc96
23 files changed
+3113
-342
No files matched your search
+76
-28
@@ -30,8 +30,9 @@ type State struct {
|
||||
TotalTokens int
|
||||
LastLatency time.Duration
|
||||
Region capture.Region
|
||||
Model string
|
||||
Profile string
|
||||
Model string // 生效接入点里的模型名
|
||||
Profile string // 生效接入点的名字
|
||||
Format string // 生效接入点的接口格式
|
||||
}
|
||||
|
||||
// Sink 是引擎向界面推送事件的回调集合。
|
||||
@@ -39,7 +40,7 @@ type State struct {
|
||||
type Sink interface {
|
||||
OnUserMessage(text string, withImage bool)
|
||||
OnAssistantStart(auto bool)
|
||||
OnAssistantDelta(delta string)
|
||||
OnAssistantDelta(delta llm.Delta)
|
||||
OnAssistantEnd(usage llm.Usage, err error)
|
||||
OnState(state State)
|
||||
}
|
||||
@@ -72,10 +73,12 @@ func New(cfg *config.Config, store *config.Store) *Engine {
|
||||
store: store,
|
||||
region: cfg.Region,
|
||||
}
|
||||
if prov, err := llm.New(toOptions(cfg.Active())); err != nil {
|
||||
slog.Warn("模型接入点不可用", "err", err)
|
||||
} else {
|
||||
e.provider = prov
|
||||
if active, ok := cfg.ActiveModel(); ok {
|
||||
if prov, err := llm.New(toOptions(active)); err != nil {
|
||||
slog.Warn("模型不可用", "err", err)
|
||||
} else {
|
||||
e.provider = prov
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
@@ -83,6 +86,7 @@ func New(cfg *config.Config, store *config.Store) *Engine {
|
||||
func toOptions(p config.Profile) llm.Options {
|
||||
return llm.Options{
|
||||
Name: p.Name,
|
||||
Format: p.Format,
|
||||
BaseURL: p.BaseURL,
|
||||
APIKey: p.APIKey,
|
||||
Model: p.Model,
|
||||
@@ -120,7 +124,7 @@ func (e *Engine) State() State {
|
||||
func (e *Engine) stateLocked() State {
|
||||
active := config.Profile{}
|
||||
if e.cfg != nil {
|
||||
active = e.cfg.Active()
|
||||
active, _ = e.cfg.ActiveModel()
|
||||
}
|
||||
return State{
|
||||
Watching: e.watching,
|
||||
@@ -131,6 +135,7 @@ func (e *Engine) stateLocked() State {
|
||||
Region: e.region,
|
||||
Model: active.Model,
|
||||
Profile: active.Name,
|
||||
Format: active.Format,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,16 +174,28 @@ func (e *Engine) SetRegion(r capture.Region) error {
|
||||
}
|
||||
|
||||
// ApplyConfig 在设置页保存后刷新引擎内部状态。
|
||||
// 接入点还没填完整时不算失败:配置照样生效,等用户补齐后自动恢复。
|
||||
func (e *Engine) ApplyConfig(cfg *config.Config) error {
|
||||
prov, err := llm.New(toOptions(cfg.Active()))
|
||||
if err != nil {
|
||||
return err
|
||||
if cfg == nil {
|
||||
return errors.New("配置为空")
|
||||
}
|
||||
e.mu.Lock()
|
||||
wasWatching := e.watching
|
||||
// 区域只由框选修改,不被设置页的旧副本覆盖。
|
||||
cfg.Region = e.region
|
||||
e.cfg = cfg
|
||||
e.mu.Unlock()
|
||||
|
||||
var prov llm.Provider
|
||||
if active, ok := cfg.ActiveModel(); ok {
|
||||
p, err := llm.New(toOptions(active))
|
||||
if err != nil {
|
||||
slog.Warn("模型初始化失败", "err", err)
|
||||
} else {
|
||||
prov = p
|
||||
}
|
||||
}
|
||||
e.mu.Lock()
|
||||
e.provider = prov
|
||||
e.mu.Unlock()
|
||||
|
||||
@@ -192,8 +209,8 @@ func (e *Engine) ApplyConfig(cfg *config.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PingProfile 用给定接入点做一次连通性测试。
|
||||
func (e *Engine) PingProfile(ctx context.Context, p config.Profile) error {
|
||||
// PingModel 用给定的模型配置做一次连通性测试。
|
||||
func (e *Engine) PingModel(ctx context.Context, p config.Profile) error {
|
||||
prov, err := llm.New(toOptions(p))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -201,6 +218,43 @@ func (e *Engine) PingProfile(ctx context.Context, p config.Profile) error {
|
||||
return prov.Ping(ctx)
|
||||
}
|
||||
|
||||
// PingActive 用当前生效的接入点做一次连通性测试。
|
||||
func (e *Engine) PingActive(ctx context.Context) error {
|
||||
e.mu.Lock()
|
||||
p, _ := e.cfg.ActiveModel()
|
||||
e.mu.Unlock()
|
||||
return e.PingModel(ctx, p)
|
||||
}
|
||||
|
||||
// EnsureReady 检查模型是否已经接好,供监听前预检。
|
||||
func (e *Engine) EnsureReady() error {
|
||||
e.mu.Lock()
|
||||
m, ok := e.cfg.ActiveModel()
|
||||
ready := e.provider != nil
|
||||
e.mu.Unlock()
|
||||
|
||||
if !ok {
|
||||
return errors.New("还没有接入模型,请到设置里添加接入点并填写 BaseURL 与模型名")
|
||||
}
|
||||
if m.BaseURL == "" || m.Model == "" {
|
||||
return errors.New("还没有接入模型,请到设置里填写 BaseURL 与模型名")
|
||||
}
|
||||
if m.APIKey == "" && !isLocalEndpoint(m.BaseURL) {
|
||||
return errors.New("还没有接入模型,请到设置里填写 API Key")
|
||||
}
|
||||
if !ready {
|
||||
return errors.New("模型初始化失败,请到设置里检查 BaseURL 与模型名")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isLocalEndpoint 判断是不是本机服务,本机服务通常不需要 API Key。
|
||||
func isLocalEndpoint(baseURL string) bool {
|
||||
return strings.Contains(baseURL, "localhost") ||
|
||||
strings.Contains(baseURL, "127.0.0.1") ||
|
||||
strings.Contains(baseURL, "[::1]")
|
||||
}
|
||||
|
||||
// Snapshot 抓取当前区域的 JPEG 预览(用于界面缩略图)。
|
||||
func (e *Engine) Snapshot() ([]byte, error) {
|
||||
e.mu.Lock()
|
||||
@@ -229,6 +283,9 @@ func (e *Engine) Ask(text string, attach bool) error {
|
||||
|
||||
// StartWatch 开始按帧差自动监听。
|
||||
func (e *Engine) StartWatch() error {
|
||||
if err := e.EnsureReady(); err != nil {
|
||||
return err
|
||||
}
|
||||
e.mu.Lock()
|
||||
if e.watching {
|
||||
e.mu.Unlock()
|
||||
@@ -264,18 +321,6 @@ func (e *Engine) StopWatch() {
|
||||
e.emitState()
|
||||
}
|
||||
|
||||
// ToggleWatch 在开始/停止之间切换。
|
||||
func (e *Engine) ToggleWatch() error {
|
||||
e.mu.Lock()
|
||||
on := e.watching
|
||||
e.mu.Unlock()
|
||||
if on {
|
||||
e.StopWatch()
|
||||
return nil
|
||||
}
|
||||
return e.StartWatch()
|
||||
}
|
||||
|
||||
// watchLoop 以低频抓屏 + 帧差判断是否需要提问。
|
||||
func (e *Engine) watchLoop(stop chan struct{}) {
|
||||
e.mu.Lock()
|
||||
@@ -378,7 +423,7 @@ func (e *Engine) run(text string, attach, auto bool) error {
|
||||
}
|
||||
if e.provider == nil {
|
||||
e.mu.Unlock()
|
||||
return errors.New("还没有可用的模型接入点,请到设置里填写")
|
||||
return errors.New("还没有接入模型,请到设置里填写")
|
||||
}
|
||||
e.busy = true
|
||||
prov := e.provider
|
||||
@@ -427,8 +472,11 @@ func (e *Engine) run(text string, attach, auto bool) error {
|
||||
|
||||
var reply strings.Builder
|
||||
start := time.Now()
|
||||
usage, err := prov.Chat(ctx, system, msgs, func(delta string) error {
|
||||
reply.WriteString(delta)
|
||||
usage, err := prov.Chat(ctx, system, msgs, func(delta llm.Delta) error {
|
||||
// 思考过程只往界面送,不进历史:历史里留正文就够了。
|
||||
if delta.Text != "" {
|
||||
reply.WriteString(delta.Text)
|
||||
}
|
||||
if sink != nil {
|
||||
sink.OnAssistantDelta(delta)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user