Files
code-utils/ai_scope.go

229 lines
8.6 KiB
Go
Raw Normal View History

2026-08-14 07:52:01 +08:00
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
}