229 lines
8.6 KiB
Go
229 lines
8.6 KiB
Go
|
|
package main
|
|||
|
|
|
|||
|
|
// ai_scope.go 提供页面级 AI 总结的上下文聚合:
|
|||
|
|
// 启动台 / 运行日志 / 排期日历 / 配置 / 笔记 五个全局 scope,
|
|||
|
|
// 结果统一存入 day_briefs(projectID=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,端口 %v,PID %d)CPU %.1f%%,内存 %.0fMB,IO %.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 %s,DeepSeek Key %s,Key 云同步 %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
|
|||
|
|
}
|