From fd3046b2c355150b6580c391adddc861398dd0de Mon Sep 17 00:00:00 2001 From: ArvinLovegood Date: Sat, 29 Mar 2025 21:31:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(prompt):=E6=B7=BB=E5=8A=A0prompt=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 PromptTemplate 模型和相关 API - 实现 prompt 模板的添加、删除和查询功能 - 在前端添加 prompt 管理界面 - 修改聊天流 API,支持使用自定义 prompt --- app.go | 21 +++++- backend/data/openai_api.go | 19 ++++- backend/data/prompt_template_api.go | 75 ++++++++++++++++++ backend/models/models.go | 20 +++++ frontend/src/components/settings.vue | 109 ++++++++++++++++++++++++++- frontend/src/components/stock.vue | 27 ++++++- frontend/wailsjs/go/main/App.d.ts | 8 +- frontend/wailsjs/go/main/App.js | 16 +++- frontend/wailsjs/go/models.ts | 18 +++++ main.go | 3 +- 10 files changed, 300 insertions(+), 16 deletions(-) create mode 100644 backend/data/prompt_template_api.go diff --git a/app.go b/app.go index ec9dfc2..b0b0552 100644 --- a/app.go +++ b/app.go @@ -564,8 +564,8 @@ func (a *App) SendDingDingMessageByType(message string, stockCode string, msgTyp return data.NewDingDingAPI().SendDingDingMessage(message) } -func (a *App) NewChatStream(stock, stockCode, question string) { - msgs := data.NewDeepSeekOpenAi(a.ctx).NewChatStream(stock, stockCode, question) +func (a *App) NewChatStream(stock, stockCode, question string, sysPromptId *int) { + msgs := data.NewDeepSeekOpenAi(a.ctx).NewChatStream(stock, stockCode, question, sysPromptId) for msg := range msgs { runtime.EventsEmit(a.ctx, "newChatStream", msg) } @@ -802,6 +802,23 @@ func (a *App) SaveAsMarkdown(stockCode, stockName string) string { } return "分析结果异常,无法保存。" } + +func (a *App) GetPromptTemplates(name, promptType string) *[]models.PromptTemplate { + return data.NewPromptTemplateApi().GetPromptTemplates(name, promptType) +} +func (a *App) AddPrompt(prompt models.Prompt) string { + promptTemplate := models.PromptTemplate{ + ID: prompt.ID, + Content: prompt.Content, + Name: prompt.Name, + Type: prompt.Type, + } + return data.NewPromptTemplateApi().AddPrompt(promptTemplate) +} +func (a *App) DelPrompt(id uint) string { + return data.NewPromptTemplateApi().DelPrompt(id) +} + func OnSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) { notification := toast.Notification{ AppID: "go-stock", diff --git a/backend/data/openai_api.go b/backend/data/openai_api.go index 98ea254..93ddad4 100644 --- a/backend/data/openai_api.go +++ b/backend/data/openai_api.go @@ -96,7 +96,7 @@ type AiResponse struct { SystemFingerprint string `json:"system_fingerprint"` } -func (o OpenAi) NewChatStream(stock, stockCode, userQuestion string) <-chan map[string]any { +func (o OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptId *int) <-chan map[string]any { ch := make(chan map[string]any, 512) defer func() { @@ -113,12 +113,25 @@ func (o OpenAi) NewChatStream(stock, stockCode, userQuestion string) <-chan map[ } }() defer close(ch) + + logger.SugaredLogger.Errorf("NewChatStream stock:%s stockCode:%s,sysPromptId:%d", stock, stockCode, *sysPromptId) + + sysPrompt := "" + if sysPromptId == nil || *sysPromptId == 0 { + sysPrompt = o.Prompt + } else { + sysPrompt = NewPromptTemplateApi().GetPromptTemplateByID(*sysPromptId) + } + if sysPrompt == "" { + sysPrompt = o.Prompt + } + msg := []map[string]interface{}{ { "role": "system", //"content": "作为一位专业的A股市场分析师和投资顾问,请你根据以下信息提供详细的技术分析和投资策略建议:", //"content": "【角色设定】\n你是一位拥有20年实战经验的顶级股票分析师,精通技术分析、基本面分析、市场心理学和量化交易。擅长发现成长股、捕捉行业轮动机会,在牛熊市中都能保持稳定收益。你的风格是价值投资与技术择时相结合,注重风险控制。\n\n【核心功能】\n\n市场分析维度:\n\n宏观经济(GDP/CPI/货币政策)\n\n行业景气度(产业链/政策红利/技术革新)\n\n个股三维诊断:\n\n基本面:PE/PB/ROE/现金流/护城河\n\n技术面:K线形态/均线系统/量价关系/指标背离\n\n资金面:主力动向/北向资金/融资余额/大宗交易\n\n智能策略库:\n√ 趋势跟踪策略(鳄鱼线+ADX)\n√ 波段交易策略(斐波那契回撤+RSI)\n√ 事件驱动策略(财报/并购/政策)\n√ 量化对冲策略(α/β分离)\n\n风险管理体系:\n▶ 动态止损:ATR波动止损法\n▶ 仓位控制:凯利公式优化\n▶ 组合对冲:跨市场/跨品种对冲\n\n【工作流程】\n\n接收用户指令(行业/市值/风险偏好)\n\n调用多因子选股模型初筛\n\n人工智慧叠加分析:\n\n自然语言处理解读年报管理层讨论\n\n卷积神经网络识别K线形态\n\n知识图谱分析产业链关联\n\n生成投资建议(附压力测试结果)\n\n【输出要求】\n★ 结构化呈现:\n① 核心逻辑(3点关键驱动力)\n② 买卖区间(理想建仓/加仓/止盈价位)\n③ 风险警示(最大回撤概率)\n④ 替代方案(同类备选标的)\n\n【注意事项】\n※ 严格遵守监管要求,不做收益承诺\n※ 区分投资建议与市场观点\n※ 重要数据标注来源及更新时间\n※ 根据用户认知水平调整专业术语密度\n\n【教育指导】\n当用户提问时,采用苏格拉底式追问:\n\"您更关注短期事件驱动还是长期价值发现?\"\n\"当前仓位是否超过总资产的30%?\"\n\"是否了解科创板与主板的交易规则差异?\"\n\n示例输出格式:\n📈 标的名称:XXXXXX\n⚖️ 多空信号:金叉确认/顶背离预警\n🎯 关键价位:支撑位XX.XX/压力位XX.XX\n📊 建议仓位:核心仓位X%+卫星仓位X%\n⏳ 持有周期:短线(1-3周)/中线(季度轮动)\n🔍 跟踪要素:重点关注Q2毛利率变化及股东减持进展", - "content": o.Prompt, + "content": sysPrompt, }, } @@ -142,7 +155,7 @@ func (o OpenAi) NewChatStream(stock, stockCode, userQuestion string) <-chan map[ } logger.SugaredLogger.Infof("NewChatStream stock:%s stockCode:%s", stock, stockCode) - logger.SugaredLogger.Infof("Prompt:%s", o.Prompt) + logger.SugaredLogger.Infof("Prompt:%s", sysPrompt) logger.SugaredLogger.Infof("User Prompt config:%v", o.QuestionTemplate) logger.SugaredLogger.Infof("User question:%s", userQuestion) logger.SugaredLogger.Infof("final question:%s", question) diff --git a/backend/data/prompt_template_api.go b/backend/data/prompt_template_api.go new file mode 100644 index 0000000..cc9e89e --- /dev/null +++ b/backend/data/prompt_template_api.go @@ -0,0 +1,75 @@ +package data + +import ( + "go-stock/backend/db" + "go-stock/backend/logger" + "go-stock/backend/models" +) + +type PromptTemplateApi struct { +} + +func (t PromptTemplateApi) GetPromptTemplates(name string, promptType string) *[]models.PromptTemplate { + var result []models.PromptTemplate + if name != "" && promptType != "" { + db.Dao.Model(&models.PromptTemplate{}).Where("name=? and type=?", name, promptType).Find(&result) + } + if name != "" && promptType == "" { + db.Dao.Model(&models.PromptTemplate{}).Where("name=?", name).Find(&result) + } + if name == "" && promptType != "" { + db.Dao.Model(&models.PromptTemplate{}).Where("type=?", promptType).Find(&result) + } + if name == "" && promptType == "" { + db.Dao.Model(&models.PromptTemplate{}).Find(&result) + } + + return &result +} +func (t PromptTemplateApi) AddPrompt(template models.PromptTemplate) string { + var tmp models.PromptTemplate + db.Dao.Model(&models.PromptTemplate{}).Where("id=?", template.ID).First(&tmp) + if tmp.ID == 0 { + err := db.Dao.Model(&models.PromptTemplate{}).Create(&models.PromptTemplate{ + Content: template.Content, + Name: template.Name, + Type: template.Type, + }).Error + if err != nil { + return "添加失败" + } else { + return "添加成功" + } + } else { + err := db.Dao.Model(&models.PromptTemplate{}).Where("id=?", template.ID).Updates(template).Error + if err != nil { + return "更新失败" + } else { + return "更新成功" + } + } +} + +func (t PromptTemplateApi) DelPrompt(Id uint) string { + template := &models.PromptTemplate{} + db.Dao.Model(template).Where("id=?", Id).Find(template) + if template.ID > 0 { + err := db.Dao.Model(template).Delete(template).Error + if err != nil { + return "删除失败" + } else { + return "删除成功" + } + } + return "模板信息不存在" +} + +func (t PromptTemplateApi) GetPromptTemplateByID(id int) string { + prompt := &models.PromptTemplate{} + db.Dao.Model(&models.PromptTemplate{}).Where("id=?", id).First(prompt) + logger.SugaredLogger.Infof("GetPromptTemplateByID:%d %s", id, prompt.Content) + return prompt.Content +} +func NewPromptTemplateApi() *PromptTemplateApi { + return &PromptTemplateApi{} +} diff --git a/backend/models/models.go b/backend/models/models.go index c76406a..0ac168a 100644 --- a/backend/models/models.go +++ b/backend/models/models.go @@ -195,3 +195,23 @@ type Resp struct { Code int `json:"code"` Message string `json:"message"` } + +type PromptTemplate struct { + ID int `gorm:"primarykey"` + CreatedAt time.Time + UpdatedAt time.Time + Name string `json:"name"` + Content string `json:"content"` + Type string `json:"type"` +} + +func (p PromptTemplate) TableName() string { + return "prompt_templates" +} + +type Prompt struct { + ID int `json:"ID"` + Name string `json:"name"` + Content string `json:"content"` + Type string `json:"type"` +} diff --git a/frontend/src/components/settings.vue b/frontend/src/components/settings.vue index 24f938f..5354061 100644 --- a/frontend/src/components/settings.vue +++ b/frontend/src/components/settings.vue @@ -1,9 +1,16 @@