Files
code-utils/ai_scope.go
2026-08-14 07:52:01 +08:00

229 lines
8.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
// ai_scope.go 提供页面级 AI 总结的上下文聚合:
// 启动台 / 运行日志 / 排期日历 / 配置 / 笔记 五个全局 scope
// 结果统一存入 day_briefsprojectID=0复用 RegenerateAISummary 流程。
import (
"fmt"
"strings"
"time"
)
// scopeSummaryKinds 是页面级总结的合法 kind 集合。
var scopeSummaryKinds = map[string]bool{
"launchpad": true, "logs": true, "calendar": true, "config": true, "notes": true,
}
// writeScopeContext 按 scope 聚合当前应用数据,写入系统提示词。
func (a *App) writeScopeContext(b *strings.Builder, scope string) {
switch scope {
case "launchpad":
a.writeLaunchpadContext(b)
case "logs":
a.writeLogsContext(b)
case "calendar":
a.writeCalendarContext(b)
case "config":
a.writeConfigContext(b)
case "notes":
a.writeNotesContext(b)
}
}
func (a *App) writeLaunchpadContext(b *strings.Builder) {
entries, e := a.ListLaunchEntries()
b.WriteString("\n## 本机运行服务与已保存应用\n")
if e != nil || len(entries) == 0 {
b.WriteString("- 无\n")
return
}
n := 0
for _, x := range entries {
if n >= 40 {
break
}
n++
state := "已停止"
if x.Running {
state = "运行中"
}
saved := "扫描到"
if x.ID > 0 {
saved = "我的应用"
}
ports := x.Ports
if len(ports) == 0 && x.Port > 0 {
ports = []int{x.Port}
}
fmt.Fprintf(b, "- [%s|%s] %s类型 %s端口 %vPID %dCPU %.1f%%,内存 %.0fMBIO %.0fKB/s\n",
saved, state, x.Name, x.Kind, ports, x.PID, x.CPU, x.MemMB, x.IOKBs)
}
}
func (a *App) writeLogsContext(b *strings.Builder) {
logs, e := a.store.Logs("all")
b.WriteString("\n## 最近运行日志(新→旧)\n")
if e != nil || len(logs) == 0 {
b.WriteString("- 无\n")
return
}
counts := map[string]int{}
for _, l := range logs {
counts[l.Level]++
}
fmt.Fprintf(b, "- 总量统计:错误 %d 条,警告 %d 条,信息 %d 条\n", counts["error"], counts["warning"], counts["info"])
for i, l := range logs {
if i >= 200 {
break
}
detail := l.Detail
if r := []rune(detail); len(r) > 120 {
detail = string(r[:120]) + "…"
}
fmt.Fprintf(b, "- %s [%s|%s] %s %s\n", strings.Replace(l.CreatedAt, "T", " ", 1), l.Level, l.Category, l.Message, detail)
}
}
func (a *App) writeCalendarContext(b *strings.Builder) {
now := time.Now()
today := now.Format("2006-01-02")
limit := now.AddDate(0, 0, 30).Format("2006-01-02")
fmt.Fprintf(b, "\n## 排期视角(今天 %s展望 30 天)\n", today)
todos, _ := a.store.ListTodos("all", 0)
tickets, _ := a.store.ListTickets("all", 0)
type item struct{ kind, title, status, priority, due, project string }
var overdue, upcoming []item
push := func(kind, title, status, priority, due, project string, doneLike bool) {
if doneLike {
return
}
d := due
if len(d) > 10 {
d = d[:10]
}
it := item{kind, title, status, priority, due, project}
switch {
case d != "" && d < today:
overdue = append(overdue, it)
case d != "" && d <= limit:
upcoming = append(upcoming, it)
}
}
for _, x := range todos {
push("待办", x.Title, x.Status, x.Priority, x.DueAt, x.ProjectName, x.Status == "done")
}
for _, x := range tickets {
push("工单", x.Title, x.Status, x.Priority, x.DueAt, x.ProjectName, x.Status == "resolved" || x.Status == "closed")
}
b.WriteString("\n## 已逾期\n")
if len(overdue) == 0 {
b.WriteString("- 无\n")
}
for i, x := range overdue {
if i >= 20 {
break
}
fmt.Fprintf(b, "- [%s|%s|%s] %s截止 %s项目 %s\n", x.kind, x.status, x.priority, x.title, x.due, orDash(x.project))
}
b.WriteString("\n## 未来 30 天到期\n")
if len(upcoming) == 0 {
b.WriteString("- 无\n")
}
for i, x := range upcoming {
if i >= 40 {
break
}
fmt.Fprintf(b, "- [%s|%s|%s] %s截止 %s项目 %s\n", x.kind, x.status, x.priority, x.title, x.due, orDash(x.project))
}
}
func (a *App) writeConfigContext(b *strings.Builder) {
st, e := a.store.Settings()
if e != nil {
return
}
keyState := func(k string) string {
if strings.TrimSpace(k) != "" {
return "已配置"
}
return "未配置"
}
b.WriteString("\n## 当前应用配置\n")
fmt.Fprintf(b, "- 界面:主题 %s语言 %s加载动画 %s玻璃透明度 %d%%\n", st.Theme, st.Locale, st.LoadingStyle, st.GlassOpacity)
fmt.Fprintf(b, "- 统计Git 统计范围 %s列表自动刷新 %v\n", st.GitScope, st.AutoRefresh)
fmt.Fprintf(b, "- 系统集成:关闭窗口最小化到托盘 %v\n", st.MinimizeToTray)
fmt.Fprintf(b, "- 自动更新统计:开启 %v模式 %s间隔 %d触发时间 %s\n", st.AutoUpdateEnabled, st.AutoUpdateMode, st.AutoUpdateInterval, st.AutoUpdateTime)
fmt.Fprintf(b, "- AI当前服务商 %s星火 Key %sDeepSeek Key %sKey 云同步 %v\n", st.AIProvider, keyState(st.SparkKey), keyState(st.DeepSeekKey), st.SyncAPIKeys)
fmt.Fprintf(b, "- 图片存储模式:%s\n", st.ImageMode)
if rules, e := a.store.Rules(); e == nil {
custom := 0
for _, r := range rules {
if !r.Builtin {
custom++
}
}
fmt.Fprintf(b, "- 排除规则:共 %d 条(自定义 %d 条)\n", len(rules), custom)
}
if projects, e := a.store.ListProjects(0); e == nil {
fmt.Fprintf(b, "- 项目:共 %d 个\n", len(projects))
}
if a.syncUserID() > 0 {
fmt.Fprintf(b, "- 云同步:已登录(%s\n", a.store.Meta("sync_username"))
} else {
b.WriteString("- 云同步:未登录\n")
}
}
func (a *App) writeNotesContext(b *strings.Builder) {
notes, e := a.store.ListNotes(200)
b.WriteString("\n## 全部笔记(新→旧)\n")
if e != nil || len(notes) == 0 {
b.WriteString("- 无\n")
return
}
for i, n := range notes {
if i >= 100 {
break
}
content := strings.TrimSpace(n.Content)
if r := []rune(content); len(r) > 400 {
content = string(r[:400]) + "…"
}
fmt.Fprintf(b, "\n### 笔记 %d更新于 %s\n%s\n", i+1, strings.Replace(n.UpdatedAt, "T", " ", 1), content)
}
}
// scopeInstruction 页面级总结指令(与 summaryInstruction 同构,按 locale 切换)。
func scopeInstruction(kind, locale string) (string, bool) {
if locale == "en" {
switch kind {
case "launchpad":
return "Based on the services above, summarize what is running on this machine (within 200 words): notable resource hogs, suspicious or duplicated services, and cleanup suggestions. Output a short Markdown list.", true
case "logs":
return "Based on the logs above, summarize recent application activity (within 200 words): recurring errors or warnings with likely causes, notable events, and suggested follow-ups. Output a short Markdown list.", true
case "calendar":
return "Based on the schedule above, summarize the coming month (within 200 words): overdue items to rescue first, busy periods, and scheduling advice. Only reference listed items. Output a short Markdown list.", true
case "config":
return "Based on the configuration above, review the app setup (within 200 words): risky or missing settings (e.g. AI keys, auto update, sync), and concrete tuning suggestions. Output a short Markdown list.", true
case "notes":
return "Based on the notes above, produce a digest (within 200 words): main themes, actionable items hidden in notes, and anything worth converting into todos. Only reference listed notes. Output a short Markdown list.", true
}
return "", false
}
switch kind {
case "launchpad":
return "基于以上服务清单,用不超过 250 字总结本机运行情况:资源占用突出的服务、可疑或重复的进程、值得关停或固化为常用应用的建议。直接输出 Markdown 列表,不要大标题。", true
case "logs":
return "基于以上日志,用不超过 250 字总结应用近期运行状况:反复出现的错误/警告及可能原因、值得关注的事件、建议的后续动作。直接输出 Markdown 列表,不要大标题。", true
case "calendar":
return "基于以上排期数据,用不超过 250 字总结未来一个月的日程:需要优先补救的逾期项、任务密集的时间段、排期调整建议。只能引用上面列出的条目,不要编造。直接输出 Markdown 列表,不要大标题。", true
case "config":
return "基于以上配置,用不超过 250 字点评当前应用设置:存在风险或缺失的配置(如 AI Key、自动统计、云同步以及具体的调优建议。直接输出 Markdown 列表,不要大标题。", true
case "notes":
return "基于以上笔记,用不超过 250 字生成一份笔记摘要:主要主题、笔记里隐藏的待办事项、值得转成正式待办的内容。只能引用上面列出的笔记,不要编造。直接输出 Markdown 列表,不要大标题。", true
}
return "", false
}