新增: 电子相册全屏模式+开机启动+Win10兼容
This commit is contained in:
126
knowledge.go
126
knowledge.go
@@ -9,14 +9,16 @@ import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
const cpaURL = "https://cpa.1216.top/v1/chat/completions"
|
||||
const cpaKey = "alink-shared-key-1"
|
||||
const cpaModel = "glm-4.5-air"
|
||||
const minKnowledgeRunes = 80
|
||||
|
||||
type knowledgeData struct {
|
||||
Content string `json:"content"`
|
||||
@@ -59,15 +61,24 @@ func getRandomKnowledgeCard(keyword string) string {
|
||||
if knowledgeDB == nil {
|
||||
return ""
|
||||
}
|
||||
var content string
|
||||
err := knowledgeDB.QueryRow(
|
||||
"SELECT content FROM knowledge_cards WHERE keyword = ? ORDER BY RANDOM() LIMIT 1",
|
||||
rows, err := knowledgeDB.Query(
|
||||
"SELECT content FROM knowledge_cards WHERE keyword = ? ORDER BY RANDOM() LIMIT 20",
|
||||
keyword,
|
||||
).Scan(&content)
|
||||
)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return content
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var content string
|
||||
if rows.Scan(&content) == nil {
|
||||
content = normalizeKnowledgeContent(content)
|
||||
if isQualityKnowledgeCard(content) {
|
||||
return content
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getKnowledgeCardCount(keyword string) int {
|
||||
@@ -86,21 +97,60 @@ func getKnowledgeCardCount(keyword string) int {
|
||||
}
|
||||
|
||||
func fetchKnowledgeFromLLM(keyword string, cfg *Config) string {
|
||||
basePrompt := fmt.Sprintf(
|
||||
"根据关键词「%s」,生成一条有趣的知识小卡片。要求:控制在80字以内,简洁有趣,有知识性。直接输出内容,不要加标题、序号或其他格式。",
|
||||
keyword,
|
||||
)
|
||||
if cfg.KnowledgePrompt != "" {
|
||||
basePrompt += "\n附加要求:" + cfg.KnowledgePrompt
|
||||
basePrompt := buildKnowledgePrompt(keyword, cfg.KnowledgePrompt)
|
||||
messages := []map[string]string{
|
||||
{
|
||||
"role": "system",
|
||||
"content": "你是严谨的中文知识卡片作者,输出必须具体、准确、有信息密度。不要写空泛鸡汤,不要只给一句定义。",
|
||||
},
|
||||
{"role": "user", "content": basePrompt},
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"model": cpaModel,
|
||||
"max_tokens": 256,
|
||||
"messages": []map[string]string{
|
||||
{"role": "user", "content": basePrompt},
|
||||
},
|
||||
"model": cpaModel,
|
||||
"max_tokens": 512,
|
||||
"temperature": 0.55,
|
||||
"messages": messages,
|
||||
}
|
||||
content := requestKnowledgeCompletion(body)
|
||||
content = normalizeKnowledgeContent(content)
|
||||
if isQualityKnowledgeCard(content) {
|
||||
return content
|
||||
}
|
||||
|
||||
log.Printf("知识卡片质量不足,重试: %q", content)
|
||||
messages = append(messages, map[string]string{
|
||||
"role": "user",
|
||||
"content": fmt.Sprintf(
|
||||
"上一条太短或信息密度不足。请重写一条「%s」知识卡片:120-180个中文字符,必须包含一个明确机制/原理、一个具体例子或应用场景、一个结论。只输出正文。",
|
||||
keyword,
|
||||
),
|
||||
})
|
||||
body["messages"] = messages
|
||||
content = requestKnowledgeCompletion(body)
|
||||
content = normalizeKnowledgeContent(content)
|
||||
if isQualityKnowledgeCard(content) {
|
||||
return content
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func buildKnowledgePrompt(keyword, customPrompt string) string {
|
||||
basePrompt := fmt.Sprintf(`围绕关键词「%s」生成一条桌面知识卡片。
|
||||
硬性要求:
|
||||
1. 120-180个中文字符,分成2-3句;
|
||||
2. 必须讲清一个具体机制、原理、权衡或实践经验;
|
||||
3. 必须包含一个具体例子、场景或判断标准;
|
||||
4. 避免“很重要、非常有用、提升效率”这类空泛表述;
|
||||
5. 不要标题、序号、Markdown、表情,直接输出正文。`, keyword)
|
||||
if customPrompt != "" {
|
||||
basePrompt += "\n附加风格要求(不能覆盖上面的字数和质量要求):" + customPrompt
|
||||
}
|
||||
return basePrompt
|
||||
}
|
||||
|
||||
func requestKnowledgeCompletion(body map[string]interface{}) string {
|
||||
jsonData, _ := json.Marshal(body)
|
||||
|
||||
req, err := http.NewRequest("POST", cpaURL, bytes.NewReader(jsonData))
|
||||
@@ -108,7 +158,12 @@ func fetchKnowledgeFromLLM(keyword string, cfg *Config) string {
|
||||
log.Println("知识API请求创建失败:", err)
|
||||
return ""
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+cpaKey)
|
||||
key := loadConfig().cpaKey()
|
||||
if key == "" {
|
||||
log.Println("未配置知识卡片 API Key")
|
||||
return ""
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+key)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
@@ -135,6 +190,41 @@ func fetchKnowledgeFromLLM(keyword string, cfg *Config) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// normalizeKnowledgeContent 清洗 LLM 输出的格式残留(Markdown标记、标题前缀、多余空白)
|
||||
func normalizeKnowledgeContent(content string) string {
|
||||
content = strings.TrimSpace(content)
|
||||
content = strings.Trim(content, "` \t\r\n")
|
||||
content = strings.TrimPrefix(content, "知识卡片:")
|
||||
content = strings.TrimPrefix(content, "知识卡片:")
|
||||
content = strings.TrimPrefix(content, "正文:")
|
||||
content = strings.TrimPrefix(content, "正文:")
|
||||
content = strings.ReplaceAll(content, "\r\n", "\n")
|
||||
lines := strings.FieldsFunc(content, func(r rune) bool {
|
||||
return r == '\n' || r == '\r' || r == '\t'
|
||||
})
|
||||
content = strings.Join(lines, " ")
|
||||
content = strings.Join(strings.Fields(content), " ")
|
||||
return strings.TrimSpace(content)
|
||||
}
|
||||
|
||||
// isQualityKnowledgeCard 检查字数 ≥80 且空泛表述 <3 条
|
||||
func isQualityKnowledgeCard(content string) bool {
|
||||
if utf8.RuneCountInString(content) < minKnowledgeRunes {
|
||||
return false
|
||||
}
|
||||
weakPhrases := []string{"很重要", "非常重要", "很有用", "提升效率", "值得关注", "可以帮助", "广泛应用"}
|
||||
weakHits := 0
|
||||
for _, phrase := range weakPhrases {
|
||||
if strings.Contains(content, phrase) {
|
||||
weakHits++
|
||||
}
|
||||
}
|
||||
if weakHits >= 3 {
|
||||
return false
|
||||
}
|
||||
return strings.ContainsAny(content, "。;;::,,")
|
||||
}
|
||||
|
||||
func pushKnowledgeJSON(content, keyword string) {
|
||||
data, _ := json.Marshal(knowledgeData{Content: content, Keyword: keyword})
|
||||
evalJS(fmt.Sprintf(`if(window.updateKnowledgeFromGo) window.updateKnowledgeFromGo(%s)`, string(data)))
|
||||
|
||||
Reference in New Issue
Block a user