Compare commits

..

11 Commits

Author SHA1 Message Date
ArvinLovegood
087b953ed8 refactor(backend):优化投资互动数据处理
- 修改搜索关键词描述,移除多余信息
- 更新搜索类型参数,仅使用代码11
- 优化结构体转换为 Markdown 的处理逻辑
2025-08-01 17:31:57 +08:00
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
ArvinLovegood
83aa4331ad feat(notify):新增只提醒红字或关注个股新闻的设置
- 在 App 中实现只推送红字或关注个股新闻的逻辑
- 在前端添加 enableOnlyPushRedNews 配置项
- 更新后端设置 API 以支持新功能
2025-07-28 18:02:33 +08:00
ArvinLovegood
d4d3c44cf4 refactor(data):优化市场行情信息获取和展示
- 调整市场指数行情的展示格式和内容,增加更多指数信息
- 修改财联社电报的新闻列表获取参数,增加随机性
- 更新测试用例,增加对新功能的测试
2025-07-25 16:52:12 +08:00
ArvinLovegood
81a9cc5927 style(frontend):禁止界面拖拽
- 在多个组件中将 --wails-draggable 属性从 drag 改为 no-drag
- 这包括 about、App、market、settings 和 stock 组件
2025-07-24 17:17:50 +08:00
ArvinLovegood
3fc89a85da feat(ai):AI分析默认使用配置的第一个AI
- 在 market.vue 和 stock.vue 组件中,获取 AI 配置后设置了第一个配置的 ID
- 这个改动确保了在组件初始化时有一个默认的 AI 配置被选中
2025-07-23 12:35:05 +08:00
ArvinLovegood
0605c8442d feat(backend):添加Reuters新闻接口并整合到OpenAI消息中
- 新增 ReutersNew 方法获取 Reuters 新闻数据
- 创建 ReutersNews 模型用于解析新闻响应
- 在 OpenAI消息中添加 Reuters 新闻资讯
- 优化 MarketNewsApi 和 OpenAI 相关代码结构
2025-07-22 16:51:25 +08:00
ArvinLovegood
cf8591c208 refactor(data):调整ReutersAPI请求超时时间并集成TradingView 新闻
- 将 Reuters API 请求超时时间从30 秒调整为 5 秒
- 在 OpenAI API 中添加 TradingView 新闻获取功能
- 优化新闻文本处理和日志输出
2025-07-22 16:38:38 +08:00
ArvinLovegood
7607c4356f refactor(data):调整ReutersAPI请求超时时间并集成TradingView 新闻
- 将 Reuters API 请求超时时间从30 秒调整为 5 秒
- 在 OpenAI API 中添加 TradingView 新闻获取功能
- 优化新闻文本处理和日志输出
2025-07-22 16:24:38 +08:00
ArvinLovegood
4aae2ece00 feat(proxy):添加http代理支持来获取外媒新闻功能
- 在设置中添加 http 代理相关配置
- 优化 TradingView 新闻获取逻辑
- 添加 Reuters 新闻获取功能
- 调整行业报告信息获取方法
- 更新前端设置组件以支持 http 代理配置
2025-07-22 15:56:06 +08:00
15 changed files with 559 additions and 38 deletions

51
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),
})
@@ -272,7 +298,7 @@ func (a *App) CheckUpdate(flag int) {
}
body := resp.Body()
if len(body) < 1024 {
if len(body) < 1024*500 {
go runtime.EventsEmit(a.ctx, "newsPush", map[string]any{
"time": "新版本:" + releaseVersion.TagName,
"isRed": true,
@@ -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": "当前版本无更新",
})
@@ -438,7 +464,7 @@ func (a *App) domReady(ctx context.Context) {
//检查新版本
go func() {
a.CheckUpdate(0)
//a.CheckStockBaseInfo(a.ctx)
a.CheckStockBaseInfo(a.ctx)
a.cron.AddFunc("30 05 8,12,20 * * *", func() {
logger.SugaredLogger.Errorf("Checking for updates...")
a.CheckUpdate(0)
@@ -545,10 +571,21 @@ func (a *App) CheckStockBaseInfo(ctx context.Context) {
}
func (a *App) NewsPush(news *[]models.Telegraph) {
follows := data.NewStockDataApi().GetFollowList(0)
stockNames := slice.Map(*follows, func(index int, item data.FollowedStock) string {
return item.Name
})
for _, telegraph := range *news {
//if telegraph.IsRed {
go runtime.EventsEmit(a.ctx, "newsPush", telegraph)
go data.NewAlertWindowsApi("go-stock", telegraph.Source+" "+telegraph.Time, telegraph.Content, string(icon)).SendNotification()
if a.GetConfig().EnableOnlyPushRedNews {
if telegraph.IsRed || strutil.ContainsAny(telegraph.Content, stockNames) {
go runtime.EventsEmit(a.ctx, "newsPush", telegraph)
}
} else {
go runtime.EventsEmit(a.ctx, "newsPush", telegraph)
}
//go data.NewAlertWindowsApi("go-stock", telegraph.Source+" "+telegraph.Time, telegraph.Content, string(icon)).SendNotification()
//}
}
}

View File

@@ -551,9 +551,14 @@ func (m MarketNewsApi) EMDictCode(code string, cache *freecache.Cache) []any {
}
func (m MarketNewsApi) TradingViewNews() *[]models.TVNews {
client := resty.New()
config := GetSettingConfig()
if config.HttpProxyEnabled && config.HttpProxy != "" {
client.SetProxy(config.HttpProxy)
}
TVNews := &[]models.TVNews{}
url := "https://news-mediator.tradingview.com/news-flow/v2/news?filter=lang:zh-Hans&filter=provider:panews,reuters&client=screener&streaming=false"
resp, err := resty.New().SetProxy("http://127.0.0.1:10809").SetTimeout(time.Duration(30)*time.Second).R().
resp, err := client.SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Host", "news-mediator.tradingview.com").
SetHeader("Origin", "https://cn.tradingview.com").
SetHeader("Referer", "https://cn.tradingview.com/").
@@ -833,7 +838,7 @@ func (m MarketNewsApi) GetPMI() *models.PMIResp {
return res
}
func (m MarketNewsApi) GetIndustryReportInfo(infoCode string) {
func (m MarketNewsApi) GetIndustryReportInfo(infoCode string) string {
url := "https://data.eastmoney.com/report/zw_industry.jshtml?infocode=" + infoCode
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
SetHeader("Host", "data.eastmoney.com").
@@ -843,7 +848,7 @@ func (m MarketNewsApi) GetIndustryReportInfo(infoCode string) {
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("GetIndustryReportInfo err:%s", err.Error())
return
return ""
}
body := resp.Body()
//logger.SugaredLogger.Debugf("GetIndustryReportInfo:%s", body)
@@ -853,7 +858,63 @@ func (m MarketNewsApi) GetIndustryReportInfo(infoCode string) {
//logger.SugaredLogger.Infof("GetIndustryReportInfo:\n%s\n%s", title, content)
markdown, err := util.HTMLToMarkdown(title + content)
if err != nil {
return
return ""
}
logger.SugaredLogger.Infof("GetIndustryReportInfo markdown:\n%s", markdown)
return markdown
}
func (m MarketNewsApi) ReutersNew() *models.ReutersNews {
client := resty.New()
config := GetSettingConfig()
if config.HttpProxyEnabled && config.HttpProxy != "" {
client.SetProxy(config.HttpProxy)
}
news := &models.ReutersNews{}
url := "https://www.reuters.com/pf/api/v3/content/fetch/articles-by-section-alias-or-id-v1?query={\"arc-site\":\"reuters\",\"fetch_type\":\"collection\",\"offset\":0,\"section_id\":\"/world/\",\"size\":9,\"uri\":\"/world/\",\"website\":\"reuters\"}&d=300&mxId=00000000&_website=reuters"
_, err := client.SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Host", "www.reuters.com").
SetHeader("Origin", "https://www.reuters.com").
SetHeader("Referer", "https://www.reuters.com/world/china/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetResult(news).
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("ReutersNew err:%s", err.Error())
return news
}
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": "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

@@ -79,11 +79,13 @@ func TestStockResearchReport(t *testing.T) {
func TestIndustryResearchReport(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().IndustryResearchReport("", 7)
resp := NewMarketNewsApi().IndustryResearchReport("735", 7)
for _, a := range resp {
logger.SugaredLogger.Debugf("value: %+v", a)
data := a.(map[string]any)
logger.SugaredLogger.Debugf("value: %s infoCode:%s", data["title"], data["infoCode"])
NewMarketNewsApi().GetIndustryReportInfo(data["infoCode"].(string))
}
}
func TestStockNotice(t *testing.T) {
@@ -101,6 +103,11 @@ func TestEMDictCode(t *testing.T) {
for _, a := range resp {
logger.SugaredLogger.Debugf("value: %+v", a)
}
bytes, err := json.Marshal(resp)
if err != nil {
return
}
logger.SugaredLogger.Debugf("value: %s", string(bytes))
}
@@ -204,3 +211,17 @@ func TestGetPMI(t *testing.T) {
func TestGetIndustryReportInfo(t *testing.T) {
NewMarketNewsApi().GetIndustryReportInfo("AP202507151709216483")
}
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, 100, "")
logger.SugaredLogger.Debugf("PageSize:%d", datas.PageSize)
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(3)
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()
@@ -219,17 +233,24 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
go func() {
defer wg.Done()
var market strings.Builder
market.WriteString(getZSInfo("上证指数", "sh000001", 30) + "\n")
market.WriteString(getZSInfo("深证成指", "sz399001", 30) + "\n")
market.WriteString(getZSInfo("创业板指数", "sz399006", 30) + "\n")
market.WriteString(getZSInfo("上证综合指数", "sh000001", 30) + "\n")
market.WriteString(getZSInfo("科创50", "sh000688", 30) + "\n")
market.WriteString(getZSInfo("沪深300指数", "sh000300", 30) + "\n")
market.WriteString(getZSInfo("中证银行", "sz399986", 30) + "\n")
market.WriteString(getZSInfo("科创芯片", "sh000685", 30) + "\n")
market.WriteString(getZSInfo("上证医药", "sh000037", 30) + "\n")
market.WriteString(getZSInfo("证券龙头", "sz399437", 30) + "\n")
market.WriteString(getZSInfo("中证白酒", "sz399997", 30) + "\n")
//logger.SugaredLogger.Infof("NewChatStream getZSInfo=\n%s", market.String())
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "当前市场指数行情",
"content": "当前市场/大盘/行业/指数行情",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": "当前市场指数行情情况如下:\n" + market.String(),
"content": "当前市场/大盘/行业/指数行情如下:\n" + market.String(),
})
}()
@@ -264,9 +285,45 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
}()
go func() {
defer wg.Done()
resp := NewMarketNewsApi().TradingViewNews()
var newsText strings.Builder
for _, a := range *resp {
logger.SugaredLogger.Debugf("TradingViewNews: %s", a.Title)
newsText.WriteString(a.Title + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "全球新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": newsText.String(),
})
}()
go func() {
defer wg.Done()
news := NewMarketNewsApi().ReutersNew()
messageText := strings.Builder{}
for _, article := range news.Result.Articles {
messageText.WriteString("## " + article.Title + "\n")
messageText.WriteString("### " + article.Description + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "外媒全球新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": messageText.String(),
})
}()
wg.Wait()
news := NewMarketNewsApi().GetNewsList("财联社电报", random.RandInt(50, 150))
news := NewMarketNewsApi().GetNewsList("财联社电报", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
@@ -338,13 +395,20 @@ func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int
"content": "当前本地时间是:" + time.Now().Format("2006-01-02 15:04:05"),
})
wg := &sync.WaitGroup{}
wg.Add(1)
wg.Add(4)
go func() {
defer wg.Done()
var market strings.Builder
market.WriteString(getZSInfo("上证指数", "sh000001", 30) + "\n")
market.WriteString(getZSInfo("深证成指", "sz399001", 30) + "\n")
market.WriteString(getZSInfo("创业板指数", "sz399006", 30) + "\n")
market.WriteString(getZSInfo("上证综合指数", "sh000001", 30) + "\n")
market.WriteString(getZSInfo("科创50", "sh000688", 30) + "\n")
market.WriteString(getZSInfo("沪深300指数", "sh000300", 30) + "\n")
market.WriteString(getZSInfo("中证银行", "sz399986", 30) + "\n")
market.WriteString(getZSInfo("科创芯片", "sh000685", 30) + "\n")
market.WriteString(getZSInfo("上证医药", "sh000037", 30) + "\n")
market.WriteString(getZSInfo("证券龙头", "sz399437", 30) + "\n")
market.WriteString(getZSInfo("中证白酒", "sz399997", 30) + "\n")
//logger.SugaredLogger.Infof("NewChatStream getZSInfo=\n%s", market.String())
msg = append(msg, map[string]interface{}{
"role": "user",
@@ -355,6 +419,57 @@ func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int
"content": "当前市场指数行情情况如下:\n" + market.String(),
})
}()
go func() {
defer wg.Done()
resp := NewMarketNewsApi().TradingViewNews()
var newsText strings.Builder
for _, a := range *resp {
logger.SugaredLogger.Debugf("TradingViewNews: %s", a.Title)
newsText.WriteString(a.Title + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "外媒全球新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": newsText.String(),
})
}()
go func() {
defer wg.Done()
news := NewMarketNewsApi().ReutersNew()
messageText := strings.Builder{}
for _, article := range news.Result.Articles {
messageText.WriteString("## " + article.Title + "\n")
messageText.WriteString("### " + article.Description + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "外媒全球新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": messageText.String(),
})
}()
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)
@@ -468,9 +583,8 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
logger.SugaredLogger.Infof("NewChatStream stock:%s stockCode:%s", stock, stockCode)
logger.SugaredLogger.Infof("Prompt%s", sysPrompt)
logger.SugaredLogger.Infof("final question:%s", question)
wg := &sync.WaitGroup{}
wg.Add(7)
wg.Add(8)
go func() {
defer wg.Done()
@@ -714,6 +828,25 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
})
}()
go func() {
defer wg.Done()
resp := NewMarketNewsApi().TradingViewNews()
var newsText strings.Builder
for _, a := range *resp {
logger.SugaredLogger.Debugf("value: %s", a.Title)
newsText.WriteString(a.Title + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": "外媒全球新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": newsText.String(),
})
}()
wg.Wait()
msg = append(msg, map[string]interface{}{
"role": "user",
@@ -1187,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

@@ -57,3 +57,9 @@ func TestSearchGuShiTongStockInfo(t *testing.T) {
SearchGuShiTongStockInfo("gb_goog", 60)
}
func TestGetZSInfo(t *testing.T) {
db.Init("../../data/stock.db")
getZSInfo("中证银行", "sz399986", 30)
getZSInfo("科创50", "sh000688", 30)
}

View File

@@ -31,7 +31,10 @@ type Settings struct {
BrowserPoolSize int `json:"browserPoolSize"`
EnableFund bool `json:"enableFund"`
EnablePushNews bool `json:"enablePushNews"`
EnableOnlyPushRedNews bool `json:"enableOnlyPushRedNews"`
SponsorCode string `json:"sponsorCode"`
HttpProxy string `json:"httpProxy"`
HttpProxyEnabled bool `json:"httpProxyEnabled"`
}
func (receiver Settings) TableName() string {
@@ -98,7 +101,10 @@ func UpdateConfig(s *SettingConfig) string {
"dark_theme": s.DarkTheme,
"enable_fund": s.EnableFund,
"enable_push_news": s.EnablePushNews,
"enable_only_push_red_news": s.EnableOnlyPushRedNews,
"sponsor_code": s.SponsorCode,
"http_proxy": s.HttpProxy,
"http_proxy_enabled": s.HttpProxyEnabled,
})
//更新AiConfig

View File

@@ -11,7 +11,7 @@ import (
// -----------------------------------------------------------------------------------
func TestGetDaily(t *testing.T) {
db.Init("../../data/stock.db")
tushareApi := NewTushareApi(GetConfig())
tushareApi := NewTushareApi(GetSettingConfig())
res := tushareApi.GetDaily("00927.HK", "20250101", "20250217", 30)
t.Log(res)
@@ -19,7 +19,7 @@ func TestGetDaily(t *testing.T) {
func TestGetUSDaily(t *testing.T) {
db.Init("../../data/stock.db")
tushareApi := NewTushareApi(GetConfig())
tushareApi := NewTushareApi(GetSettingConfig())
res := tushareApi.GetDaily("gb_AAPL", "20250101", "20250217", 30)
t.Log(res)

View File

@@ -500,3 +500,180 @@ type OldSettings struct {
func (receiver OldSettings) TableName() string {
return "settings"
}
type ReutersNews struct {
StatusCode int `json:"statusCode"`
Message string `json:"message"`
Result struct {
ParentSectionName string `json:"parent_section_name"`
Pagination struct {
Size int `json:"size"`
ExpectedSize int `json:"expected_size"`
TotalSize int `json:"total_size"`
Orderby string `json:"orderby"`
} `json:"pagination"`
DateModified time.Time `json:"date_modified"`
FetchType string `json:"fetch_type"`
Articles []struct {
Id string `json:"id"`
CanonicalUrl string `json:"canonical_url"`
Website string `json:"website"`
Web string `json:"web"`
Native string `json:"native"`
UpdatedTime time.Time `json:"updated_time"`
PublishedTime time.Time `json:"published_time"`
ArticleType string `json:"article_type"`
DisplayMyNews bool `json:"display_my_news"`
DisplayNewsletterSignup bool `json:"display_newsletter_signup"`
DisplayNotifications bool `json:"display_notifications"`
DisplayRelatedMedia bool `json:"display_related_media"`
DisplayRelatedOrganizations bool `json:"display_related_organizations"`
ContentCode string `json:"content_code"`
Source struct {
Name string `json:"name"`
OriginalName string `json:"original_name"`
} `json:"source"`
Title string `json:"title"`
BasicHeadline string `json:"basic_headline"`
Distributor string `json:"distributor"`
Description string `json:"description"`
PrimaryMediaType string `json:"primary_media_type,omitempty"`
PrimaryTag struct {
ShortBio string `json:"short_bio"`
Description string `json:"description"`
Slug string `json:"slug"`
Text string `json:"text"`
TopicUrl string `json:"topic_url"`
CanFollow bool `json:"can_follow,omitempty"`
IsTopic bool `json:"is_topic,omitempty"`
} `json:"primary_tag"`
WordCount int `json:"word_count"`
ReadMinutes int `json:"read_minutes"`
Kicker struct {
Path string `json:"path"`
Names []string `json:"names"`
Name string `json:"name,omitempty"`
} `json:"kicker"`
AdTopics []string `json:"ad_topics"`
Thumbnail struct {
Url string `json:"url"`
Caption string `json:"caption,omitempty"`
Type string `json:"type"`
ResizerUrl string `json:"resizer_url"`
Location string `json:"location,omitempty"`
Id string `json:"id"`
Authors string `json:"authors,omitempty"`
AltText string `json:"alt_text"`
Width int `json:"width"`
Height int `json:"height"`
Subtitle string `json:"subtitle"`
Slug string `json:"slug,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
Company string `json:"company,omitempty"`
PurchaseLicensingPath string `json:"purchase_licensing_path,omitempty"`
} `json:"thumbnail"`
Authors []struct {
Id string `json:"id,omitempty"`
Name string `json:"name"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Company string `json:"company"`
Thumbnail struct {
Url string `json:"url"`
Type string `json:"type"`
ResizerUrl string `json:"resizer_url"`
} `json:"thumbnail"`
SocialLinks []struct {
Site string `json:"site"`
Url string `json:"url"`
} `json:"social_links,omitempty"`
Byline string `json:"byline"`
Description string `json:"description,omitempty"`
TopicUrl string `json:"topic_url,omitempty"`
Role string `json:"role,omitempty"`
} `json:"authors"`
DisplayTime time.Time `json:"display_time"`
ThumbnailDark struct {
Url string `json:"url"`
Type string `json:"type"`
ResizerUrl string `json:"resizer_url"`
Id string `json:"id"`
AltText string `json:"alt_text"`
Width int `json:"width"`
Height int `json:"height"`
Subtitle string `json:"subtitle"`
UpdatedAt time.Time `json:"updated_at"`
} `json:"thumbnail_dark,omitempty"`
} `json:"articles"`
Section struct {
Id string `json:"id"`
AdUnitCode string `json:"ad_unit_code"`
Website string `json:"website"`
Name string `json:"name"`
PageTitle string `json:"page_title"`
CanFollow bool `json:"can_follow"`
Language string `json:"language"`
Type string `json:"type"`
Advertising struct {
Sponsored string `json:"sponsored"`
} `json:"advertising"`
VideoPlaylistId string `json:"video_playlistId"`
MobileAdUnitPath string `json:"mobile_ad_unit_path"`
AdUnitPath string `json:"ad_unit_path"`
CollectionAlias string `json:"collection_alias"`
SectionAbout string `json:"section_about"`
Title string `json:"title"`
Personalization struct {
Id string `json:"id"`
Type string `json:"type"`
ShowTags bool `json:"show_tags"`
CanFollow bool `json:"can_follow"`
} `json:"personalization"`
} `json:"section"`
AdUnitPath string `json:"ad_unit_path"`
ResponseTime int64 `json:"response_time"`
} `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:"-"`
}

View File

@@ -2,6 +2,8 @@ package util
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/strutil"
"reflect"
"strings"
)
@@ -217,7 +219,7 @@ func formatValue(value reflect.Value) string {
}
// 基本类型
return fmt.Sprintf("%v", value.Interface())
return fmt.Sprintf("%s", strutil.RemoveNonPrintable(convertor.ToString(value.Interface())))
}
// 示例结构体

View File

@@ -702,7 +702,7 @@ onMounted(() => {
</n-spin>
</n-gi>
<n-gi style="position: fixed;bottom:0;z-index: 9;width: 100%;">
<n-card size="small" style="--wails-draggable:drag">
<n-card size="small" style="--wails-draggable:no-drag">
<n-menu style="font-size: 18px;"
v-model:value="activeKey"
mode="horizontal"

View File

@@ -104,7 +104,7 @@ EventsOn("updateVersion",async (msg) => {
</script>
<template>
<n-space vertical size="large" style="--wails-draggable:drag">
<n-space vertical size="large" style="--wails-draggable:no-drag">
<!-- 软件描述 -->
<n-card size="large">
<n-divider title-placement="center">关于软件</n-divider>

View File

@@ -102,6 +102,7 @@ onBeforeMount(() => {
GetAiConfigs().then(res=>{
aiConfigs.value = res
aiConfigId.value = res[0].ID
})
GetTelegraphList("财联社电报").then((res) => {
@@ -317,7 +318,7 @@ function ReFlesh(source) {
<template>
<n-card>
<n-tabs type="line" animated @update-value="updateTab" :value="nowTab" style="--wails-draggable:drag">
<n-tabs type="line" animated @update-value="updateTab" :value="nowTab" style="--wails-draggable:no-drag">
<n-tab-pane name="市场快讯" tab="市场快讯">
<n-grid :cols="2" :y-gap="0">
<n-gi>

View File

@@ -41,7 +41,10 @@ const formValue = ref({
darkTheme: true,
enableFund: false,
enablePushNews: false,
enableOnlyPushRedNews: false,
sponsorCode: "",
httpProxy:"",
httpProxyEnabled:false,
})
// 添加一个新的AI配置到列表
@@ -96,7 +99,11 @@ onMounted(() => {
formValue.value.darkTheme = res.darkTheme
formValue.value.enableFund = res.enableFund
formValue.value.enablePushNews = res.enablePushNews
formValue.value.enableOnlyPushRedNews = res.enableOnlyPushRedNews
formValue.value.sponsorCode = res.sponsorCode
formValue.value.httpProxy=res.httpProxy;
formValue.value.httpProxyEnabled=res.httpProxyEnabled;
})
GetPromptTemplates("", "").then(res => {
@@ -131,7 +138,10 @@ function saveConfig() {
darkTheme: formValue.value.darkTheme,
enableFund: formValue.value.enableFund,
enablePushNews: formValue.value.enablePushNews,
sponsorCode: formValue.value.sponsorCode
enableOnlyPushRedNews: formValue.value.enableOnlyPushRedNews,
sponsorCode: formValue.value.sponsorCode,
httpProxy:formValue.value.httpProxy,
httpProxyEnabled:formValue.value.httpProxyEnabled,
})
if (config.sponsorCode) {
@@ -217,7 +227,10 @@ function importConfig() {
formValue.value.darkTheme = config.darkTheme
formValue.value.enableFund = config.enableFund
formValue.value.enablePushNews = config.enablePushNews
formValue.value.enableOnlyPushRedNews = config.enableOnlyPushRedNews
formValue.value.sponsorCode = config.sponsorCode
formValue.value.httpProxy=config.httpProxy
formValue.value.httpProxyEnabled=config.httpProxyEnabled
};
reader.readAsText(file);
};
@@ -283,7 +296,7 @@ function deletePrompt(ID) {
</script>
<template>
<n-flex justify="left" style="text-align: left; --wails-draggable:drag">
<n-flex justify="left" style="text-align: left; --wails-draggable:no-drag">
<n-form ref="formRef" :label-placement="'left'" :label-align="'left'">
<n-space vertical size="large">
<n-card :title="() => h(NTag, { type: 'primary', bordered: false }, () => '基础设置')" size="small">
@@ -321,21 +334,25 @@ function deletePrompt(ID) {
<n-card :title="() => h(NTag, { type: 'primary', bordered: false }, () => '通知设置')" size="small">
<n-grid :cols="24" :x-gap="24" style="text-align: left">
<n-form-item-gi :span="4" label="钉钉推送:" path="dingPush.enable">
<n-form-item-gi :span="3" label="钉钉推送:" path="dingPush.enable">
<n-switch v-model:value="formValue.dingPush.enable"/>
</n-form-item-gi>
<n-form-item-gi :span="4" label="本地推送:" path="localPush.enable">
<n-form-item-gi :span="3" label="本地推送:" path="localPush.enable">
<n-switch v-model:value="formValue.localPush.enable"/>
</n-form-item-gi>
<n-form-item-gi :span="4" label="弹幕功能:" path="enableDanmu">
<n-form-item-gi :span="3" label="弹幕功能:" path="enableDanmu">
<n-switch v-model:value="formValue.enableDanmu"/>
</n-form-item-gi>
<n-form-item-gi :span="4" label="显示滚动快讯:" path="enableNews">
<n-form-item-gi :span="3" label="显示滚动快讯:" path="enableNews">
<n-switch v-model:value="formValue.enableNews"/>
</n-form-item-gi>
<n-form-item-gi :span="4" label="市场资讯提醒:" path="enablePushNews">
<n-form-item-gi :span="3" label="市场资讯提醒:" path="enablePushNews">
<n-switch v-model:value="formValue.enablePushNews"/>
</n-form-item-gi>
<n-form-item-gi v-if="formValue.enablePushNews" :span="4" label="只提醒红字或关注个股的新闻:" path="enableOnlyPushRedNews">
<n-switch v-model:value="formValue.enableOnlyPushRedNews"/>
</n-form-item-gi>
<n-form-item-gi :span="22" v-if="formValue.dingPush.enable" label="钉钉机器人接口地址:"
path="dingPush.dingRobot">
<n-input placeholder="请输入钉钉机器人接口地址" v-model:value="formValue.dingPush.dingRobot"/>
@@ -354,10 +371,18 @@ function deletePrompt(ID) {
title="资讯采集超时时间(秒)" path="openAI.crawlTimeOut">
<n-input-number min="30" step="1" v-model:value="formValue.openAI.crawlTimeOut"/>
</n-form-item-gi>
<n-form-item-gi :span="6" v-if="formValue.openAI.enable" title="天数越多消耗tokens越多"
<n-form-item-gi :span="4" v-if="formValue.openAI.enable" title="天数越多消耗tokens越多"
label="日K线数据(天)" path="openAI.kDays">
<n-input-number min="30" step="1" max="365" v-model:value="formValue.openAI.kDays"/>
</n-form-item-gi>
<n-form-item-gi :span="2" label="http代理" path="httpProxyEnabled">
<n-switch v-model:value="formValue.httpProxyEnabled"/>
</n-form-item-gi>
<n-form-item-gi :span="10" v-if="formValue.httpProxyEnabled" title="http代理地址"
label="http代理地址" path="httpProxy">
<n-input type="text" placeholder="http代理地址" v-model:value="formValue.httpProxy" clearable/>
</n-form-item-gi>
<n-gi :span="24" v-if="formValue.openAI.enable">
<n-divider title-placement="left">Prompt 内容设置</n-divider>
@@ -445,7 +470,6 @@ function deletePrompt(ID) {
</n-grid>
</n-card>
</n-space>
</n-form>
</n-flex>

View File

@@ -220,6 +220,7 @@ onBeforeMount(() => {
GetAiConfigs().then(res=>{
aiConfigs.value = res
data.aiConfigId =res[0].ID
})
})
@@ -1722,7 +1723,7 @@ function searchStockReport(stockCode) {
</n-gradient-text>
</template>
</vue-danmaku>
<n-tabs type="card" style="--wails-draggable:drag" animated addable :data-currentGroupId="currentGroupId"
<n-tabs type="card" style="--wails-draggable:no-drag" animated addable :data-currentGroupId="currentGroupId"
:value="currentGroupId" @add="addTab" @update-value="updateTab" placement="top" @close="(key)=>{delTab(key)}">
<n-tab-pane :name="0" :tab="'全部'">
<n-grid :x-gap="8" :cols="3" :y-gap="8">

View File

@@ -389,7 +389,10 @@ export namespace data {
browserPoolSize: number;
enableFund: boolean;
enablePushNews: boolean;
enableOnlyPushRedNews: boolean;
sponsorCode: string;
httpProxy: string;
httpProxyEnabled: boolean;
aiConfigs: AIConfig[];
static createFrom(source: any = {}) {
@@ -421,7 +424,10 @@ export namespace data {
this.browserPoolSize = source["browserPoolSize"];
this.enableFund = source["enableFund"];
this.enablePushNews = source["enablePushNews"];
this.enableOnlyPushRedNews = source["enableOnlyPushRedNews"];
this.sponsorCode = source["sponsorCode"];
this.httpProxy = source["httpProxy"];
this.httpProxyEnabled = source["httpProxyEnabled"];
this.aiConfigs = this.convertValues(source["aiConfigs"], AIConfig);
}