Compare commits

...

2 Commits

Author SHA1 Message Date
ArvinLovegood
3c5205738f feat(data):添加投资者互动问答数据
- 在 app.go 中添加了 InteractiveAnswer 工具函数
- 在 market_news_api.go 中实现了 InteractiveAnswer 方法
- 在 market_news_api_test.go 中添加了 InteractiveAnswer 测试用例
- 在 models.go 中定义了 InteractiveAnswer 相关的结构体
- 在 openai_api.go 中集成了 InteractiveAnswer 功能
2025-07-31 18:48:28 +08:00
ArvinLovegood
b1b34d950b feat(notify):新增只提醒红字或关注个股新闻的设置
- 在 App 中实现只推送红字或关注个股新闻的逻辑
- 在前端添加 enableOnlyPushRedNews 配置项
- 更新后端设置 API 以支持新功能
2025-07-28 18:22:37 +08:00
5 changed files with 197 additions and 4 deletions

30
app.go
View File

@@ -104,6 +104,32 @@ func AddTools(tools []data.Tool) []data.Tool {
},
})
tools = append(tools, data.Tool{
Type: "function",
Function: data.ToolFunction{
Name: "InteractiveAnswer",
Description: "获取投资者与上市公司互动问答的数据,反映当前投资者关注的热点问题",
Parameters: data.FunctionParameters{
Type: "object",
Properties: map[string]any{
"page": map[string]any{
"type": "string",
"description": "分页号",
},
"pageSize": map[string]any{
"type": "string",
"description": "分页大小",
},
"keyWord": map[string]any{
"type": "string",
"description": "搜索关键词,多个关键词空格隔开(可输入股票名称或者当前热门板块/行业/概念/标的/事件等)",
},
},
Required: []string{"page", "pageSize"},
},
},
})
return tools
}
@@ -256,7 +282,7 @@ func (a *App) CheckUpdate(flag int) {
}
go runtime.EventsEmit(a.ctx, "newsPush", map[string]any{
"time": "发现新版本:" + releaseVersion.TagName,
"isRed": false,
"isRed": true,
"source": "go-stock",
"content": fmt.Sprintf("%s", commit.Message),
})
@@ -299,7 +325,7 @@ func (a *App) CheckUpdate(flag int) {
if flag == 1 {
go runtime.EventsEmit(a.ctx, "newsPush", map[string]any{
"time": "当前版本:" + Version,
"isRed": false,
"isRed": true,
"source": "go-stock",
"content": "当前版本无更新",
})

View File

@@ -886,3 +886,35 @@ func (m MarketNewsApi) ReutersNew() *models.ReutersNews {
logger.SugaredLogger.Infof("Articles:%+v", news.Result.Articles)
return news
}
func (m MarketNewsApi) InteractiveAnswer(page int, pageSize int, keyWord string) *models.InteractiveAnswer {
client := resty.New()
config := GetSettingConfig()
if config.HttpProxyEnabled && config.HttpProxy != "" {
client.SetProxy(config.HttpProxy)
}
url := fmt.Sprintf("https://irm.cninfo.com.cn/newircs/index/search?_t=%d", time.Now().Unix())
answers := &models.InteractiveAnswer{}
logger.SugaredLogger.Infof("请求url:%s", url)
resp, err := client.SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Host", "irm.cninfo.com.cn").
SetHeader("Origin", "https://irm.cninfo.com.cn").
SetHeader("Referer", "https://irm.cninfo.com.cn/views/interactiveAnswer").
SetHeader("handleError", "true").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/142.0").
SetFormData(map[string]string{
"pageNo": convertor.ToString(page),
"pageSize": convertor.ToString(pageSize),
"searchTypes": "1,11",
"highLight": "true",
"keyWord": keyWord,
}).
SetResult(answers).
Post(url)
if err != nil {
logger.SugaredLogger.Errorf("InteractiveAnswer-err:%+v", err)
}
logger.SugaredLogger.Debugf("InteractiveAnswer-resp:%s", resp.Body())
return answers
}

View File

@@ -3,12 +3,14 @@ package data
import (
"encoding/json"
"github.com/coocood/freecache"
"github.com/duke-git/lancet/v2/convertor"
"github.com/tidwall/gjson"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/util"
"strings"
"testing"
"time"
)
// @Author spark
@@ -216,3 +218,20 @@ func TestReutersNew(t *testing.T) {
db.Init("../../data/stock.db")
NewMarketNewsApi().ReutersNew()
}
func TestInteractiveAnswer(t *testing.T) {
db.Init("../../data/stock.db")
datas := NewMarketNewsApi().InteractiveAnswer(1, 25, "")
logger.SugaredLogger.Debugf("PageSize:%d", datas.PageSize)
for _, res := range datas.Results {
toInt, err := convertor.ToInt(res.AttachedPubDate)
if err != nil {
continue
}
logger.SugaredLogger.Debugf("%s companyShortName:%s[%s] \n MainContent:%s \n AttachedContent:%s", time.UnixMilli(toInt).Format("2006-01-02 15:04:05"), res.CompanyShortName, res.StockCode, res.MainContent, res.AttachedContent)
}
md := util.MarkdownTableWithTitle("投资互动", datas.Results)
logger.SugaredLogger.Debugf(md)
}

View File

@@ -188,7 +188,21 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
"content": "当前本地时间是:" + time.Now().Format("2006-01-02 15:04:05"),
})
wg := &sync.WaitGroup{}
wg.Add(5)
wg.Add(6)
go func() {
defer wg.Done()
datas := NewMarketNewsApi().InteractiveAnswer(1, 100, "")
content := util.MarkdownTableWithTitle("当前最新投资者互动数据", datas.Results)
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "投资者互动数据",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": content,
})
}()
go func() {
defer wg.Done()
@@ -381,7 +395,7 @@ func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int
"content": "当前本地时间是:" + time.Now().Format("2006-01-02 15:04:05"),
})
wg := &sync.WaitGroup{}
wg.Add(3)
wg.Add(4)
go func() {
defer wg.Done()
var market strings.Builder
@@ -442,6 +456,20 @@ func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int
})
}()
go func() {
defer wg.Done()
datas := NewMarketNewsApi().InteractiveAnswer(1, 100, "")
content := util.MarkdownTableWithTitle("当前最新投资者互动数据", datas.Results)
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "投资者互动数据",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": content,
})
}()
wg.Wait()
news := NewMarketNewsApi().GetNewsList("", 100)
@@ -1292,6 +1320,52 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
}
}
if funcName == "InteractiveAnswer" {
page := gjson.Get(funcArguments, "page").String()
pageSize := gjson.Get(funcArguments, "pageSize").String()
keyWord := gjson.Get(funcArguments, "keyWord").String()
ch <- map[string]any{
"code": 1,
"question": question,
"chatId": streamResponse.Id,
"model": streamResponse.Model,
"content": "\r\n```\r\n开始调用工具InteractiveAnswer\n参数" + page + "," + pageSize + "," + keyWord + "\r\n```\r\n",
"time": time.Now().Format(time.DateTime),
}
pageNo, err := convertor.ToInt(page)
if err != nil {
pageNo = 1
}
pageSizeNum, err := convertor.ToInt(pageSize)
if err != nil {
pageSizeNum = 50
}
datas := NewMarketNewsApi().InteractiveAnswer(int(pageNo), int(pageSizeNum), keyWord)
content := util.MarkdownTableWithTitle("投资互动数据", datas.Results)
logger.SugaredLogger.Infof("InteractiveAnswer=\n%s", content)
messages = append(messages, map[string]interface{}{
"role": "assistant",
"content": currentAIContent.String(),
"tool_calls": []map[string]any{
{
"id": currentCallId,
"tool_call_id": currentCallId,
"type": "function",
"function": map[string]string{
"name": funcName,
"arguments": funcArguments,
"parameters": funcArguments,
},
},
},
})
messages = append(messages, map[string]interface{}{
"role": "tool",
"content": content,
"tool_call_id": currentCallId,
})
}
}
AskAiWithTools(o, err, messages, ch, question, tools)
}

View File

@@ -635,3 +635,45 @@ type ReutersNews struct {
} `json:"result"`
Id string `json:"_id"`
}
type InteractiveAnswer struct {
PageNo int `json:"pageNo"`
PageSize int `json:"pageSize"`
TotalRecord int `json:"totalRecord"`
TotalPage int `json:"totalPage"`
Results []InteractiveAnswerResults `json:"results"`
Count bool `json:"count"`
}
type InteractiveAnswerResults struct {
EsId string `json:"esId" md:"-"`
IndexId string `json:"indexId" md:"-"`
ContentType int `json:"contentType" md:"-"`
Trade []string `json:"trade" md:"行业名称"`
MainContent string `json:"mainContent" md:"投资者提问"`
StockCode string `json:"stockCode" md:"股票代码"`
Secid string `json:"secid" md:"-"`
CompanyShortName string `json:"companyShortName" md:"股票名称"`
CompanyLogo string `json:"companyLogo,omitempty" md:"-"`
BoardType []string `json:"boardType" md:"-"`
PubDate string `json:"pubDate" md:"发布时间"`
UpdateDate string `json:"updateDate" md:"-"`
Author string `json:"author" md:"-"`
AuthorName string `json:"authorName" md:"-"`
PubClient string `json:"pubClient" md:"-"`
AttachedId string `json:"attachedId" md:"-"`
AttachedContent string `json:"attachedContent" md:"上市公司回复"`
AttachedAuthor string `json:"attachedAuthor" md:"-"`
AttachedPubDate string `json:"attachedPubDate" md:"回复时间"`
Score float64 `json:"score" md:"-"`
TopStatus int `json:"topStatus" md:"-"`
PraiseCount int `json:"praiseCount" md:"-"`
PraiseStatus bool `json:"praiseStatus" md:"-"`
FavoriteStatus bool `json:"favoriteStatus" md:"-"`
AttentionCompany bool `json:"attentionCompany" md:"-"`
IsCheck string `json:"isCheck" md:"-"`
QaStatus int `json:"qaStatus" md:"-"`
PackageDate string `json:"packageDate" md:"-"`
RemindStatus bool `json:"remindStatus" md:"-"`
InterviewLive bool `json:"interviewLive" md:"-"`
}