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 @@
@@ -304,6 +359,9 @@ window.onerror = function (event, source, lineno, colno, error) {
+
+ 添加提示词模板
+
保存
@@ -316,7 +374,52 @@ window.onerror = function (event, source, lineno, colno, error) {
+
+
+ {{prompt.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 保存
+
+
+ 取消
+
+
+
+
+