Compare commits

...

39 Commits

Author SHA1 Message Date
ArvinLovegood
1ae1bb0116 feat(vip):移除水印内容显示
- 更新窗口标题以包含授权声明
- 移除水印内容显示为空字符串
2025-10-31 18:37:26 +08:00
ArvinLovegood
d8971935ee feat(settings):添加AI智能体功能开关(建议关闭,使用体验不理想)
- 在设置界面新增AI智能体启用开关
- 支持保存和读取AI智能体启用状态
- 更新配置模型以支持新的启用选项
- 动态控制菜单项显示状态
- 同步前后端配置结构以支持新功能
2025-10-31 17:11:17 +08:00
ArvinLovegood
9c68458b81 feat(stockhotmap):添加财联社行情数据标签页
- 在 stockhotmap 组件中新增财联社-行情数据标签页
- 配置嵌入链接指向 https://www.cls.cn/quotation
- 设置高度为 calc(100vh - 252px) 以适配页面布局
2025-10-31 16:54:05 +08:00
ArvinLovegood
b367d1eb40 feat(stockhotmap):启用百度股市通和摸鱼网页标签页
- 取消注释百度股市通标签页,恢复其功能
- 取消注释摸鱼标签页,恢复其功能
-保持其他标签页配置不变
2025-10-29 16:18:29 +08:00
ArvinLovegood
8fe79adbb1 feat(stock):增加股票搜索结果数量并优化搜索条件
- 将随机返回的股票数量从5-10支增加到5-20支
- 更新测试用例中的搜索关键词,提高筛选条件准确性
- 调整搜索逻辑以适应新的数据范围和质量要求
2025-10-29 16:13:30 +08:00
ArvinLovegood
1d81fdba87 chore(deps):指标选股问题
- 将 User-Agent 中的 Firefox 版本从 140.0 更新为 145.0
2025-10-27 18:16:25 +08:00
ArvinLovegood
6aca0e15cc fix(backend): 指标选股问题
- 修改fingerprint和requestId为新的固定值
- 将timestamp设置为动态生成的时间戳
- 更新xcId为新的标识符
- 在测试中增加返回结果的日志输出
2025-10-27 18:15:02 +08:00
ArvinLovegood
173ce6f243 fix(backend):修复新闻列表排序逻辑
- 将新闻列表按ID降序排列,确保最新新闻优先显示
- 保持is_red字段的降序排序,确保重要新闻优先显示
- 修复了查询条件中缺少的排序字段逗号问题
2025-09-29 18:10:55 +08:00
ArvinLovegood
e7875e73d3 feat(backend):新增并使用GetNewsList2方法以支持标签功能
- 在MarketNewsApi中添加GetNewsList2方法,支持根据source和limit获取新闻列表
- GetNewsList2方法中预加载TelegraphTags并关联标签名称
- 修改openai_api.go中调用GetNewsList的地方为GetNewsList2- 调整获取新闻列表的参数,使用固定source和随机limit值
2025-09-29 17:34:31 +08:00
ArvinLovegood
ca4727db80 fix(app):更新股票研究报告工具描述
- 修改了GetStockResearchReport函数的描述信息
- 简化了描述文本,去除冗余的"机构的"前缀
- 保持了原有功能和参数结构不变
2025-09-27 19:12:51 +08:00
ArvinLovegood
84ffe7c5fd refactor(openai_api):移除行业板块相关工具调用逻辑
- 注释掉QueryBKDictInfo工具的调用实现
- 注释掉GetIndustryResearchReport工具的调用实现
- 移除对freecache包的依赖引用- 保留GetStockResearchReport工具的调用逻辑
- 简化工具调用处理流程
2025-09-27 18:55:28 +08:00
ArvinLovegood
da02d1bd1c feat(stock):更新股票研究报接口参数并完善行业研究描述
- 修改行业研究工具函数描述,增加调用前需查询行业代码的提示
- 更新股票研究报接口测试用例中的股票代码参数值
- 完善行业研究报相关功能的使用说明和参数校验逻辑- 优化研究报数据获取流程,提升接口稳定性与准确性
2025-09-27 16:59:05 +08:00
ArvinLovegood
bae2bf9c5c docs(readme): 更新AI智能选股功能描述
- 在功能说明中添加AI智能体功能的描述
- 保持其他功能状态和备注信息不变
2025-09-27 15:46:33 +08:00
ArvinLovegood
6568b5949a docs(readme): 更新AI智能选股功能状态
- 将AI智能选股功能状态从开发中更新为已完成
- 修改功能描述为"市场行情-》AI总结"
- 调整了功能备注信息的表述方式
2025-09-27 15:44:03 +08:00
ArvinLovegood
c4287f9b78 feat(ai):新增了机构/券商的研究报告AI工具函数
- 添加 QueryBKDictInfo 工具用于获取板块/行业字典信息
- 实现 GetIndustryResearchReport 工具用于获取行业研究报告
- 实现 GetStockResearchReport 工具用于获取股票研究报告
- 在数据库中新增 BKDict 模型并自动迁移
- 更新 MarketNewsApi 测试用例以支持新工具调用
- 在 OpenAI API 中集成新工具的调用逻辑与响应处理
2025-09-27 15:39:37 +08:00
ArvinLovegood
87441d8923 feat(ai):新增了机构/券商的研究报告AI工具函数
- 添加 QueryBKDictInfo 工具用于获取板块/行业字典信息
- 实现 GetIndustryResearchReport 工具用于获取行业研究报告
- 实现 GetStockResearchReport 工具用于获取股票研究报告
- 在数据库中新增 BKDict 模型并自动迁移
- 更新 MarketNewsApi 测试用例以支持新工具调用
- 在 OpenAI API 中集成新工具的调用逻辑与响应处理
2025-09-27 15:39:25 +08:00
ArvinLovegood
ebd166e72b fix(agent):调整代理最大步骤数并修复前端显示逻辑
- 减少代理工具调用的最大步骤数计算方式
- 启用并修复前端聊天组件中的推理内容显示
- 修复删除分组时传递正确参数类型
- 更新依赖项,移除旧版本的 chromedp 和 golang.org/x/sys
2025-09-27 10:59:45 +08:00
ArvinLovegood
494a60debe refactor(backend):注释掉获取股势通资讯的代码
- 在 openai_api.go 中注释掉了获取股势通资讯的相关代码
- 在 openai_api_test.go 中添加了对 SearchGuShiTongStockInfo 函数的测试用例
2025-09-16 15:14:38 +08:00
ArvinLovegood
b3e2565a02 build: 更新多个依赖至最新版本 2025-09-16 14:19:11 +08:00
ArvinLovegood
c0a87d5d2e perf(app):优化股票基础信息更新逻辑
- 移除了定时任务中重复的股票基础信息检查逻辑
- 在获取股票基础信息前增加数据库记录数量检查,避免重复更新
2025-08-26 18:28:10 +08:00
ArvinLovegood
d74ad3c03d refactor(frontend):升级go到1.25版本(性能更强劲!)
- 从 components.d.ts 文件中删除了未使用的 TChat、TChatAction、TChatContent、TChatLoading 和 TChatSender导入
- 更新 go.mod 文件,移除 toolchain go1.24.5 并将 Go 版本升级到 1.25.0
2025-08-26 11:45:03 +08:00
ArvinLovegood
6dff9d95c4 build:更新Go依赖并升级到Go1.25
- 更新 golang.org/x/sys从 v0.33.0 到 v0.35.0
- 更新 sonic、sonic/loader、base64x、cpuid、arch 等多个依赖库
- 将 Go 版本从 1.24升级到 1.25
2025-08-26 11:28:49 +08:00
ArvinLovegood
06967420f8 feat(app):添加股票基础信息自动检查功能
- 在应用启动时检查股票基础信息是否为空
- 如果为空,则自动调用 CheckStockBaseInfo 方法获取数据
-每天凌晨 2 点定时检查股票基础信息
2025-08-26 10:45:17 +08:00
ArvinLovegood
6f4eb0ac86 refactor(backend): 移除未使用的导入语句
- 删除了 "C" 的导入语句,该语句在代码中未被使用
- 优化代码结构,提高代码的可读性和维护性
2025-08-24 20:11:57 +08:00
ArvinLovegood
f59255cc6c style:调整 Windows特定代码的构建标签
- 在 alert_windows_api.go 和 alert_windows_api_test.go 文件中添加额外的 +build windows 标签
- 优化代码结构,提高代码的可读性和可维护性
2025-08-24 16:29:57 +08:00
ArvinLovegood
4f4fa46338 refactor(app):优化代码结构和功能
- 调整导入顺序,提高代码可读性
- 使用 cron 定时任务替代直接调用检查更新方法
- 在前端 App.vue 中添加名站优选市场选项
-调整主窗口高度
-优化股票情感分析数据处理
2025-08-24 15:46:55 +08:00
SparkMemory
05bf35fdf4 Merge pull request #99 from CodeNoobLH/dev
feat(frontend): 添加股票分组拖拽排序功能
2025-08-24 14:35:21 +08:00
浓睡不消残酒
567c81ae7c feat(frontend): 添加股票分组拖拽排序功能
- 在前端实现股票分组的拖拽排序功能
- 新增后端接口支持分组排序的更新
- 优化数据库操作,确保分组顺序的正确性和唯一性
- 修复了一些与分组列表相关的小问题
2025-08-22 15:10:47 +08:00
SparkMemory
86f4e54d13 Merge pull request #97 from CodeNoobLH/dev
feat(frontend): 增加关注股票功能并优化表格显示- 在指标选股组件中添加关注股票功能
2025-08-16 21:36:50 +08:00
浓睡不消残酒
71e6ff4233 feat(frontend): 增加关注股票功能并优化表格显示- 在 SelectStock 组件中添加关注股票功能
- 实现股票关注逻辑,包括检查是否已关注
- 优化表格宽度计算,确保适配不同屏幕
- 在表格中添加操作列,用于关注股票
2025-08-14 18:13:24 +08:00
ArvinLovegood
e844e2cff9 feat(frontend):添加AI智能体聊天功能
- 在前端 App.vue 中添加 AI智能体聊天入口
- 在后端 App.d.ts 和 App.js 中添加 ChatWithAgent 函数- 在 app_common.go 中实现 ChatWithAgent 方法,使用 agent.NewStockAiAgentApi().Chat 进行聊天
- 更新 go.mod,添加与 AI 聊天相关的依赖
2025-08-09 19:54:49 +08:00
ArvinLovegood
27af39ff61 docs(README): 更新大模型支持列表并调整推广链接
-移除 MACOS 安装版下载链接
- 删除 优云智算 注册链接
-优化大模型支持列表格式
2025-08-08 11:56:27 +08:00
ArvinLovegood
5537ebb87a docs(README):更新支持大模型/平台列表
- 在支持大模型/平台列表中添加了302.AI
- 302.AI 提供新用户注册赠送 $1 测试额度
- 更新了README中的注册链接信息
2025-08-05 18:38:14 +08:00
ArvinLovegood
b906140dd5 docs(README):更新支持大模型/平台列表
- 在支持大模型/平台列表中添加了302.AI
- 302.AI 提供新用户注册赠送 $1 测试额度
- 更新了README中的注册链接信息
2025-08-05 18:32:33 +08:00
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
58 changed files with 4612 additions and 366 deletions

View File

@@ -50,7 +50,7 @@ jobs:
build-name: ${{ matrix.build.name }}
build-platform: ${{ matrix.build.platform }}
package: true
go-version: '1.24'
go-version: '1.25'
build-tags: ${{ github.ref_name }}
build-commit-message: ${{ steps.get_commit_message.outputs.commit_message }}
build-statement: ${{ env.OFFICIAL_STATEMENT }}

View File

@@ -25,21 +25,24 @@
- 安装版:[go-stock-amd64-installer.exe](https://github.com/ArvinLovegood/go-stock/releases)
- 绿色版:[go-stock-windows-amd64.exe](https://github.com/ArvinLovegood/go-stock/releases)
- MACOS绿色版[go-stock-darwin-universal](https://github.com/ArvinLovegood/go-stock/releases)
- MACOS安装版[go-stock-darwin-universal.pkg](https://github.com/ArvinLovegood/go-stock/releases)
[//]: # (- MACOS安装版[go-stock-darwin-universal.pkg](https://github.com/ArvinLovegood/go-stock/releases))
### 💬 支持大模型/平台
| 模型 | 状态 | 备注 |
| --- | --- |---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [OpenAI](https://platform.openai.com/) | ✅ | 可接入任何 OpenAI 接口格式模型 |
| [Ollama](https://ollama.com/) | ✅ | 本地大模型运行平台 |
| [LMStudio](https://lmstudio.ai/) | ✅ | 本地大模型运行平台 |
| [AnythingLLM](https://anythingllm.com/) | ✅ | 本地知识库 |
| [DeepSeek](https://www.deepseek.com/) | ✅ | deepseek-reasoner,deepseek-chat |
| [大模型聚合平台](https://cloud.siliconflow.cn/i/foufCerk) | ✅ | 如:[硅基流动](https://cloud.siliconflow.cn/i/foufCerk)[火山方舟](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ) ,[优云智算](https://www.compshare.cn/image-community?ytag=GPU_YY-gh_gostock) |
| 模型 | 状态 | 备注 |
| --- | --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [OpenAI](https://platform.openai.com/) | ✅ | 可接入任何 OpenAI 接口格式模型 |
| [Ollama](https://ollama.com/) | ✅ | 本地大模型运行平台 |
| [LMStudio](https://lmstudio.ai/) | ✅ | 本地大模型运行平台 |
| [AnythingLLM](https://anythingllm.com/) | ✅ | 本地知识库 |
| [DeepSeek](https://www.deepseek.com/) | ✅ | deepseek-reasoner,deepseek-chat |
| [大模型聚合平台](https://cloud.siliconflow.cn/i/foufCerk) | ✅ | 如:[302.AI](https://share.302.ai/1KUpfG)[硅基流动](https://cloud.siliconflow.cn/i/foufCerk)[火山方舟](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ) |
### <span style="color: #568DF4;">各位亲爱的朋友们,如果您对这个项目感兴趣,请先给我一个<i style="color: #EA2626;">star</i>吧,谢谢!</span>💕
- 优云智算by UCloud万卡规模4090免费用10小时新人注册另增50万tokens海量热门源项目镜像一键部署[注册链接](https://www.compshare.cn/image-community?ytag=GPU_YY-gh_gostock)
- 302.AI新用户使用邀请码注册即可领取 $1 测试额度![注册链接](https://share.302.ai/1KUpfG)
[//]: # (- 优云智算by UCloud万卡规模4090免费用10小时新人注册另增50万tokens海量热门源项目镜像一键部署[注册链接]&#40;https://www.compshare.cn/image-community?ytag=GPU_YY-gh_gostock&#41;)
- 火山方舟新用户每个模型注册即送50万tokens[注册链接](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ)
- 硅基流动(siliconflow)注册即送2000万Tokens[注册链接](https://cloud.siliconflow.cn/i/foufCerk)
- Tushare大数据开放社区,免费提供各类金融数据,助力行业和量化研究(注意Tushare只需要120积分即可注册完成个人资料补充即可得120积分)[注册链接](https://tushare.pro/register?reg=701944)
@@ -59,7 +62,7 @@
| 功能说明 | 状态 | 备注 |
|-----------------|----|----------------------------------------------------------------------------------------------------------|
| 股票分析知识库 | 🚧 | 未来计划 |
| Ai智能选股 | 🚧 | Ai智能选股功能开发中(下半年重点开发计划) |
| Ai智能选股 | | Ai智能选股功能(市场行情-》AI总结/AI智能体功能) |
| ETF支持 | 🚧 | ETF数据支持 (目前可以查看净值和估值) |
| 美股支持 | ✅ | 美股数据支持 |
| 港股支持 | ✅ | 港股数据支持 |

116
app.go
View File

@@ -7,8 +7,6 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
"github.com/inconshreveable/go-update"
"go-stock/backend/data"
"go-stock/backend/db"
"go-stock/backend/logger"
@@ -18,6 +16,9 @@ import (
"strings"
"time"
"github.com/duke-git/lancet/v2/cryptor"
"github.com/inconshreveable/go-update"
"github.com/PuerkitoBio/goquery"
"github.com/coocood/freecache"
"github.com/duke-git/lancet/v2/convertor"
@@ -104,6 +105,76 @@ 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"},
},
},
})
//tools = append(tools, data.Tool{
// Type: "function",
// Function: data.ToolFunction{
// Name: "QueryBKDictInfo",
// Description: "获取所有板块/行业名称或者代码(bkCode,bkName)",
// },
//})
//tools = append(tools, data.Tool{
// Type: "function",
// Function: data.ToolFunction{
// Name: "GetIndustryResearchReport",
// Description: "获取行业/板块研究报告,请先使用QueryBKDictInfo工具获取行业代码然后输入行业代码调用",
// Parameters: data.FunctionParameters{
// Type: "object",
// Properties: map[string]any{
// "bkCode": map[string]any{
// "type": "string",
// "description": "板块/行业代码",
// },
// },
// Required: []string{"bkCode"},
// },
// },
//})
tools = append(tools, data.Tool{
Type: "function",
Function: data.ToolFunction{
Name: "GetStockResearchReport",
Description: "获取股票的分析/研究报告",
Parameters: data.FunctionParameters{
Type: "object",
Properties: map[string]any{
"stockCode": map[string]any{
"type": "string",
"description": "股票代码",
},
},
Required: []string{"stockCode"},
},
},
})
return tools
}
@@ -256,7 +327,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 +370,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 +509,12 @@ func (a *App) domReady(ctx context.Context) {
//检查新版本
go func() {
a.CheckUpdate(0)
a.CheckStockBaseInfo(a.ctx)
go a.CheckStockBaseInfo(a.ctx)
a.cron.AddFunc("0 0 2 * * *", func() {
logger.SugaredLogger.Errorf("Checking for updates...")
a.CheckStockBaseInfo(a.ctx)
})
a.cron.AddFunc("30 05 8,12,20 * * *", func() {
logger.SugaredLogger.Errorf("Checking for updates...")
a.CheckUpdate(0)
@@ -488,6 +564,11 @@ func (a *App) CheckStockBaseInfo(ctx context.Context) {
SetResult(stockBasics).
Get("http://8.134.249.145:18080/go-stock/stock_basic.json")
count := int64(0)
db.Dao.Model(&data.StockBasic{}).Count(&count)
if count == int64(len(*stockBasics)) {
return
}
for _, stock := range *stockBasics {
stockInfo := &data.StockBasic{
TsCode: stock.TsCode,
@@ -545,10 +626,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()
//}
}
}
@@ -1182,6 +1274,14 @@ func (a *App) GetGroupList() []data.Group {
return data.NewStockGroupApi(db.Dao).GetGroupList()
}
func (a *App) UpdateGroupSort(id int, newSort int) bool {
return data.NewStockGroupApi(db.Dao).UpdateGroupSort(id, newSort)
}
func (a *App) InitializeGroupSort() bool {
return data.NewStockGroupApi(db.Dao).InitializeGroupSort()
}
func (a *App) GetGroupStockList(groupId int) []data.GroupStock {
return data.NewStockGroupApi(db.Dao).GetGroupStockByGroupId(groupId)
}

View File

@@ -1,6 +1,8 @@
package main
import (
"github.com/wailsapp/wails/v2/pkg/runtime"
"go-stock/backend/agent"
"go-stock/backend/data"
"go-stock/backend/models"
)
@@ -62,3 +64,10 @@ func (a App) SearchStock(words string) map[string]any {
func (a App) GetHotStrategy() map[string]any {
return data.NewSearchStockApi("").HotStrategy()
}
func (a App) ChatWithAgent(question string, aiConfigId int, sysPromptId *int) {
ch := agent.NewStockAiAgentApi().Chat(question, aiConfigId, sysPromptId)
for msg := range ch {
runtime.EventsEmit(a.ctx, "agent-message", msg)
}
}

93
backend/agent/agent.go Normal file
View File

@@ -0,0 +1,93 @@
package agent
import (
"context"
"go-stock/backend/agent/tools"
"go-stock/backend/data"
"go-stock/backend/logger"
"time"
"github.com/cloudwego/eino-ext/components/model/ark"
"github.com/cloudwego/eino-ext/components/model/deepseek"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/flow/agent/react"
"github.com/cloudwego/eino/schema"
)
// GetStockAiAgent @Author spark
// @Date 2025/8/4 16:17
// @Desc
// -----------------------------------------------------------------------------------
func GetStockAiAgent(ctx *context.Context, aiConfig data.AIConfig) *react.Agent {
logger.SugaredLogger.Infof("GetStockAiAgent aiConfig: %v", aiConfig)
temperature := float32(aiConfig.Temperature)
var toolableChatModel model.ToolCallingChatModel
var err error
if aiConfig.BaseUrl == "https://ark.cn-beijing.volces.com/api/v3" {
toolableChatModel, err = ark.NewChatModel(context.Background(), &ark.ChatModelConfig{
BaseURL: aiConfig.BaseUrl,
Model: aiConfig.ModelName,
APIKey: aiConfig.ApiKey,
MaxTokens: &aiConfig.MaxTokens,
Temperature: &temperature,
})
} else if aiConfig.BaseUrl == "https://api.deepseek.com" {
toolableChatModel, err = deepseek.NewChatModel(*ctx, &deepseek.ChatModelConfig{
BaseURL: aiConfig.BaseUrl,
Model: aiConfig.ModelName,
APIKey: aiConfig.ApiKey,
Timeout: time.Duration(aiConfig.TimeOut) * time.Second,
MaxTokens: aiConfig.MaxTokens,
Temperature: temperature,
})
} else {
toolableChatModel, err = openai.NewChatModel(*ctx, &openai.ChatModelConfig{
BaseURL: aiConfig.BaseUrl,
Model: aiConfig.ModelName,
APIKey: aiConfig.ApiKey,
Timeout: time.Duration(aiConfig.TimeOut) * time.Second,
MaxTokens: &aiConfig.MaxTokens,
Temperature: &temperature,
})
}
if err != nil {
logger.SugaredLogger.Error(err.Error())
return nil
}
// 初始化所需的 tools
aiTools := compose.ToolsNodeConfig{
Tools: []tool.BaseTool{
tools.GetQueryEconomicDataTool(),
tools.GetQueryStockPriceInfoTool(),
tools.GetQueryStockCodeInfoTool(),
tools.GetQueryMarketNewsTool(),
tools.GetChoiceStockByIndicatorsTool(),
tools.GetStockKLineTool(),
tools.GetInteractiveAnswerDataTool(),
tools.GetFinancialReportTool(),
tools.GetQueryStockNewsTool(),
tools.GetIndustryResearchReportTool(),
tools.GetQueryBKDictTool(),
},
}
// 创建 agent
agent, err := react.NewAgent(*ctx, &react.AgentConfig{
ToolCallingModel: toolableChatModel,
ToolsConfig: aiTools,
MaxStep: len(aiTools.Tools)*1 + 3,
MessageModifier: func(ctx context.Context, input []*schema.Message) []*schema.Message {
return input
},
})
if err != nil {
logger.SugaredLogger.Error(err.Error())
return nil
}
return agent
}

View File

@@ -0,0 +1,91 @@
package agent
import (
"context"
"errors"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/flow/agent"
"github.com/cloudwego/eino/flow/agent/react"
"github.com/cloudwego/eino/schema"
"github.com/samber/lo"
"go-stock/backend/agent/tool_logger"
"go-stock/backend/data"
"go-stock/backend/logger"
"io"
)
// @Author spark
// @Date 2025/8/7 9:07
// @Desc
// -----------------------------------------------------------------------------------
type StockAiAgent struct {
*react.Agent
}
func NewStockAiAgentApi() *StockAiAgent {
return &StockAiAgent{}
}
func (receiver StockAiAgent) newStockAiAgent(ctx *context.Context, aiConfigId int) *StockAiAgent {
settingConfig := data.GetSettingConfig()
aiConfig, ok := lo.Find(settingConfig.AiConfigs, func(item *data.AIConfig) bool {
return uint(aiConfigId) == item.ID
})
if !ok {
return nil
}
return &StockAiAgent{
Agent: GetStockAiAgent(ctx, *aiConfig),
}
}
func (receiver StockAiAgent) Chat(question string, aiConfigId int, sysPromptId *int) chan *schema.Message {
ch := make(chan *schema.Message, 512)
ctx := context.Background()
stockAiAgent := receiver.newStockAiAgent(&ctx, aiConfigId)
sysPrompt := ""
if sysPromptId == nil || *sysPromptId == 0 {
sysPrompt = "你现在扮演一位拥有20年实战经验的顶级股票投资大师精通价值投资、趋势交易、量化分析等多种策略。你擅长结合宏观经济、行业周期和企业基本面进行全方位、精准的多维分析尤其对A股、港股、美股市场有深刻理解始终秉持“风险控制第一”的原则善于用通俗易懂的方式传授投资智慧。"
} else {
sysPrompt = data.NewPromptTemplateApi().GetPromptTemplateByID(*sysPromptId)
}
agentOption := []agent.AgentOption{
agent.WithComposeOptions(compose.WithCallbacks(&tool_logger.LoggerCallback{MessageChanel: ch})),
//react.WithChatModelOptions(ark.WithCache(cacheOption)),
}
go func() {
defer close(ch)
sr, err := stockAiAgent.Stream(ctx, []*schema.Message{
{
Role: schema.System,
Content: sysPrompt,
},
{
Role: schema.User,
Content: question,
},
}, agentOption...)
if err != nil {
logger.SugaredLogger.Errorf("stream error: %v", err)
return
}
defer sr.Close()
for {
msg, err := sr.Recv()
if err != nil {
if errors.Is(err, io.EOF) {
// finish
break
}
// error
logger.SugaredLogger.Errorf("failed to recv: %v", err)
break
}
logger.SugaredLogger.Infof("stream: %s", msg.String())
ch <- msg
}
}()
return ch
}

View File

@@ -0,0 +1,84 @@
package agent
import (
"context"
"errors"
"go-stock/backend/agent/tool_logger"
"go-stock/backend/data"
"go-stock/backend/db"
"go-stock/backend/logger"
"io"
"strings"
"testing"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/flow/agent"
"github.com/cloudwego/eino/schema"
)
// @Author spark
// @Date 2025/8/4 17:32
// @Desc
//-----------------------------------------------------------------------------------
func TestGetStockAiAgent(t *testing.T) {
ctx := context.Background()
db.Init("../../data/stock.db")
config := data.GetSettingConfig()
aiAgent := GetStockAiAgent(&ctx, *config.AiConfigs[0])
opt := []agent.AgentOption{
agent.WithComposeOptions(compose.WithCallbacks(&tool_logger.LoggerCallback{})),
//react.WithChatModelOptions(ark.WithCache(cacheOption)),
}
sr, err := aiAgent.Stream(ctx, []*schema.Message{
{
Role: schema.System,
Content: config.Settings.Prompt + "",
},
{
Role: schema.User,
Content: "结合以上提供的宏观经济数据/市场指数行情/国内外市场资讯/电报/会议/事件/投资者关注的问题,\n结合宏观经济事件驱动政策支持投资者关注的问题分析当前市场情绪和热点 找出有潜力/优质的板块/行业/概念/标的/主题,\n多因子深度分析计算上涨或下跌的逻辑和概率\n最后按风险和投资周期给出具体推荐标的操作建议",
},
}, opt...)
if err != nil {
logger.SugaredLogger.Errorf("stream error: %v", err)
return
}
defer sr.Close() // remember to close the stream
md := strings.Builder{}
for {
msg, err := sr.Recv()
if err != nil {
if errors.Is(err, io.EOF) {
// finish
break
}
// error
logger.SugaredLogger.Errorf("failed to recv: %v", err)
return
}
//logger.SugaredLogger.Infof("stream recv: %v", msg)
if msg.ReasoningContent != "" {
md.WriteString(msg.ReasoningContent)
}
if msg.Content != "" {
md.WriteString(msg.Content)
}
}
logger.SugaredLogger.Info(md.String())
//logger.SugaredLogger.Infof("stream done:\n%s", md.String())
}
func TestAgent(t *testing.T) {
db.Init("../../data/stock.db")
ch := NewStockAiAgentApi().Chat("分析一下海立股份,使用工具", 1, nil)
for message := range ch {
logger.SugaredLogger.Infof("res:%s", message.String())
}
}

View File

@@ -0,0 +1,98 @@
package tool_logger
import (
"context"
"encoding/json"
"errors"
"go-stock/backend/logger"
"io"
"github.com/cloudwego/eino/callbacks"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/flow/agent/react"
"github.com/cloudwego/eino/schema"
)
// @Author spark
// @Date 2025/8/5 10:21
// @Desc
//-----------------------------------------------------------------------------------
type LoggerCallback struct {
MessageChanel chan *schema.Message
callbacks.HandlerBuilder // 可以用 callbacks.HandlerBuilder 来辅助实现 callback
}
func (cb *LoggerCallback) OnStart(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context {
logger.SugaredLogger.Infof("==================")
inputStr, _ := json.MarshalIndent(input, "", " ") // nolint: byted_s_returned_err_check
logger.SugaredLogger.Infof("[OnStart] %s\n", string(inputStr))
modelCallbackInput := model.ConvCallbackInput(input)
if modelCallbackInput != nil {
for _, message := range modelCallbackInput.Messages {
cb.MessageChanel <- message
}
}
return ctx
}
func (cb *LoggerCallback) OnEnd(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) context.Context {
logger.SugaredLogger.Infof("=========[OnEnd]=========")
outputStr, _ := json.MarshalIndent(output, "", " ") // nolint: byted_s_returned_err_check
logger.SugaredLogger.Infof(string(outputStr))
return ctx
}
func (cb *LoggerCallback) OnError(ctx context.Context, info *callbacks.RunInfo, err error) context.Context {
logger.SugaredLogger.Infof("=========[OnError]=========")
logger.SugaredLogger.Infof("%s", err.Error())
return ctx
}
func (cb *LoggerCallback) OnEndWithStreamOutput(ctx context.Context, info *callbacks.RunInfo,
output *schema.StreamReader[callbacks.CallbackOutput]) context.Context {
var graphInfoName = react.GraphName
go func() {
defer func() {
if err := recover(); err != nil {
logger.SugaredLogger.Infof("[OnEndStream] panic err:", err)
}
}()
defer output.Close() // remember to close the stream in defer
logger.SugaredLogger.Infof("=========[OnEndStream]=========")
for {
frame, err := output.Recv()
if errors.Is(err, io.EOF) {
// finish
break
}
if err != nil {
logger.SugaredLogger.Infof("internal error: %s\n", err)
return
}
s, err := json.Marshal(frame)
if err != nil {
logger.SugaredLogger.Infof("internal error: %s\n", err)
return
}
if info.Name == graphInfoName { // 仅打印 graph 的输出, 否则每个 stream 节点的输出都会打印一遍
logger.SugaredLogger.Infof("%s: %s\n", info.Name, string(s))
}
}
}()
return ctx
}
func (cb *LoggerCallback) OnStartWithStreamInput(ctx context.Context, info *callbacks.RunInfo,
input *schema.StreamReader[callbacks.CallbackInput]) context.Context {
defer input.Close()
return ctx
}

View File

@@ -0,0 +1,34 @@
package tools
import (
"context"
"encoding/json"
"go-stock/backend/data"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/coocood/freecache"
)
// @Author spark
// @Date 2025/9/27 14:09
// @Desc
// -----------------------------------------------------------------------------------
type ToolQueryBKDict struct{}
func (t ToolQueryBKDict) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "QueryBKDictInfo",
Desc: "获取所有板块/行业名称或者代码(bkCode,bkName)",
}, nil
}
func (t ToolQueryBKDict) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
resp := data.NewMarketNewsApi().EMDictCode("016", freecache.NewCache(100))
bytes, err := json.Marshal(resp)
return string(bytes), err
}
func GetQueryBKDictTool() tool.InvokableTool {
return &ToolQueryBKDict{}
}

View File

@@ -0,0 +1,140 @@
package tools
import (
"context"
"encoding/json"
"fmt"
"go-stock/backend/data"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/random"
)
// @Author spark
// @Date 2025/8/5 11:17
// @Desc
//-----------------------------------------------------------------------------------
func GetChoiceStockByIndicatorsTool() tool.InvokableTool {
return &ChoiceStockByIndicators{}
}
type ChoiceStockByIndicators struct {
}
func (c ChoiceStockByIndicators) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "ChoiceStockByIndicators",
Desc: "根据自然语言筛选股票,返回自然语言选股条件要求的股票所有相关数据。输入股票名称可以获取当前股票最新的股价交易数据和基础财务指标信息,多个股票名称使用,分隔。",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"words": {
Type: "string",
Desc: "选股自然语言。" +
"例:上海贝岭,macd,rsi,kdj,boll,5日均线,14日均线,30日均线,60日均线,成交量,OBV,EMA" +
"例1创新药,半导体;PE<30;净利润增长率>50%。 " +
"例2上证指数,科创50。 " +
"例3长电科技,上海贝岭。" +
"例4长电科技,上海贝岭;KDJ,MACD,RSI,BOLL,主力净流入/流出" +
"例5换手率大于3%小于25%.量比1以上. 10日内有过涨停.股价处于峰值的二分之一以下.流通股本<100亿.当日和连续四日净流入;股价在20日均线以上.分时图股价在均线之上.热门板块下涨幅领先的A股. 当日量能20000手以上.沪深个股.近一年市盈率波动小于150%.MACD金叉;不要ST股及不要退市股非北交所每股收益>0。" +
"例6沪深主板.流通市值小于100亿.市值大于10亿.60分钟dif大于dea.60分钟skdj指标k值大于d值.skdj指标k值小于90.换手率大于3%.成交额大于1亿元.量比大于2.涨幅大于2%小于7%.股价大于5小于50.创业板.10日均线大于20日均线;不要ST股及不要退市股;不要北交所;不要科创板;不要创业板。" +
"例7股价在20日线上一月之内涨停次数>=1量比大于1换手率大于3%,流通市值大于 50亿小于200亿。" +
"例8基本条件前期有爆量回调到 10 日线,当日是缩量阴线,均线趋势向上。;优选条件:一月之内涨停次数>=1" +
"例9今日涨幅大于等于2%小于等于9%;量比大于等于1.1小于等于5;换手率大于等于5%小于等于20%;市值大于等于30小于等于300亿;5日、10日、30日、60日均线、5周、10周、30周、60周均线多头排列",
Required: true,
},
}),
}, nil
}
func (c ChoiceStockByIndicators) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
parms := map[string]any{}
err := json.Unmarshal([]byte(argumentsInJSON), &parms)
if err != nil {
return "", err
}
content := "无符合条件的数据"
words := parms["words"].(string)
res := data.NewSearchStockApi(words).SearchStock(random.RandInt(5, 20))
if convertor.ToString(res["code"]) == "100" {
resData := res["data"].(map[string]any)
result := resData["result"].(map[string]any)
dataList := result["dataList"].([]any)
columns := result["columns"].([]any)
headers := map[string]string{}
for _, v := range columns {
//logger.SugaredLogger.Infof("v:%+v", v)
d := v.(map[string]any)
//logger.SugaredLogger.Infof("key:%s title:%s dateMsg:%s unit:%s", d["key"], d["title"], d["dateMsg"], d["unit"])
title := convertor.ToString(d["title"])
if convertor.ToString(d["dateMsg"]) != "" {
title = title + "[" + convertor.ToString(d["dateMsg"]) + "]"
}
if convertor.ToString(d["unit"]) != "" {
title = title + "(" + convertor.ToString(d["unit"]) + ")"
}
headers[d["key"].(string)] = title
}
table := &[]map[string]any{}
for _, v := range dataList {
d := v.(map[string]any)
tmp := map[string]any{}
for key, title := range headers {
tmp[title] = convertor.ToString(d[key])
}
*table = append(*table, tmp)
}
jsonData, _ := json.Marshal(*table)
markdownTable, _ := JSONToMarkdownTable(jsonData)
//logger.SugaredLogger.Infof("markdownTable=\n%s", markdownTable)
content = "\r\n### 工具筛选出的股票数据:\r\n" + markdownTable + "\r\n"
}
return content, nil
}
// JSONToMarkdownTable 将JSON数据转换为Markdown表格
func JSONToMarkdownTable(jsonData []byte) (string, error) {
var data []map[string]interface{}
err := json.Unmarshal(jsonData, &data)
if err != nil {
return "", err
}
if len(data) == 0 {
return "", nil
}
// 获取表头
headers := []string{}
for key := range data[0] {
headers = append(headers, key)
}
// 构建表头行
headerRow := "|"
for _, header := range headers {
headerRow += fmt.Sprintf(" %s |", header)
}
headerRow += "\n"
// 构建分隔行
separatorRow := "|"
for range headers {
separatorRow += " --- |"
}
separatorRow += "\n"
// 构建数据行
bodyRows := ""
for _, rowData := range data {
bodyRow := "|"
for _, header := range headers {
value := rowData[header]
bodyRow += fmt.Sprintf(" %v |", value)
}
bodyRows += bodyRow + "\n"
}
return headerRow + separatorRow + bodyRows, nil
}

View File

@@ -0,0 +1,35 @@
package tools
import (
"github.com/duke-git/lancet/v2/strutil"
"strings"
)
// @Author spark
// @Date 2025/8/5 17:20
// @Desc
//-----------------------------------------------------------------------------------
func GetStockCode(dcCode string) string {
if strutil.ContainsAny(dcCode, []string{"."}) {
sp := strings.Split(dcCode, ".")
return strings.ToLower(sp[1] + sp[0])
}
//北京证券交易所 883、87、88 等) 创新型中小企业(专精特新为主)
//上海证券交易所 660、688 等) 大盘蓝筹、科创板(高新技术)
//深圳证券交易所 0、3000、002、30 等) 中小盘、创业板(成长型创新企业)
switch dcCode[0:1] {
case "8":
return "bj" + dcCode
case "9":
return "bj" + dcCode
case "6":
return "sh" + dcCode
case "0":
return "sz" + dcCode
case "3":
return "sz" + dcCode
}
return dcCode
}

View File

@@ -0,0 +1,79 @@
package tools
import (
"context"
"encoding/json"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"go-stock/backend/data"
"go-stock/backend/util"
"strings"
)
// @Author spark
// @Date 2025/8/4 16:38
// @Desc
//-----------------------------------------------------------------------------------
func GetQueryEconomicDataTool() tool.InvokableTool {
return &ToolQueryEconomicData{}
}
type ToolQueryEconomicData struct {
}
func (t ToolQueryEconomicData) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "QueryEconomicData",
Desc: "查询宏观经济数据(GDP,CPI,PPI,PMI)",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"flag": {
Type: "string",
Desc: "all:宏观经济数据(GDP,CPI,PPI,PMI);GDP:国内生产总值;CPI:居民消费价格指数;PPI:工业品出厂价格指数;PMI:采购经理人指数",
Required: false,
},
}),
}, nil
}
func (t ToolQueryEconomicData) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
parms := map[string]any{}
err := json.Unmarshal([]byte(argumentsInJSON), &parms)
if err != nil {
return "", err
}
var market strings.Builder
switch parms["flag"].(string) {
case "GDP":
res := data.NewMarketNewsApi().GetGDP()
md := util.MarkdownTableWithTitle("国内生产总值(GDP)", res.GDPResult.Data)
market.WriteString(md)
case "CPI":
res2 := data.NewMarketNewsApi().GetCPI()
md2 := util.MarkdownTableWithTitle("居民消费价格指数(CPI)", res2.CPIResult.Data)
market.WriteString(md2)
case "PPI":
res3 := data.NewMarketNewsApi().GetPPI()
md3 := util.MarkdownTableWithTitle("工业品出厂价格指数(PPI)", res3.PPIResult.Data)
market.WriteString(md3)
case "PMI":
res4 := data.NewMarketNewsApi().GetPMI()
md4 := util.MarkdownTableWithTitle("商品价格指数(PMI)", res4.PMIResult.Data)
market.WriteString(md4)
default:
res := data.NewMarketNewsApi().GetGDP()
md := util.MarkdownTableWithTitle("国内生产总值(GDP)", res.GDPResult.Data)
market.WriteString(md)
res2 := data.NewMarketNewsApi().GetCPI()
md2 := util.MarkdownTableWithTitle("居民消费价格指数(CPI)", res2.CPIResult.Data)
market.WriteString(md2)
res3 := data.NewMarketNewsApi().GetPPI()
md3 := util.MarkdownTableWithTitle("工业品出厂价格指数(PPI)", res3.PPIResult.Data)
market.WriteString(md3)
res4 := data.NewMarketNewsApi().GetPMI()
md4 := util.MarkdownTableWithTitle("采购经理人指数(PMI)", res4.PMIResult.Data)
market.WriteString(md4)
}
return market.String(), nil
}

View File

@@ -0,0 +1,50 @@
package tools
import (
"context"
"fmt"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/tidwall/gjson"
"go-stock/backend/data"
"strings"
)
// @Author spark
// @Date 2025/8/5 15:49
// @Desc
//-----------------------------------------------------------------------------------
func GetFinancialReportTool() tool.InvokableTool {
return &FinancialReportTool{}
}
type FinancialReportTool struct {
}
func (f FinancialReportTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "GetFinancialReport",
Desc: "查询股票财务报表数据",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"stockCode": {
Type: "string",
Desc: "股票代码A股sh,sz开头;港股hk开头,美股us开头不能批量查询",
Required: true,
},
}),
}, nil
}
func (f FinancialReportTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
stockCode := gjson.Get(argumentsInJSON, "stockCode").String()
messages := data.GetFinancialReportsByXUEQIU(GetStockCode(stockCode), 30)
if messages == nil || len(*messages) == 0 {
return "", fmt.Errorf("没有找到%s的财务报告", stockCode)
}
md := strings.Builder{}
for _, s := range *messages {
md.WriteString(s)
}
return md.String(), nil
}

View File

@@ -0,0 +1,69 @@
package tools
import (
"context"
"go-stock/backend/data"
log "go-stock/backend/logger"
"strings"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/strutil"
"github.com/tidwall/gjson"
)
// @Author spark
// @Date 2025/8/9 18:48
// @Desc
//-----------------------------------------------------------------------------------
func GetIndustryResearchReportTool() tool.InvokableTool {
return &IndustryResearchReportTool{api: data.NewMarketNewsApi()}
}
type IndustryResearchReportTool struct {
api *data.MarketNewsApi
}
func (i IndustryResearchReportTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "GetIndustryResearchReport",
Desc: "获取行业/板块研究报告",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"name": {
Type: "string",
Desc: "行业/板块行业名称",
Required: false,
},
"code": {
Type: "string",
Desc: "行业/板块代码",
Required: true,
},
}),
}, nil
}
func (i IndustryResearchReportTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
code := gjson.Get(argumentsInJSON, "code").String()
code = strutil.ReplaceWithMap(code, map[string]string{
"-": "",
"_": "",
"bk": "",
"BK": "",
"bk0": "",
"BK0": "",
})
log.SugaredLogger.Debugf("code:%s", code)
codeStr := convertor.ToString(code)
resp := i.api.IndustryResearchReport(codeStr, 7)
md := strings.Builder{}
for _, a := range resp {
data := a.(map[string]any)
md.WriteString(i.api.GetIndustryReportInfo(data["infoCode"].(string)))
}
log.SugaredLogger.Debugf("codeNum:%s IndustryResearchReport:\n %s", code, md.String())
return md.String(), nil
}

View File

@@ -0,0 +1,64 @@
package tools
import (
"context"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/duke-git/lancet/v2/convertor"
"github.com/tidwall/gjson"
"go-stock/backend/data"
"go-stock/backend/util"
)
// @Author spark
// @Date 2025/8/5 12:46
// @Desc
//-----------------------------------------------------------------------------------
func GetInteractiveAnswerDataTool() tool.InvokableTool {
return &InteractiveAnswerDataTool{}
}
type InteractiveAnswerDataTool struct {
}
func (i InteractiveAnswerDataTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "QueryInteractiveAnswerData",
Desc: "获取投资者与上市公司互动问答的数据,反映当前投资者关注的热点问题。",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"page": {
Type: "string",
Desc: "分页号",
Required: true,
},
"pageSize": {
Type: "string",
Desc: "分页大小",
Required: true,
},
"keyWord": {
Type: "string",
Desc: "搜索关键词,多个关键词空格隔开(可输入股票名称或者当前热门板块/行业/概念/标的/事件等)",
Required: false,
},
}),
}, nil
}
func (i InteractiveAnswerDataTool) InvokableRun(ctx context.Context, funcArguments string, opts ...tool.Option) (string, error) {
page := gjson.Get(funcArguments, "page").String()
pageSize := gjson.Get(funcArguments, "pageSize").String()
keyWord := gjson.Get(funcArguments, "keyWord").String()
pageNo, err := convertor.ToInt(page)
if err != nil {
pageNo = 1
}
pageSizeNum, err := convertor.ToInt(pageSize)
if err != nil {
pageSizeNum = 50
}
datas := data.NewMarketNewsApi().InteractiveAnswer(int(pageNo), int(pageSizeNum), keyWord)
content := util.MarkdownTableWithTitle("投资互动数据", datas.Results)
return content, nil
}

View File

@@ -0,0 +1,79 @@
package tools
import (
"context"
"encoding/json"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/duke-git/lancet/v2/random"
"github.com/tidwall/gjson"
"go-stock/backend/data"
"go-stock/backend/logger"
"strings"
)
// @Author spark
// @Date 2025/8/4 16:38
// @Desc
//-----------------------------------------------------------------------------------
func GetQueryMarketNewsTool() tool.InvokableTool {
return &QueryMarketNews{}
}
type QueryMarketNews struct {
}
func (q QueryMarketNews) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "QueryMarketNews",
Desc: "国内外市场资讯/电报/会议/事件",
}, nil
}
func (q QueryMarketNews) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
md := strings.Builder{}
res := data.NewMarketNewsApi().ClsCalendar()
for _, a := range res {
bytes, err := json.Marshal(a)
if err != nil {
continue
}
//logger.SugaredLogger.Debugf("value: %+v", string(bytes))
date := gjson.Get(string(bytes), "calendar_day")
md.WriteString("\n### 事件/会议日期:" + date.String())
list := gjson.Get(string(bytes), "items")
//logger.SugaredLogger.Debugf("value: %+v,list: %+v", date.String(), list)
list.ForEach(func(key, value gjson.Result) bool {
logger.SugaredLogger.Debugf("key: %+v,value: %+v", key.String(), gjson.Get(value.String(), "title"))
md.WriteString("\n- " + gjson.Get(value.String(), "title").String())
return true
})
}
news := data.NewMarketNewsApi().GetNewsList("财联社电报", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
messageText.WriteString("### " + telegraph.Content + "\n")
}
md.WriteString("\n### 市场资讯:\n" + messageText.String())
resp := data.NewMarketNewsApi().TradingViewNews()
var newsText strings.Builder
for _, a := range *resp {
logger.SugaredLogger.Debugf("TradingViewNews: %s", a.Title)
newsText.WriteString(a.Title + "\n")
}
md.WriteString("\n### 全球新闻资讯:\n" + newsText.String())
reutersNew := data.NewMarketNewsApi().ReutersNew()
reutersNewMessageText := strings.Builder{}
for _, article := range reutersNew.Result.Articles {
reutersNewMessageText.WriteString("## " + article.Title + "\n")
reutersNewMessageText.WriteString("### " + article.Description + "\n")
}
md.WriteString("\n### 外媒全球新闻资讯:\n" + reutersNewMessageText.String())
return md.String(), nil
}

View File

@@ -0,0 +1,49 @@
package tools
import (
"context"
"encoding/json"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"go-stock/backend/data"
)
// @Author spark
// @Date 2025/8/4 18:25
// @Desc
//-----------------------------------------------------------------------------------
func GetQueryStockCodeInfoTool() tool.InvokableTool {
return &QueryStockCodeInfo{}
}
type QueryStockCodeInfo struct {
}
func (q QueryStockCodeInfo) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "QueryStockCodeInfo",
Desc: "查询股票/指数信息(股票/指数名称,股票/指数代码,股票/指数拼音,股票/指数拼音首字母,股票/指数交易所等",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"searchWord": {
Type: "string",
Desc: "股票搜索关键词",
Required: true,
},
}),
}, nil
}
func (q QueryStockCodeInfo) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
parms := map[string]any{}
err := json.Unmarshal([]byte(argumentsInJSON), &parms)
if err != nil {
return "", err
}
stockList := data.NewStockDataApi().GetStockList(parms["searchWord"].(string))
marshal, err := json.Marshal(stockList)
if err != nil {
return "", err
}
return string(marshal), nil
}

View File

@@ -0,0 +1,80 @@
package tools
import (
"context"
"encoding/json"
"fmt"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/strutil"
"github.com/tidwall/gjson"
"go-stock/backend/data"
)
// @Author spark
// @Date 2025/8/5 11:31
// @Desc
//-----------------------------------------------------------------------------------
func GetStockKLineTool() tool.InvokableTool {
return &QueryStockKLine{}
}
type QueryStockKLine struct {
}
func (q QueryStockKLine) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "QueryStockKLine",
Desc: "获取股票K线数据。输入股票名称和K线周期返回股票K线数据。",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"days": {
Type: "string",
Desc: "日K数据条数。",
Required: true,
},
"stockCode": {
Type: "string",
Desc: "股票代码A股sh,sz开头;港股hk开头,美股us开头",
Required: true,
},
}),
}, nil
}
func (q QueryStockKLine) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
stockCode := GetStockCode(gjson.Get(argumentsInJSON, "stockCode").String())
days := gjson.Get(argumentsInJSON, "days").String()
toIntDay, err := convertor.ToInt(days)
if err != nil {
toIntDay = 90
}
if strutil.HasPrefixAny(stockCode, []string{"sz", "sh", "hk", "us", "gb_"}) {
K := &[]data.KLineData{}
if strutil.HasPrefixAny(stockCode, []string{"sz", "sh"}) {
K = data.NewStockDataApi().GetKLineData(stockCode, "240", toIntDay)
}
if strutil.HasPrefixAny(stockCode, []string{"hk", "us", "gb_"}) {
K = data.NewStockDataApi().GetHK_KLineData(stockCode, "day", toIntDay)
}
Kmap := &[]map[string]any{}
for _, kline := range *K {
mapk := make(map[string]any, 6)
mapk["日期"] = kline.Day
mapk["开盘价"] = kline.Open
mapk["最高价"] = kline.High
mapk["最低价"] = kline.Low
mapk["收盘价"] = kline.Close
Volume, _ := convertor.ToFloat(kline.Volume)
mapk["成交量(万手)"] = Volume / 10000.00 / 100.00
*Kmap = append(*Kmap, mapk)
}
jsonData, _ := json.Marshal(Kmap)
markdownTable, _ := JSONToMarkdownTable(jsonData)
res := "\r\n ### " + stockCode + " " + convertor.ToString(toIntDay) + "日K线数据\r\n" + markdownTable + "\r\n"
return res, nil
} else {
return "无数据可能股票代码错误。A股sh,sz开头;港股hk开头,美股us开头", fmt.Errorf("不支持的股票代码:%s", stockCode)
}
}

View File

@@ -0,0 +1,42 @@
package tools
import (
"context"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/tidwall/gjson"
"go-stock/backend/data"
"go-stock/backend/util"
)
// @Author spark
// @Date 2025/8/5 16:27
// @Desc
//-----------------------------------------------------------------------------------
func GetQueryStockNewsTool() tool.InvokableTool {
return &QueryStockNewsTool{}
}
type QueryStockNewsTool struct {
}
func (q QueryStockNewsTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "QueryStockNewsTool",
Desc: "按关键词搜索相关市场资讯/新闻",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"searchWords": {
Type: "string",
Desc: "搜索关键词(多个关键词使用空格分隔)",
Required: true,
},
}),
}, nil
}
func (q QueryStockNewsTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
searchWords := gjson.Get(argumentsInJSON, "searchWords").String()
res := data.NewMarketNewsApi().CailianpressWeb(searchWords)
return util.MarkdownTableWithTitle(searchWords+"市场资讯/新闻", res.List), nil
}

View File

@@ -0,0 +1,57 @@
package tools
import (
"context"
"encoding/json"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"go-stock/backend/data"
"strings"
)
// @Author spark
// @Date 2025/8/4 17:58
// @Desc
//-----------------------------------------------------------------------------------
func GetQueryStockPriceInfoTool() tool.InvokableTool {
return &ToolQueryStockPriceInfo{}
}
type ToolQueryStockPriceInfo struct{}
func (t ToolQueryStockPriceInfo) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "QueryStockPriceInfo",
Desc: "批量获取实时股价数据",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"stockCodes": {
Type: "string",
Desc: "股票代码,多个,隔开,股票代码必须转化为sh或者sz或者hk开头的形式例如sz399001,sh600859",
Required: true,
},
}),
}, nil
}
func (t ToolQueryStockPriceInfo) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
parms := map[string]any{}
err := json.Unmarshal([]byte(argumentsInJSON), &parms)
if err != nil {
return "", err
}
stockCodes := strings.Split(parms["stockCodes"].(string), ",")
var codes []string
for _, code := range stockCodes {
codes = append(codes, GetStockCode(code))
}
realTimeData, err := data.NewStockDataApi().GetStockCodeRealTimeData(codes...)
if err != nil {
return "", err
}
marshal, err := json.Marshal(realTimeData)
if err != nil {
return "", err
}
return string(marshal), nil
}

View File

@@ -1,11 +1,12 @@
//go:build windows
// +build windows
package data
import "C"
import (
"github.com/go-toast/toast"
"go-stock/backend/logger"
"github.com/go-toast/toast"
)
// AlertWindowsApi @Author spark

View File

@@ -1,11 +1,13 @@
//go:build windows
// +build windows
package data
import (
"github.com/go-toast/toast"
"go-stock/backend/logger"
"testing"
"github.com/go-toast/toast"
)
// @Author spark

View File

@@ -4,6 +4,14 @@ import (
"bytes"
"encoding/json"
"fmt"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"strconv"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/coocood/freecache"
"github.com/duke-git/lancet/v2/convertor"
@@ -12,13 +20,6 @@ import (
"github.com/robertkrimen/otto"
"github.com/samber/lo"
"github.com/tidwall/gjson"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"strconv"
"strings"
"time"
)
// @Author spark
@@ -37,7 +38,7 @@ func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
response, _ := resty.New().SetTimeout(time.Duration(crawlTimeOut)*time.Second).R().
SetHeader("Referer", "https://www.cls.cn/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
Get(fmt.Sprintf(url))
Get(url)
var telegraphs []models.Telegraph
//logger.SugaredLogger.Info(string(response.Body()))
document, _ := goquery.NewDocumentFromReader(strings.NewReader(string(response.Body())))
@@ -116,6 +117,27 @@ func (m MarketNewsApi) GetNewsList(source string, limit int) *[]*models.Telegrap
}
return news
}
func (m MarketNewsApi) GetNewsList2(source string, limit int) *[]*models.Telegraph {
news := &[]*models.Telegraph{}
if source != "" {
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("id desc,is_red desc").Limit(limit).Find(news)
} else {
db.Dao.Model(news).Preload("TelegraphTags").Order("id desc,is_red desc").Limit(limit).Find(news)
}
for _, item := range *news {
tags := &[]models.Tags{}
db.Dao.Model(&models.Tags{}).Where("id in ?", lo.Map(item.TelegraphTags, func(item models.TelegraphTags, index int) uint {
return item.TagId
})).Find(&tags)
tagNames := lo.Map(*tags, func(item models.Tags, index int) string {
return item.Name
})
item.SubjectTags = tagNames
logger.SugaredLogger.Infof("tagNames %v SubjectTags%s", tagNames, item.SubjectTags)
}
return news
}
func (m MarketNewsApi) GetTelegraphList(source string) *[]*models.Telegraph {
news := &[]*models.Telegraph{}
if source != "" {
@@ -754,27 +776,27 @@ func (m MarketNewsApi) GetCPI() *models.CPIResp {
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("GDP err:%s", err.Error())
logger.SugaredLogger.Errorf("GetCPI err:%s", err.Error())
return res
}
body := resp.Body()
logger.SugaredLogger.Debugf("GDP:%s", body)
logger.SugaredLogger.Debugf("GetCPI:%s", body)
vm := otto.New()
vm.Run("function data(res){return res};")
val, err := vm.Run(body)
if err != nil {
logger.SugaredLogger.Errorf("GDP err:%s", err.Error())
logger.SugaredLogger.Errorf("GetCPI err:%s", err.Error())
return res
}
data, _ := val.Object().Value().Export()
logger.SugaredLogger.Infof("GDP:%v", data)
logger.SugaredLogger.Infof("GetCPI:%v", data)
marshal, err := json.Marshal(data)
if err != nil {
return res
}
json.Unmarshal(marshal, &res)
logger.SugaredLogger.Infof("GDP:%+v", res)
logger.SugaredLogger.Infof("GetCPI:%+v", res)
return res
}
@@ -789,7 +811,7 @@ func (m MarketNewsApi) GetPPI() *models.PPIResp {
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("GDP err:%s", err.Error())
logger.SugaredLogger.Errorf("GetPPI err:%s", err.Error())
return res
}
body := resp.Body()
@@ -886,3 +908,54 @@ 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": "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
}
func (m MarketNewsApi) CailianpressWeb(searchWords string) *models.CailianpressWeb {
res := &models.CailianpressWeb{}
_, err := resty.New().SetTimeout(time.Second*10).R().
SetHeader("Content-Type", "application/json").
SetHeader("Host", "www.cls.cn").
SetHeader("Origin", "https://www.cls.cn").
SetHeader("Referer", "https://www.cls.cn/telegraph").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
SetBody(fmt.Sprintf(`{"app":"CailianpressWeb","os":"web","sv":"8.4.6","category":"","keyword":"%s"}`, searchWords)).
SetResult(res).
Post("https://www.cls.cn/api/csw?app=CailianpressWeb&os=web&sv=8.4.6&sign=9f8797a1f4de66c2370f7a03990d2737")
if err != nil {
return nil
}
logger.SugaredLogger.Debug(res)
return res
}

View File

@@ -2,13 +2,15 @@ package data
import (
"encoding/json"
"github.com/coocood/freecache"
"github.com/tidwall/gjson"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"strings"
"testing"
"github.com/coocood/freecache"
"github.com/tidwall/gjson"
)
// @Author spark
@@ -71,15 +73,18 @@ func TestLongTiger(t *testing.T) {
func TestStockResearchReport(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().StockResearchReport("600584.sh", 7)
resp := NewMarketNewsApi().StockResearchReport("688082", 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 TestIndustryResearchReport(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().IndustryResearchReport("735", 7)
resp := NewMarketNewsApi().IndustryResearchReport("456", 7)
for _, a := range resp {
logger.SugaredLogger.Debugf("value: %+v", a)
data := a.(map[string]any)
@@ -107,7 +112,11 @@ func TestEMDictCode(t *testing.T) {
if err != nil {
return
}
dict := &[]models.BKDict{}
json.Unmarshal(bytes, dict)
logger.SugaredLogger.Debugf("value: %s", string(bytes))
md := util.MarkdownTableWithTitle("行业/板块代码", dict)
logger.SugaredLogger.Debugf(md)
}
@@ -216,3 +225,12 @@ 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

@@ -6,6 +6,14 @@ import (
"encoding/json"
"errors"
"fmt"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"strings"
"sync"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/chromedp/chromedp"
"github.com/duke-git/lancet/v2/convertor"
@@ -15,13 +23,6 @@ import (
"github.com/samber/lo"
"github.com/tidwall/gjson"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"strings"
"sync"
"time"
)
// @Author spark
@@ -188,7 +189,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()
@@ -219,17 +234,24 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
go func() {
defer wg.Done()
var market strings.Builder
market.WriteString(getZSInfo("创业板指数", "sz399006", 30) + "\n")
market.WriteString(getZSInfo("上证综合指数", "sh000001", 30) + "\n")
market.WriteString(getZSInfo("沪深300指数", "sh000300", 30) + "\n")
market.WriteString(GetZSInfo("上证指数", "sh000001", 30) + "\n")
market.WriteString(GetZSInfo("深证成指", "sz399001", 30) + "\n")
market.WriteString(GetZSInfo("创业板指数", "sz399006", 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(),
})
}()
@@ -302,7 +324,7 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
}()
wg.Wait()
news := NewMarketNewsApi().GetNewsList("财联社电报", random.RandInt(50, 150))
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
@@ -374,13 +396,20 @@ 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
market.WriteString(getZSInfo("创业板指数", "sz399006", 30) + "\n")
market.WriteString(getZSInfo("上证综合指数", "sh000001", 30) + "\n")
market.WriteString(getZSInfo("沪深300指数", "sh000300", 30) + "\n")
market.WriteString(GetZSInfo("上证指数", "sh000001", 30) + "\n")
market.WriteString(GetZSInfo("深证成指", "sz399001", 30) + "\n")
market.WriteString(GetZSInfo("创业板指数", "sz399006", 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",
@@ -428,9 +457,23 @@ 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)
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
@@ -547,9 +590,9 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
go func() {
defer wg.Done()
var market strings.Builder
market.WriteString(getZSInfo("创业板指数", "sz399006", 30) + "\n")
market.WriteString(getZSInfo("上证综合指数", "sh000001", 30) + "\n")
market.WriteString(getZSInfo("沪深300指数", "sh000300", 30) + "\n")
market.WriteString(GetZSInfo("创业板指数", "sz399006", 30) + "\n")
market.WriteString(GetZSInfo("上证综合指数", "sh000001", 30) + "\n")
market.WriteString(GetZSInfo("沪深300指数", "sh000300", 30) + "\n")
//logger.SugaredLogger.Infof("NewChatStream getZSInfo=\n%s", market.String())
msg = append(msg, map[string]interface{}{
"role": "user",
@@ -765,25 +808,25 @@ func (o *OpenAi) NewChatStream(stock, stockCode, userQuestion string, sysPromptI
return
}
messages := SearchGuShiTongStockInfo(stockCode, o.CrawlTimeOut)
if messages == nil || len(*messages) == 0 {
logger.SugaredLogger.Error("获取股势通资讯失败")
//ch <- "***❗获取股势通资讯失败,分析结果可能不准确***<hr>"
//go runtime.EventsEmit(o.ctx, "warnMsg", "❗获取股势通资讯失败,分析结果可能不准确")
return
}
var newsText strings.Builder
for _, message := range *messages {
newsText.WriteString(message + "\n")
}
msg = append(msg, map[string]interface{}{
"role": "user",
"content": stock + "相关新闻资讯",
})
msg = append(msg, map[string]interface{}{
"role": "assistant",
"content": newsText.String(),
})
//messages := SearchGuShiTongStockInfo(stockCode, o.CrawlTimeOut)
//if messages == nil || len(*messages) == 0 {
// logger.SugaredLogger.Error("获取股势通资讯失败")
// //ch <- "***❗获取股势通资讯失败,分析结果可能不准确***<hr>"
// //go runtime.EventsEmit(o.ctx, "warnMsg", "❗获取股势通资讯失败,分析结果可能不准确")
// return
//}
//var newsText strings.Builder
//for _, message := range *messages {
// newsText.WriteString(message + "\n")
//}
//msg = append(msg, map[string]interface{}{
// "role": "user",
// "content": stock + "相关新闻资讯",
//})
//msg = append(msg, map[string]interface{}{
// "role": "assistant",
// "content": newsText.String(),
//})
}()
go func() {
@@ -1112,7 +1155,7 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
}
content := "无符合条件的数据"
res := NewSearchStockApi(words).SearchStock(random.RandInt(5, 10))
res := NewSearchStockApi(words).SearchStock(random.RandInt(5, 20))
if convertor.ToString(res["code"]) == "100" {
resData := res["data"].(map[string]any)
result := resData["result"].(map[string]any)
@@ -1278,6 +1321,186 @@ 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,
})
}
//
//if funcName == "QueryBKDictInfo" {
// ch <- map[string]any{
// "code": 1,
// "question": question,
// "chatId": streamResponse.Id,
// "model": streamResponse.Model,
// "content": "\r\n```\r\n开始调用工具QueryBKDictInfo\n参数" + funcArguments + "\r\n```\r\n",
// "time": time.Now().Format(time.DateTime),
// }
// res := NewMarketNewsApi().EMDictCode("016", freecache.NewCache(100))
// bytes, err := json.Marshal(res)
// if err != nil {
// return
// }
// dict := &[]models.BKDict{}
// json.Unmarshal(bytes, dict)
// md := util.MarkdownTableWithTitle("行业/板块代码", dict)
// logger.SugaredLogger.Infof("行业/板块代码=\n%s", md)
// 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": md,
// "tool_call_id": currentCallId,
// })
//}
//if funcName == "GetIndustryResearchReport" {
// bkCode := gjson.Get(funcArguments, "bkCode").String()
// ch <- map[string]any{
// "code": 1,
// "question": question,
// "chatId": streamResponse.Id,
// "model": streamResponse.Model,
// "content": "\r\n```\r\n开始调用工具GetIndustryResearchReport\n参数" + bkCode + "\r\n```\r\n",
// "time": time.Now().Format(time.DateTime),
// }
// bkCode = strutil.ReplaceWithMap(bkCode, map[string]string{
// "-": "",
// "_": "",
// "bk": "",
// "BK": "",
// "bk0": "",
// "BK0": "",
// })
//
// logger.SugaredLogger.Debugf("code:%s", bkCode)
// codeStr := convertor.ToString(bkCode)
// res := NewMarketNewsApi().IndustryResearchReport(codeStr, 7)
// md := strings.Builder{}
// for _, a := range res {
// d := a.(map[string]any)
// md.WriteString(NewMarketNewsApi().GetIndustryReportInfo(d["infoCode"].(string)))
// }
// logger.SugaredLogger.Infof("bkCode:%s IndustryResearchReport:\n %s", bkCode, md.String())
// 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": md.String(),
// "tool_call_id": currentCallId,
// })
//}
if funcName == "GetStockResearchReport" {
stockCode := gjson.Get(funcArguments, "stockCode").String()
ch <- map[string]any{
"code": 1,
"question": question,
"chatId": streamResponse.Id,
"model": streamResponse.Model,
"content": "\r\n```\r\n开始调用工具GetStockResearchReport\n参数" + stockCode + "\r\n```\r\n",
"time": time.Now().Format(time.DateTime),
}
res := NewMarketNewsApi().StockResearchReport(stockCode, 7)
md := strings.Builder{}
for _, a := range res {
logger.SugaredLogger.Debugf("value: %+v", a)
d := a.(map[string]any)
logger.SugaredLogger.Debugf("value: %s infoCode:%s", d["title"], d["infoCode"])
md.WriteString(NewMarketNewsApi().GetIndustryReportInfo(d["infoCode"].(string)))
}
logger.SugaredLogger.Infof("stockCode:%s StockResearchReport:\n %s", stockCode, md.String())
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": md.String(),
"tool_call_id": currentCallId,
})
}
}
AskAiWithTools(o, err, messages, ch, question, tools)
}
@@ -1482,7 +1705,7 @@ func GetTelegraphList(crawlTimeOut int64) *[]string {
response, err := resty.New().SetTimeout(time.Duration(crawlTimeOut)*time.Second).R().
SetHeader("Referer", "https://www.cls.cn/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
Get(fmt.Sprintf(url))
Get(url)
if err != nil {
return &[]string{}
}
@@ -1504,7 +1727,7 @@ func GetTopNewsList(crawlTimeOut int64) *[]string {
response, err := resty.New().SetTimeout(time.Duration(crawlTimeOut)*time.Second).R().
SetHeader("Referer", "https://www.cls.cn/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
Get(fmt.Sprintf(url))
Get(url)
if err != nil {
return &[]string{}
}

View File

@@ -3,6 +3,7 @@ package data
import (
"context"
"go-stock/backend/db"
log "go-stock/backend/logger"
"testing"
)
@@ -52,8 +53,17 @@ func TestGetTopNewsList(t *testing.T) {
func TestSearchGuShiTongStockInfo(t *testing.T) {
db.Init("../../data/stock.db")
SearchGuShiTongStockInfo("hk01810", 60)
SearchGuShiTongStockInfo("sh600745", 60)
SearchGuShiTongStockInfo("gb_goog", 60)
//SearchGuShiTongStockInfo("hk01810", 60)
msgs := SearchGuShiTongStockInfo("sh600745", 60)
for _, msg := range *msgs {
log.SugaredLogger.Infof("%s", msg)
}
//SearchGuShiTongStockInfo("gb_goog", 60)
}
func TestGetZSInfo(t *testing.T) {
db.Init("../../data/stock.db")
GetZSInfo("中证银行", "sz399986", 30)
GetZSInfo("上海贝岭", "sh600171", 30)
}

View File

@@ -3,9 +3,10 @@ package data
import (
"encoding/json"
"fmt"
"github.com/go-resty/resty/v2"
"go-stock/backend/logger"
"time"
"github.com/go-resty/resty/v2"
)
// @Author spark
@@ -25,25 +26,25 @@ func (s SearchStockApi) SearchStock(pageSize int) map[string]any {
SetHeader("Host", "np-tjxg-g.eastmoney.com").
SetHeader("Origin", "https://xuangu.eastmoney.com").
SetHeader("Referer", "https://xuangu.eastmoney.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0").
SetHeader("Content-Type", "application/json").
SetBody(fmt.Sprintf(`{
"keyWord": "%s",
"pageSize": %d,
"pageNo": 1,
"fingerprint": "e38b5faabf9378c8238e57219f0ebc9b",
"fingerprint": "02efa8944b1f90fbfe050e1e695a480d",
"gids": [],
"matchWord": "",
"timestamp": "1751113883290349",
"timestamp": "%d",
"shareToGuba": false,
"requestId": "8xTWgCDAjvQ5lmvz5mDA3Ydk2AE4yoiJ1751113883290",
"requestId": "RMd3Y76AJI98axPvdhdbKvbBDVwLlUK61761559950168",
"needCorrect": true,
"removedConditionIdList": [],
"xcId": "xc0af28549ab330013ed",
"xcId": "xc0d61279aad33008260",
"ownSelectAll": false,
"dxInfo": [],
"extraCondition": ""
}`, s.words, pageSize)).Post(url)
}`, s.words, pageSize, time.Now().Unix())).Post(url)
if err != nil {
logger.SugaredLogger.Errorf("SearchStock-err:%+v", err)
return map[string]any{}

View File

@@ -2,16 +2,18 @@ package data
import (
"encoding/json"
"github.com/duke-git/lancet/v2/convertor"
"go-stock/backend/db"
"go-stock/backend/logger"
"testing"
"github.com/duke-git/lancet/v2/convertor"
)
func TestSearchStock(t *testing.T) {
db.Init("../../data/stock.db")
res := NewSearchStockApi("算力股;净利润连续3年增长").SearchStock(10)
res := NewSearchStockApi("量比大于2基本面优秀2025年三季报已披露主力连续3日净流入非创业板非科创板非ST").SearchStock(20)
logger.SugaredLogger.Infof("res:%+v", res)
data := res["data"].(map[string]any)
result := data["result"].(map[string]any)
dataList := result["dataList"].([]any)

View File

@@ -3,11 +3,12 @@ package data
import (
"encoding/json"
"errors"
"github.com/samber/lo"
"go-stock/backend/db"
"go-stock/backend/logger"
"gorm.io/gorm"
"time"
"github.com/samber/lo"
"gorm.io/gorm"
)
type Settings struct {
@@ -31,9 +32,11 @@ 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"`
EnableAgent bool `json:"enableAgent"`
}
func (receiver Settings) TableName() string {
@@ -100,9 +103,11 @@ 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,
"enable_agent": s.EnableAgent,
})
//更新AiConfig

View File

@@ -378,6 +378,9 @@ func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]
logger.SugaredLogger.Error(err.Error())
continue
}
if stockData == nil {
continue
}
stockInfos = append(stockInfos, *stockData)
go func() {
@@ -416,6 +419,15 @@ func (receiver StockDataApi) Follow(stockCode string) string {
}
stockCode = strings.ToLower(stockCode)
// 检查是否已经关注过该股票
var existingStock FollowedStock
result := db.Dao.Model(&FollowedStock{}).Where("stock_code = ? AND is_del = ?", stockCode, 0).First(&existingStock)
if result.Error == nil {
// 股票已经关注过
return "已经关注了"
}
maxSort := int64(0)
db.Dao.Model(&FollowedStock{}).Raw("select max(sort) as sort from followed_stock").Scan(&maxSort)
@@ -1165,7 +1177,7 @@ func getHKStockPriceInfo(stockCode string, crawlTimeOut int64) *[]string {
return &messages
}
func getZSInfo(name, stockCode string, crawlTimeOut int64) string {
func GetZSInfo(name, stockCode string, crawlTimeOut int64) string {
url := "https://finance.sina.com.cn/realstock/company/" + stockCode + "/nc.shtml"
crawlerAPI := CrawlerApi{}
crawlerBaseInfo := CrawlerBaseInfo{
@@ -1190,6 +1202,10 @@ func getZSInfo(name, stockCode string, crawlTimeOut int64) string {
price := strutil.RemoveWhiteSpace(document.Find("div#price").First().Text(), false)
hqTime := strutil.RemoveWhiteSpace(document.Find("div#hqTime").First().Text(), false)
if strutil.ContainsAny(price, []string{"-", "--", ""}) {
return "暂无数据"
}
var markdown strings.Builder
markdown.WriteString(fmt.Sprintf("### 时间:%s %s%s \n", hqTime, name, price))
GetTableMarkdown(document, "div#hqDetails table", &markdown)

View File

@@ -4,17 +4,19 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/random"
"github.com/duke-git/lancet/v2/strutil"
"github.com/go-resty/resty/v2"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/util"
"io/ioutil"
"regexp"
"strings"
"testing"
"time"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/random"
"github.com/duke-git/lancet/v2/strutil"
"github.com/go-resty/resty/v2"
)
// @Author spark
@@ -48,14 +50,23 @@ func TestGetFinancialReports(t *testing.T) {
func TestGetTelegraphSearch(t *testing.T) {
db.Init("../../data/stock.db")
searchWords := "半导体 新能源汽车 机器人"
//url := "https://www.cls.cn/searchPage?keyword=%E9%97%BB%E6%B3%B0%E7%A7%91%E6%8A%80&type=telegram"
messages := SearchStockInfo("谷歌", "telegram", 30)
messages := SearchStockInfo(searchWords, "telegram", 30)
for _, message := range *messages {
logger.SugaredLogger.Info(message)
}
//https://www.cls.cn/stock?code=sh600745
}
func TestCailianpressWeb(t *testing.T) {
db.Init("../../data/stock.db")
searchWords := "半导体 新能源汽车 机器人"
res := NewMarketNewsApi().CailianpressWeb(searchWords)
md := util.MarkdownTableWithTitle(searchWords+"财联社新闻", res.List)
logger.SugaredLogger.Info(md)
}
func TestSearchStockInfoByCode(t *testing.T) {
db.Init("../../data/stock.db")
SearchStockInfoByCode("sh600745")
@@ -63,7 +74,7 @@ func TestSearchStockInfoByCode(t *testing.T) {
func TestSearchStockPriceInfo(t *testing.T) {
db.Init("../../data/stock.db")
//SearchStockPriceInfo("中信证券", "hk06030", 30)
SearchStockPriceInfo("博安生物", "hk06955", 30)
SearchStockPriceInfo("上海贝岭", "sh600171", 30)
//SearchStockPriceInfo("苹果公司", "gb_aapl", 30)
//SearchStockPriceInfo("微创光电", "bj430198", 30)

View File

@@ -39,17 +39,74 @@ func NewStockGroupApi(dao *gorm.DB) *StockGroupApi {
}
func (receiver StockGroupApi) AddGroup(group Group) bool {
err := receiver.dao.Where("name = ?", group.Name).FirstOrCreate(&group).Updates(&Group{
Name: group.Name,
Sort: group.Sort,
}).Error
// 检查是否已存在相同sort的组
var existingGroup Group
err := receiver.dao.Where("sort = ?", group.Sort).First(&existingGroup).Error
// 如果存在相同sort的组则将该组及之后的所有组向后移动一位
if err == nil {
// 处理sort冲突将相同sort值及之后的所有组向后移动一位
receiver.dao.Model(&Group{}).Where("sort >= ?", group.Sort).Update("sort", gorm.Expr("sort + ?", 1))
}
// 创建新组
err = receiver.dao.Create(&group).Error
return err == nil
}
func (receiver StockGroupApi) GetGroupList() []Group {
var groups []Group
receiver.dao.Find(&groups)
receiver.dao.Order("sort ASC").Find(&groups)
return groups
}
func (receiver StockGroupApi) UpdateGroupSort(id int, newSort int) bool {
// First, get the current group to check if it exists
var currentGroup Group
if err := receiver.dao.First(&currentGroup, id).Error; err != nil {
return false
}
// If the new sort is the same as current, no need to update
if currentGroup.Sort == newSort {
return true
}
// Get all groups ordered by sort
var allGroups []Group
receiver.dao.Order("sort ASC").Find(&allGroups)
// Adjust sort numbers to make space for the new sort value
if newSort > currentGroup.Sort {
// Moving down: decrease sort of groups between old and new position
receiver.dao.Model(&Group{}).Where("sort > ? AND sort <= ? AND id != ?", currentGroup.Sort, newSort, id).Update("sort", gorm.Expr("sort - ?", 1))
} else {
// Moving up: increase sort of groups between new and old position
receiver.dao.Model(&Group{}).Where("sort >= ? AND sort < ? AND id != ?", newSort, currentGroup.Sort, id).Update("sort", gorm.Expr("sort + ?", 1))
}
// Update the target group's sort
err := receiver.dao.Model(&Group{}).Where("id = ?", id).Update("sort", newSort).Error
return err == nil
}
// InitializeGroupSort initializes sort order for all groups based on created time
func (receiver StockGroupApi) InitializeGroupSort() bool {
// Get all groups ordered by created time
var groups []Group
err := receiver.dao.Order("created_at ASC").Find(&groups).Error
if err != nil {
return false
}
// Update each group with new sort value based on their position
for i, group := range groups {
newSort := i + 1
err := receiver.dao.Model(&Group{}).Where("id = ?", group.ID).Update("sort", newSort).Error
if err != nil {
return false
}
}
return true
}
func (receiver StockGroupApi) GetGroupStockByGroupId(groupId int) []GroupStock {
var stockGroup []GroupStock
receiver.dao.Preload("GroupInfo").Where("group_id = ?", groupId).Find(&stockGroup)

View File

@@ -3,10 +3,11 @@ package data
import (
"bufio"
"fmt"
"github.com/go-ego/gse"
"go-stock/backend/logger"
"os"
"strings"
"github.com/go-ego/gse"
)
// 金融情感词典,包含股票市场相关的专业词汇

View File

@@ -1,9 +1,10 @@
package models
import (
"time"
"gorm.io/gorm"
"gorm.io/plugin/soft_delete"
"time"
)
// @Author spark
@@ -635,3 +636,68 @@ 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:"-"`
}
type CailianpressWeb struct {
Total int `json:"total"`
List []struct {
Title string `json:"title" md:"资讯标题"`
Ctime int `json:"ctime" md:"资讯时间"`
Content string `json:"content" md:"资讯内容"`
Author string `json:"author" md:"资讯发布者"`
} `json:"list"`
}
type BKDict struct {
gorm.Model `md:"-"`
BkCode string `json:"bkCode" md:"行业/板块代码"`
BkName string `json:"bkName" md:"行业/板块名称"`
FirstLetter string `json:"firstLetter" md:"first_letter"`
FubkCode string `json:"fubkCode" md:"fubk_code"`
PublishCode string `json:"publishCode" md:"publish_code"`
}
func (b BKDict) TableName() string {
return "bk_dict"
}

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())))
}
// 示例结构体

10
frontend/auto-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
}

44
frontend/components.d.ts vendored Normal file
View File

@@ -0,0 +1,44 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
About: typeof import('./src/components/about.vue')['default']
AgentChat: typeof import('./src/components/agent-chat.vue')['default']
AgentChat_bk: typeof import('./src/components/agent-chat_bk.vue')['default']
ClsCalendarTimeLine: typeof import('./src/components/ClsCalendarTimeLine.vue')['default']
EmbeddedUrl: typeof import('./src/components/EmbeddedUrl.vue')['default']
Fund: typeof import('./src/components/fund.vue')['default']
HotEvents: typeof import('./src/components/HotEvents.vue')['default']
HotStockList: typeof import('./src/components/HotStockList.vue')['default']
HotTopics: typeof import('./src/components/HotTopics.vue')['default']
IndustryMoneyRank: typeof import('./src/components/industryMoneyRank.vue')['default']
IndustryResearchReportList: typeof import('./src/components/IndustryResearchReportList.vue')['default']
InvestCalendarTimeLine: typeof import('./src/components/InvestCalendarTimeLine.vue')['default']
KLineChart: typeof import('./src/components/KLineChart.vue')['default']
LongTigerRankList: typeof import('./src/components/LongTigerRankList.vue')['default']
Market: typeof import('./src/components/market.vue')['default']
MoneyTrend: typeof import('./src/components/moneyTrend.vue')['default']
NewsList: typeof import('./src/components/newsList.vue')['default']
RankTable: typeof import('./src/components/rankTable.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SelectStock: typeof import('./src/components/SelectStock.vue')['default']
Settings: typeof import('./src/components/settings.vue')['default']
Stock: typeof import('./src/components/stock.vue')['default']
Stockhotmap: typeof import('./src/components/stockhotmap.vue')['default']
StockNoticeList: typeof import('./src/components/StockNoticeList.vue')['default']
StockResearchReportList: typeof import('./src/components/StockResearchReportList.vue')['default']
StockSparkLine: typeof import('./src/components/stockSparkLine.vue')['default']
TChat: typeof import('@tdesign-vue-next/chat')['Chat']
TChatAction: typeof import('@tdesign-vue-next/chat')['ChatAction']
TChatContent: typeof import('@tdesign-vue-next/chat')['ChatContent']
TChatLoading: typeof import('@tdesign-vue-next/chat')['ChatLoading']
TChatSender: typeof import('@tdesign-vue-next/chat')['ChatSender']
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@tdesign-vue-next/chat": "^0.4.5",
"@types/file-saver": "^2.0.7",
"@vavt/cm-extension": "^1.8.0",
"@vavt/v3-extension": "^3.0.0",
@@ -18,11 +19,13 @@
"html2canvas": "^1.4.1",
"lodash": "^4.17.21",
"md-editor-v3": "^5.2.3",
"tdesign-icons-vue-next": "^0.3.7",
"vue": "^3.5.17",
"vue-router": "^4.5.0",
"vue3-danmaku": "^1.6.1"
},
"devDependencies": {
"@tdesign-vue-next/auto-import-resolver": "^0.1.1",
"@vicons/antd": "^0.13.0",
"@vicons/carbon": "^0.13.0",
"@vicons/fa": "^0.13.0",
@@ -33,7 +36,10 @@
"@vicons/tabler": "^0.13.0",
"@vitejs/plugin-vue": "^5.2.1",
"html-docx-js-typescript": "^0.1.5",
"less": "^4.4.0",
"naive-ui": "^2.41.0",
"unplugin-auto-import": "^20.0.0",
"unplugin-vue-components": "^29.0.0",
"vfonts": "^0.0.3",
"vite": "^6.3.5"
},

View File

@@ -1 +1 @@
4be2da172610a6498067f3ec99698918
b0b9f944d9af9c00b6d48234793db58c

View File

@@ -6,7 +6,8 @@ import {
Quit,
WindowFullscreen,
WindowHide,
WindowUnfullscreen
WindowUnfullscreen,
WindowSetTitle
} from '../wailsjs/runtime'
import {h, onBeforeMount, onBeforeUnmount, onMounted, ref} from "vue";
import {RouterLink, useRouter} from 'vue-router'
@@ -28,7 +29,7 @@ import {
Wallet, WarningOutline,
} from '@vicons/ionicons5'
import {AnalyzeSentiment, GetConfig, GetGroupList,GetVersionInfo} from "../wailsjs/go/main/App";
import {Dragon, Fire, Gripfire} from "@vicons/fa";
import {Dragon, Fire, FirefoxBrowser, Gripfire, Robot} from "@vicons/fa";
import {ReportSearch} from "@vicons/tabler";
import {LocalFireDepartmentRound} from "@vicons/material";
import {BoxSearch20Regular, CommentNote20Filled} from "@vicons/fluent";
@@ -43,6 +44,7 @@ const loadingMsg = ref("加载数据中...")
const enableNews = ref(false)
const contentStyle = ref("")
const enableFund = ref(false)
const enableAgent = ref(false)
const enableDarkTheme = ref(null)
const content = ref('未经授权,禁止商业目的!\n\n数据来源于网络,仅供参考;投资有风险,入市需谨慎')
const isFullscreen = ref(false)
@@ -369,6 +371,28 @@ const menuOptions = ref([
key: 'market11',
icon: renderIcon(BoxSearch20Regular),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "名站优选",
}
},
onClick: () => {
activeKey.value = 'market'
EventsEmit("changeMarketTab", {ID: 0, name: '名站优选'})
},
},
{default: () => '名站优选',}
),
key: 'market12',
icon: renderIcon(FirefoxBrowser),
},
]
},
{
@@ -400,6 +424,27 @@ const menuOptions = ref([
},
]
},
{
label: () =>
h(
RouterLink,
{
to: {
name: 'agent',
query: {
name:"Ai智能体",
},
onClick: () => {
activeKey.value = 'agent'
},
}
},
{default: () => 'Ai智能体'}
),
key: 'agent',
show:enableAgent.value,
icon: renderIcon(Robot),
},
{
label: () =>
h(
@@ -441,6 +486,7 @@ const menuOptions = ref([
icon: renderIcon(LogoGithub),
},
{
show:false,
label: () => h("a", {
href: '#',
onClick: toggleFullscreen,
@@ -603,11 +649,15 @@ onBeforeMount(() => {
GetConfig().then((res) => {
//console.log(res)
enableFund.value = res.enableFund
enableAgent.value = res.enableAgent
menuOptions.value.filter((item) => {
if (item.key === 'fund') {
item.show = res.enableFund
}
if (item.key === 'agent') {
item.show = res.enableAgent
}
})
if (res.darkTheme) {
@@ -619,12 +669,14 @@ onBeforeMount(() => {
})
onMounted(() => {
WindowSetTitle("go-stockAI赋能股票分析✨ 未经授权,禁止商业目的! [数据来源于网络,仅供参考;投资有风险,入市需谨慎]")
contentStyle.value = "max-height: calc(92vh);overflow: hidden"
GetConfig().then((res) => {
if (res.enableNews) {
enableNews.value = true
}
enableFund.value = res.enableFund
enableAgent.value = res.enableAgent
const {notification } =createDiscreteApi(["notification"], {
configProviderProps: {
theme: enableDarkTheme.value ? darkTheme : lightTheme ,
@@ -671,7 +723,7 @@ onMounted(() => {
<n-modal-provider>
<n-dialog-provider>
<n-watermark
:content="content"
:content="''"
cross
selectable
:font-size="16"

View File

@@ -1,9 +1,10 @@
<script setup lang="ts">
import {h, onBeforeMount, onMounted, onUnmounted, ref} from 'vue'
import {SearchStock, GetHotStrategy, OpenURL} from "../../wailsjs/go/main/App";
import {SearchStock, GetHotStrategy, OpenURL, Follow, GetFollowList} from "../../wailsjs/go/main/App";
import {useMessage, NText, NTag, NButton} from 'naive-ui'
import {Environment} from "../../wailsjs/runtime"
import {RefreshCircleSharp} from "@vicons/ionicons5";
import {EventsEmit} from "../../wailsjs/runtime";
const message = useMessage()
const search = ref('')
@@ -11,6 +12,32 @@ const columns = ref([])
const dataList = ref([])
const hotStrategy = ref([])
const traceInfo = ref('')
const tableScrollX = ref(2800) // 默认滚动宽度
// 计算表格总宽度
function calculateTableWidth(cols) {
let totalWidth = 0;
cols.forEach(col => {
if (col.children && col.children.length > 0) {
// 有子列的情况
let childrenWidth = 0;
col.children.forEach(child => {
childrenWidth += child.width || child.minWidth || 100;
});
// 取标题列宽度和子列总宽度的较大值
totalWidth += Math.max(col.width || col.minWidth || 200, childrenWidth);
} else {
// 没有子列的情况
totalWidth += col.width || col.minWidth || 120;
}
});
// 加上操作列的宽度
totalWidth += 100;
return Math.max(totalWidth, 1200); // 最小宽度1200
}
function Search() {
if (!search.value) {
@@ -71,11 +98,32 @@ function Search() {
}
},
}
}
})
columns.value.push({
title: '操作',
key: 'actions',
width: 80,
fixed: 'right', // 固定在右侧
render: (row) => {
return h(
NButton,
{
strong: true,
tertiary: true,
size: 'small',
type: 'warning', // 橙色按钮
style: 'font-size: 14px; padding: 0 10px;', // 稍微大一点的按钮
onClick: () => handleFollow(row)
},
{ default: () => '关注' }
)
}
});
dataList.value = res.data.result.dataList
console.log("sss"+columns.value. length)
// 计算并设置表格宽度
tableScrollX.value = calculateTableWidth(columns.value);
} else {
message.error(res.msg)
}
@@ -84,6 +132,18 @@ function Search() {
})
}
// 修改handleFollow方法使用stock.vue的AddStock逻辑
function handleFollow(row) {
let code=row.MARKET_SHORT_NAME.toLowerCase()+row.SECURITY_CODE
Follow(code).then(result => {
if (result === "关注成功") {
message.success(result)
} else {
message.error(result)
}
});
}
function isNumeric(value) {
return !isNaN(parseFloat(value)) && isFinite(value);
}
@@ -188,7 +248,7 @@ function openCenteredWindow(url, width, height) {
:columns="columns"
:data="dataList"
:pagination="{pageSize: 10}"
:scroll-x="1800"
:scroll-x="tableScrollX"
:render-cell="(value, rowData, column) => {
if(column.key=='SECURITY_CODE'||column.key=='SERIAL'){

View File

@@ -5,7 +5,9 @@ import 'md-editor-v3/lib/preview.css';
import {h, onBeforeUnmount, onMounted, ref} from 'vue';
import {CheckUpdate, GetVersionInfo,GetSponsorInfo,OpenURL} from "../../wailsjs/go/main/App";
import {EventsOff, EventsOn,Environment} from "../../wailsjs/runtime";
import {NAvatar, NButton, useNotification} from "naive-ui";
import {NAvatar, NButton, useNotification,NText} from "naive-ui";
import { addMonths, format ,parse} from 'date-fns';
import { zhCN } from 'date-fns/locale';
const updateLog = ref('');
const versionInfo = ref('');
const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png');
@@ -16,6 +18,7 @@ const notify = useNotification()
const vipLevel=ref("");
const vipStartTime=ref("");
const vipEndTime=ref("");
const expired=ref(false)
onMounted(() => {
document.title = '关于软件';
@@ -31,6 +34,13 @@ onMounted(() => {
vipLevel.value = res.vipLevel;
vipStartTime.value = res.vipStartTime;
vipEndTime.value = res.vipEndTime;
//判断时间是否到期
if (res.vipLevel) {
if (res.vipEndTime < format(new Date(), 'yyyy-MM-dd HH:mm:ss')) {
notify.warning({content: 'VIP已到期'})
expired.value = true;
}
}
})
});
@@ -115,10 +125,10 @@ EventsOn("updateVersion",async (msg) => {
<n-gradient-text type="info" :size="50" >go-stock</n-gradient-text>
</n-badge>
<n-badge v-if="vipLevel" :value="versionInfo" :offset="[50,10]" type="success">
<n-gradient-text type="warning" :size="50" >go-stock</n-gradient-text><n-tag :bordered="false" size="small" type="warning">VIP{{vipLevel}}</n-tag>
<n-gradient-text :type="expired?'error':'warning'" :size="50" >go-stock</n-gradient-text><n-tag :bordered="false" size="small" type="warning">VIP{{vipLevel}}</n-tag>
</n-badge>
</h1>
<n-gradient-text type="warning" v-if="vipLevel" >vip到期时间{{vipEndTime}}</n-gradient-text>
<n-gradient-text :type="expired?'error':'warning'" v-if="vipLevel" >vip到期时间{{vipEndTime}}</n-gradient-text>
<n-button size="tiny" @click="CheckUpdate(1)" type="info" tertiary >检查更新</n-button>
<div style="justify-self: center;text-align: left" >
<p>自选股行情实时监控基于Wails和NaiveUI构建的AI赋能股票分析工具</p>

View File

@@ -0,0 +1,365 @@
<template>
<div class="chat-box">
<t-chat
ref="chatRef"
:clear-history="chatList.length > 0 && !isStreamLoad"
:data="chatList"
:text-loading="loading"
:is-stream-load="isStreamLoad"
style="height: 100%"
@scroll="handleChatScroll"
@clear="clearConfirm"
>
<!-- eslint-disable vue/no-unused-vars -->
<template #content="{ item, index }">
<t-chat-reasoning v-if="item.role === 'assistant'" expand-icon-placement="right">
<t-chat-loading v-if="isStreamLoad" text="思考中..." />
<t-chat-content v-if="item.reasoning.length > 0" :content="item.reasoning" />
</t-chat-reasoning>
<t-chat-content v-if="item.content.length > 0" :content="item.content" />
</template>
<template #actions="{ item, index }">
<t-chat-action
:content="item.content"
:operation-btn="['copy']"
@operation="handleOperation"
/>
</template>
<template #footer>
<!-- <t-chat-input :stop-disabled="isStreamLoad" @send="inputEnter" @stop="onStop"> </t-chat-input>-->
<t-chat-sender
ref="chatSenderRef"
v-model="inputValue"
class="chat-sender"
:textarea-props="{
placeholder: '请输入消息...',
}"
:loading="loading"
:stop-disabled="isStreamLoad"
@send="inputEnter"
@stop="onStop"
>
<template #suffix>
<!-- 监听键盘回车发送事件需要在sender组件监听 -->
<t-button theme="default" variant="text" size="large" class="btn" @click="inputEnter"> 发送 </t-button>
</template>
<template #prefix>
<NFlex>
<NSelect
v-model:value="selectValue"
:options="selectOptions"
label-field="name" value-field="ID"
size="tiny"
style="width: 200px;"
/>
</NFlex>
</template>
</t-chat-sender>
</template>
</t-chat>
<t-button v-show="isShowToBottom" variant="text" class="bottomBtn" @click="backBottom">
<div class="to-bottom">
<ArrowDownIcon />
</div>
</t-button>
</div>
</template>
<script setup lang="ts">
import {ref, onMounted, h, onBeforeUnmount, onBeforeMount} from 'vue';
import {ArrowDownIcon, CheckCircleIcon, SystemSumIcon} from 'tdesign-icons-vue-next';
const fetchCancel = ref(null);
const loading = ref(false);
const inputValue = ref('');
// 流式数据加载中
const isStreamLoad = ref(false);
const chatRef = ref(null);
const isShowToBottom = ref(false);
const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png');
import {darkTheme, NFlex, NImage,NSelect} from "naive-ui";
import {ChatWithAgent, GetAiConfigs, GetConfig, GetSponsorInfo, GetVersionInfo} from "../../wailsjs/go/main/App";
import {EventsOff, EventsOn} from '../../wailsjs/runtime'
import 'tdesign-vue-next/es/style/index.css';
const allowToolTip = ref(true);
const chatSenderRef = ref(null);
const selectOptions = ref([]);
const selectValue = ref("default");
onBeforeUnmount(() => {
EventsOff("agent-message")
})
EventsOn("agent-message", (data) => {
console.log(data)
if(data['role']==="assistant"){
loading.value = false;
const lastItem = chatList.value[0];
if (data['reasoning_content']){
lastItem.reasoning += data['reasoning_content'];
}
if (data['content']){
lastItem.content +=data['content'];
}
if(data['tool_calls']){
for (const tool of data['tool_calls']) {
console.log(tool.id, tool.type, tool.function.name, tool.function.arguments);
lastItem.reasoning += "\n```"+tool.function.name+"\n" +
"参数:"+ (tool.function.arguments?tool.function.arguments:"无")+
"\n```\n";
}
}
}
if(data['response_meta']&&data['response_meta'].finish_reason==="stop"){
isStreamLoad.value = false;
loading.value = false;
}
})
onBeforeMount(() => {
GetAiConfigs().then(res=>{
console.log(res)
selectOptions.value = res
selectValue.value = res[0].ID
})
})
onMounted(() => {
//chatRef.value.scrollToBottom();
GetConfig().then((res) => {
if (res.darkTheme) {
document.documentElement.setAttribute("theme-mode", "dark");
} else {
document.documentElement.removeAttribute("theme-mode"); }
})
GetVersionInfo().then((res) => {
icon.value = res.icon;
});
});
// 滚动到底部
const backBottom = () => {
chatRef.value.scrollToBottom({
behavior: 'smooth',
});
};
// 是否显示回到底部按钮
const handleChatScroll = function ({ e }) {
const scrollTop = e.target.scrollTop;
isShowToBottom.value = scrollTop < 0;
};
// 清空消息
const clearConfirm = function () {
chatList.value = [];
};
const handleOperation = function (type, options) {
console.log('handleOperation', type, options);
};
// 倒序渲染
const chatList = ref([
// {
// content: `模型由<span>hunyuan</span>变为<span>GPT4</span>`,
// role: 'model-change',
// reasoning: '',
// },
{
avatar: h(NImage, { src: icon.value, height: '48px', width: '48px'}),
name: 'Go-Stock AI',
datetime: '',
reasoning: '',
content: '我是您的AI赋能股票分析助手,您可以问我任何关于股票投资方面的问题。',
role: 'assistant',
duration: 10,
},
{
avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
name: '宇宙无敌大韭菜',
datetime: '',
content: '介绍下自己?',
role: 'user',
reasoning: '',
},
]);
const onStop = function () {
if (fetchCancel.value) {
fetchCancel.value.controller.close();
loading.value = false;
isStreamLoad.value = false;
}
};
const inputEnter = function () {
if (isStreamLoad.value) {
return;
}
if (!inputValue.value) return;
const params = {
avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
name: '宇宙无敌大韭菜',
datetime: new Date().toDateString(),
content: inputValue.value,
role: 'user',
};
chatList.value.unshift(params);
// 空消息占位
const params2 = {
avatar: h(NImage, { src: icon.value, height: '48px', width: '48px'}),
name: 'Go-Stock AI',
datetime: new Date().toDateString(),
content: '',
reasoning: '',
role: 'assistant',
};
chatList.value.unshift(params2);
loading.value = true;
isStreamLoad.value = true;
ChatWithAgent(inputValue.value,selectValue.value,0)
};
</script>
<style lang="less">
/* 应用滚动条样式 */
::-webkit-scrollbar-thumb {
background-color: var(--td-scrollbar-color);
}
::-webkit-scrollbar-thumb:horizontal:hover {
background-color: var(--td-scrollbar-hover-color);
}
::-webkit-scrollbar-track {
background-color: var(--td-scroll-track-color);
}
.chat-box {
position: relative;
height: 100%;
margin: 5px 10px 5px 10px;
text-align: left;
.bottomBtn {
position: absolute;
left: 50%;
margin-left: -20px;
bottom: 210px;
padding: 0;
border: 0;
width: 40px;
height: 40px;
border-radius: 50%;
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.08), 0px 16px 24px 2px rgba(0, 0, 0, 0.04),
0px 6px 30px 5px rgba(0, 0, 0, 0.05);
}
.to-bottom {
width: 40px;
height: 40px;
border: 1px solid #dcdcdc;
box-sizing: border-box;
background: var(--td-bg-color-container);
border-radius: 50%;
font-size: 24px;
line-height: 40px;
display: flex;
align-items: center;
justify-content: center;
.t-icon {
font-size: 24px;
}
}
}
.model-select {
display: flex;
align-items: center;
.t-select {
width: 112px;
height: 32px;
margin-right: 8px;
.t-input {
border-radius: 32px;
padding: 0 15px;
}
}
.check-box {
width: 112px;
height: 32px;
border-radius: 32px;
border: 0;
background: #e7e7e7;
color: rgba(0, 0, 0, 0.9);
box-sizing: border-box;
flex: 0 0 auto;
.t-button__text {
display: flex;
align-items: center;
justify-content: center;
span {
margin-left: 4px;
}
}
}
.check-box.is-active {
border: 1px solid #d9e1ff;
background: #f2f3ff;
color: var(--td-brand-color);
}
}
.chat-sender {
.btn {
color: var(--td-text-color-disabled);
border: none;
&:hover {
color: var(--td-brand-color-hover);
border: none;
background: none;
}
}
.btn.t-button {
height: var(--td-comp-size-m);
padding: 0;
}
.model-select {
display: flex;
align-items: center;
.t-select {
width: 112px;
height: var(--td-comp-size-m);
margin-right: var(--td-comp-margin-s);
.t-input {
border-radius: 32px;
padding: 0 15px;
}
.t-input.t-is-focused {
box-shadow: none;
}
}
.check-box {
width: 112px;
height: var(--td-comp-size-m);
border-radius: 32px;
border: 0;
background: var(--td-bg-color-component);
color: var(--td-text-color-primary);
box-sizing: border-box;
flex: 0 0 auto;
.t-button__text {
display: flex;
align-items: center;
justify-content: center;
span {
margin-left: var(--td-comp-margin-xs);
}
}
}
.check-box.is-active {
border: 1px solid var(--td-brand-color-focus);
background: var(--td-brand-color-light);
color: var(--td-text-color-brand);
}
}
}
</style>

View File

@@ -0,0 +1,338 @@
<template>
<div class="chat-box">
<t-chat
ref="chatRef"
:clear-history="chatList.length > 0 && !isStreamLoad"
:data="chatList"
:text-loading="loading"
:is-stream-load="isStreamLoad"
style="height: 100%"
@scroll="handleChatScroll"
@clear="clearConfirm"
>
<!-- eslint-disable vue/no-unused-vars -->
<template #content="{ item, index }">
<t-chat-reasoning v-if="item.reasoning?.length > 0" expand-icon-placement="right">
<template #header>
<t-chat-loading v-if="isStreamLoad && item.content.length === 0" text="思考中..." />
<div v-else style="display: flex; align-items: center">
<CheckCircleIcon style="color: var(--td-success-color-5); font-size: 20px; margin-right: 8px" />
<span>已深度思考</span>
</div>
</template>
<t-chat-content v-if="item.reasoning.length > 0" :content="item.reasoning" />
</t-chat-reasoning>
<t-chat-content v-if="item.content.length > 0" :content="item.content" />
</template>
<template #actions="{ item, index }">
<t-chat-action
:content="item.content"
:operation-btn="['good', 'bad', 'replay', 'copy']"
@operation="handleOperation"
/>
</template>
<template #footer>
<t-chat-input :stop-disabled="isStreamLoad" @send="inputEnter" @stop="onStop"> </t-chat-input>
</template>
</t-chat>
<t-button v-show="isShowToBottom" variant="text" class="bottomBtn" @click="backBottom">
<div class="to-bottom">
<ArrowDownIcon />
</div>
</t-button>
</div>
</template>
<script setup lang="jsx">
import {ref, onMounted, h, onBeforeUnmount} from 'vue';
import { MockSSEResponse } from '../mock-data/index';
import { ArrowDownIcon, CheckCircleIcon } from 'tdesign-icons-vue-next';
const fetchCancel = ref(null);
const loading = ref(false);
// 流式数据加载中
const isStreamLoad = ref(false);
const chatRef = ref(null);
const isShowToBottom = ref(false);
const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png');
import {darkTheme, NAvatar, NImage} from "naive-ui";
import {ChatWithAgent, GetConfig, GetSponsorInfo, GetVersionInfo} from "../../wailsjs/go/main/App";
import {EventsOff, EventsOn} from '../../wailsjs/runtime'
import 'tdesign-vue-next/es/style/index.css';
onBeforeUnmount(() => {
EventsOff("agent-message")
})
EventsOn("agent-message", (data) => {
console.log(data)
if(data['role']==="assistant"){
loading.value = false;
isStreamLoad.value = true;
const lastItem = chatList.value[0];
if (data['reasoning_content']){
lastItem.reasoning += data['reasoning_content'];
}
if (data['content']){
lastItem.content +=data['content'];
}
if(data['response_meta'].finish_reason==="stop"){
isStreamLoad.value = false;
}
if(data['tool_calls']){
lastItem.tool_calls = data['tool_calls'];
}
}
})
onMounted(() => {
//chatRef.value.scrollToBottom();
GetConfig().then((res) => {
if (res.darkTheme) {
document.documentElement.setAttribute("theme-mode", "dark");
} else {
document.documentElement.removeAttribute("theme-mode"); }
})
GetVersionInfo().then((res) => {
icon.value = res.icon;
});
});
// 滚动到底部
const backBottom = () => {
chatRef.value.scrollToBottom({
behavior: 'smooth',
});
};
// 是否显示回到底部按钮
const handleChatScroll = function ({ e }) {
const scrollTop = e.target.scrollTop;
isShowToBottom.value = scrollTop < 0;
};
// 清空消息
const clearConfirm = function () {
chatList.value = [];
};
const handleOperation = function (type, options) {
console.log('handleOperation', type, options);
};
// 倒序渲染
const chatList = ref([
// {
// content: `模型由<span>hunyuan</span>变为<span>GPT4</span>`,
// role: 'model-change',
// reasoning: '',
// },
{
avatar: h(NImage, { src: icon.value, height: '48px', width: '48px'}),
name: 'Go-Stock AI',
datetime: '',
reasoning: '',
content: '我是您的AI赋能股票分析助手,您可以问我任何关于股票投资方面的问题。',
role: 'assistant',
duration: 10,
},
{
avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
name: '宇宙无敌大韭菜',
datetime: '',
content: '介绍下自己?',
role: 'user',
reasoning: '',
},
]);
const onStop = function () {
if (fetchCancel.value) {
fetchCancel.value.controller.close();
loading.value = false;
isStreamLoad.value = false;
}
};
const inputEnter = function (inputValue) {
if (isStreamLoad.value) {
return;
}
if (!inputValue) return;
const params = {
avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
name: '宇宙无敌大韭菜',
datetime: new Date().toDateString(),
content: inputValue,
role: 'user',
};
chatList.value.unshift(params);
// 空消息占位
const params2 = {
avatar: h(NImage, { src: icon.value, height: '48px', width: '48px'}),
name: 'Go-Stock AI',
datetime: new Date().toDateString(),
content: '',
reasoning: '',
role: 'assistant',
};
chatList.value.unshift(params2);
handleData(inputValue);
ChatWithAgent(inputValue,1,0)
};
const fetchSSE = async (fetchFn, options) => {
const response = await fetchFn();
const { success, fail, complete } = options;
// 如果不 ok 说明有请求错误
if (!response.ok) {
complete?.(false, response.statusText);
fail?.();
return;
}
const reader = response?.body?.getReader();
const decoder = new TextDecoder();
if (!reader) return;
reader.read().then(function processText({ done, value }) {
if (done) {
// 正常的返回
complete?.(true);
return;
}
const chunk = decoder.decode(value, { stream: true });
const buffers = chunk.toString().split(/\r?\n/);
const jsonData = JSON.parse(buffers);
success(jsonData);
reader.read().then(processText);
});
};
const handleData = async () => {
loading.value = true;
isStreamLoad.value = true;
const lastItem = chatList.value[0];
const mockedData = {
reasoning: `嗯,用户问牛顿第一定律是不是适用于所有参考系。首先,我得先回忆一下牛顿第一定律的内容。牛顿第一定律,也就是惯性定律,说物体在没有外力作用时会保持静止或匀速直线运动。也就是说,保持原来的运动状态。
那问题来了,这个定律是否适用于所有参考系呢?记得以前学过的参考系分惯性系和非惯性系。惯性系里,牛顿定律成立;非惯性系里,可能需要引入惯性力之类的修正。所以牛顿第一定律应该只在惯性参考系中成立,而在非惯性系中不适用,比如加速的电梯或者旋转的参考系,这时候物体会有看似无外力下的加速度,所以必须引入假想的力来解释。`,
content: `牛顿第一定律(惯性定律)**并不适用于所有参考系**,它只在**惯性参考系**中成立。以下是关键点:
---
### **1. 牛顿第一定律的核心**
- **内容**:物体在不受外力(或合力为零)时,将保持静止或匀速直线运动状态。
- **本质**:定义了惯性系的存在——即存在一类参考系,在其中惯性定律成立。`,
};
const mockResponse = new MockSSEResponse(mockedData);
fetchCancel.value = mockResponse;
await fetchSSE(
() => {
return mockResponse.getResponse();
},
{
success(result) {
console.log('success', result);
loading.value = false;
lastItem.reasoning += result.delta.reasoning_content;
lastItem.content += result.delta.content;
},
complete(isOk, msg) {
if (!isOk) {
lastItem.role = 'error';
lastItem.content = msg;
lastItem.reasoning = msg;
}
// 显示用时xx秒业务侧需要自行处理
lastItem.duration = 20;
// 控制终止按钮
isStreamLoad.value = false;
loading.value = false;
},
},
);
};
</script>
<style lang="less">
/* 应用滚动条样式 */
::-webkit-scrollbar-thumb {
background-color: var(--td-scrollbar-color);
}
::-webkit-scrollbar-thumb:horizontal:hover {
background-color: var(--td-scrollbar-hover-color);
}
::-webkit-scrollbar-track {
background-color: var(--td-scroll-track-color);
}
.chat-box {
position: relative;
height: 100%;
margin: 5px 10px 5px 10px;
.bottomBtn {
position: absolute;
left: 50%;
margin-left: -20px;
bottom: 210px;
padding: 0;
border: 0;
width: 40px;
height: 40px;
border-radius: 50%;
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.08), 0px 16px 24px 2px rgba(0, 0, 0, 0.04),
0px 6px 30px 5px rgba(0, 0, 0, 0.05);
}
.to-bottom {
width: 40px;
height: 40px;
border: 1px solid #dcdcdc;
box-sizing: border-box;
background: var(--td-bg-color-container);
border-radius: 50%;
font-size: 24px;
line-height: 40px;
display: flex;
align-items: center;
justify-content: center;
.t-icon {
font-size: 24px;
}
}
}
.model-select {
display: flex;
align-items: center;
.t-select {
width: 112px;
height: 32px;
margin-right: 8px;
.t-input {
border-radius: 32px;
padding: 0 15px;
}
}
.check-box {
width: 112px;
height: 32px;
border-radius: 32px;
border: 0;
background: #e7e7e7;
color: rgba(0, 0, 0, 0.9);
box-sizing: border-box;
flex: 0 0 auto;
.t-button__text {
display: flex;
align-items: center;
justify-content: center;
span {
margin-left: 4px;
}
}
}
.check-box.is-active {
border: 1px solid #d9e1ff;
background: #f2f3ff;
color: var(--td-brand-color);
}
}
</style>

View File

@@ -41,9 +41,11 @@ const formValue = ref({
darkTheme: true,
enableFund: false,
enablePushNews: false,
enableOnlyPushRedNews: false,
sponsorCode: "",
httpProxy:"",
httpProxyEnabled:false,
enableAgent: false,
})
// 添加一个新的AI配置到列表
@@ -98,9 +100,12 @@ 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;
formValue.value.enableAgent = res.enableAgent;
})
GetPromptTemplates("", "").then(res => {
@@ -135,9 +140,11 @@ function saveConfig() {
darkTheme: formValue.value.darkTheme,
enableFund: formValue.value.enableFund,
enablePushNews: formValue.value.enablePushNews,
enableOnlyPushRedNews: formValue.value.enableOnlyPushRedNews,
sponsorCode: formValue.value.sponsorCode,
httpProxy:formValue.value.httpProxy,
httpProxyEnabled:formValue.value.httpProxyEnabled,
enableAgent: formValue.value.enableAgent,
})
if (config.sponsorCode) {
@@ -223,9 +230,11 @@ 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
formValue.value.enableAgent = config.enableAgent
};
reader.readAsText(file);
};
@@ -316,6 +325,10 @@ function deletePrompt(ID) {
<n-form-item-gi :span="3" label="指数基金:" path="enableFund">
<n-switch v-model:value="formValue.enableFund"/>
</n-form-item-gi>
<n-form-item-gi :span="3" label="AI智能体" path="enableAgent">
<n-switch v-model:value="formValue.enableAgent"/>
</n-form-item-gi>
<n-form-item-gi :span="11" label="赞助码:" path="sponsorCode">
<n-input-group>
<n-input :show-count="true" placeholder="赞助码" v-model:value="formValue.sponsorCode"/>
@@ -329,21 +342,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"/>
@@ -461,7 +478,6 @@ function deletePrompt(ID) {
</n-grid>
</n-card>
</n-space>
</n-form>
</n-flex>

View File

@@ -1,10 +1,11 @@
<script setup>
import {computed, h, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
import {computed, h, nextTick, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, watch} from 'vue'
import * as echarts from 'echarts';
import {
AddGroup,
AddStockGroup,
Follow,
GetAiConfigs,
GetAIResponseResult,
GetConfig,
GetFollowList,
@@ -15,11 +16,15 @@ import {
GetStockMinutePriceLineData,
GetVersionInfo,
Greet,
InitializeGroupSort,
NewChatStream,
OpenURL,
RemoveGroup,
RemoveStockGroup,
SaveAIResponseResult,
SaveAsMarkdown,
SaveImage,
SaveWordFile,
SendDingDingMessageByType,
SetAlarmChangePercent,
SetCostPriceAndVolume,
@@ -27,10 +32,7 @@ import {
SetStockSort,
ShareAnalysis,
UnFollow,
OpenURL,
SaveImage,
SaveWordFile,
GetAiConfigs
UpdateGroupSort
} from '../../wailsjs/go/main/App'
import {
NAvatar,
@@ -68,7 +70,6 @@ import vueDanmaku from 'vue3-danmaku'
import {keys, padStart} from "lodash";
import {useRoute, useRouter} from 'vue-router'
import MoneyTrend from "./moneyTrend.vue";
import {TaskTools} from "@vicons/carbon";
import StockSparkLine from "./stockSparkLine.vue";
const route = useRoute()
@@ -170,22 +171,137 @@ const sortedResults = computed(() => {
const groupResults = computed(() => {
const group = {}
for (const key in sortedResults.value) {
if (stocks.value.includes(sortedResults.value[key]['股票代码'])) {
group[key] = sortedResults.value[key]
if (currentGroupId.value === 0) {
return sortedResults.value
} else {
for (const key in sortedResults.value) {
if (stocks.value.includes(sortedResults.value[key]['股票代码'])) {
group[key] = sortedResults.value[key]
}
}
return group
}
return group
})
const showPopover = ref(false)
// 拖拽相关变量
const dragSourceIndex = ref(null)
const dragTargetIndex = ref(null)
// 拖拽处理函数
function handleTabDragStart(event, name) {
// "全部"标签name=0不应该触发拖拽
if (name === 0) {
event.preventDefault();
return;
}
dragSourceIndex.value = name;
event.dataTransfer.effectAllowed = 'move';
event.target.classList.add('tab-dragging');
}
function handleTabDragOver(event) {
event.preventDefault()
event.dataTransfer.dropEffect = 'move'
}
function handleTabDragEnter(event, name) {
event.preventDefault();
// "全部"标签name=0不应该作为拖拽目标
if (name > 0) {
dragTargetIndex.value = name;
if (event.target.classList) {
// 查找最近的标签元素并添加高亮样式
let tabElement = event.target.closest('.n-tabs-tab');
if (tabElement) {
tabElement.classList.add('tab-drag-over');
}
}
}
}
function handleTabDragLeave(event) {
// 查找最近的标签元素并移除高亮样式
let tabElement = event.target.closest('.n-tabs-tab')
if (tabElement && tabElement.classList) {
tabElement.classList.remove('tab-drag-over')
}
// 不要重置 dragTargetIndex因为可能会在元素间快速移动
}
function handleTabDrop(event) {
event.preventDefault();
// 移除所有高亮样式
const tabs = document.querySelectorAll('.n-tabs-tab');
tabs.forEach(tab => {
tab.classList.remove('tab-drag-over');
});
if (dragSourceIndex.value !== null && dragTargetIndex.value !== null &&
dragSourceIndex.value !== dragTargetIndex.value) {
// 确保索引有效(排除"全部"选项卡)
if (dragSourceIndex.value > 0 && dragTargetIndex.value > 0) {
// 查找源分组和目标分组
const sourceGroup = groupList.value.find(g => g.ID === dragSourceIndex.value);
const targetGroup = groupList.value.find(g => g.ID === dragTargetIndex.value);
if (sourceGroup && targetGroup) {
// 计算新的位置序号使用目标分组的sort值
const newSortPosition = targetGroup.sort;
// 调用后端API更新组排序
UpdateGroupSort(sourceGroup.ID, newSortPosition).then(result => {
if (result) {
message.success('分组排序更新成功');
// 重新获取分组列表以更新界面
GetGroupList().then(result => {
groupList.value = result;
});
} else {
message.error('分组排序更新失败');
}
}).catch(error => {
message.error('分组排序更新失败: ' + error.message);
});
}
}
}
// 重置状态
dragSourceIndex.value = null;
dragTargetIndex.value = null;
}
function handleTabDragEnd(event) {
// 移除所有高亮样式
const tabs = document.querySelectorAll('.n-tabs-tab')
tabs.forEach(tab => {
tab.classList.remove('tab-drag-over', 'tab-dragging')
})
dragSourceIndex.value = null
dragTargetIndex.value = null
}
onBeforeMount(() => {
GetGroupList().then(result => {
groupList.value = result
if (route.query.groupId) {
message.success("切换分组:" + route.query.groupName)
currentGroupId.value = Number(route.query.groupId)
//console.log("route.params",route.query)
// 检查是否存在相同的序号
const sorts = result.map(item => item.sort);
const uniqueSorts = new Set(sorts);
// 如果存在重复的序号,则重新初始化序号
if (sorts.length !== uniqueSorts.size) {
// 调用InitializeGroupSort重新初始化序号
// 然后重新获取分组列表
fetchGroupList();
} else {
// 没有重复序号,继续正常流程
if (route.query.groupId) {
message.success("切换分组:" + route.query.groupName)
currentGroupId.value = Number(route.query.groupId)
}
}
})
GetStockList("").then(result => {
@@ -214,20 +330,168 @@ onBeforeMount(() => {
sysPromptOptions.value = promptTemplates.value.filter(item => item.type === '模型系统Prompt')
userPromptOptions.value = promptTemplates.value.filter(item => item.type === '模型用户Prompt')
//console.log("userPromptOptions",userPromptOptions.value)
//console.log("sysPromptOptions",sysPromptOptions.value)
})
GetAiConfigs().then(res=>{
GetAiConfigs().then(res => {
aiConfigs.value = res
data.aiConfigId =res[0].ID
data.aiConfigId = res[0].ID
})
EventsOn("loadingDone", (data) => {
message.loading("刷新股票基础数据...")
GetStockList("").then(result => {
stockList.value = result
options.value = result.map(item => {
return {
label: item.name + " - " + item.ts_code,
value: item.ts_code
}
})
})
})
EventsOn("refresh", (data) => {
message.success(data)
})
EventsOn("showSearch", (data) => {
addBTN.value = data === 1;
})
EventsOn("stock_price", (data) => {
updateData(data)
})
EventsOn("refreshFollowList", (data) => {
WindowReload()
})
EventsOn("newChatStream", async (msg) => {
data.loading = false
if (msg === "DONE") {
SaveAIResponseResult(data.code, data.name, data.airesult, data.chatId, data.question, data.aiConfigId)
message.info("AI分析完成")
message.destroyAll()
} else {
if (msg.chatId) {
data.chatId = msg.chatId
}
if (msg.question) {
data.question = msg.question
}
if (msg.content) {
data.airesult = data.airesult + msg.content
}
if (msg.extraContent) {
data.airesult = data.airesult + msg.extraContent
}
}
})
EventsOn("changeTab", async (msg) => {
currentGroupId.value = Number(msg.ID)
nextTick(() => {
updateTab(currentGroupId.value);
});
})
EventsOn("updateVersion", async (msg) => {
const githubTimeStr = msg.published_at;
// 创建一个 Date 对象
const utcDate = new Date(githubTimeStr);
// 获取本地时间
const date = new Date(utcDate.getTime());
const year = date.getFullYear();
// getMonth 返回值是 0 - 11所以要加 1
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
notify.info({
avatar: () =>
h(NAvatar, {
size: 'small',
round: false,
src: icon.value
}),
title: '发现新版本: ' + msg.tag_name,
content: () => {
//return h(MdPreview, {theme:'dark',modelValue:msg.commit?.message}, null)
return h('div', {
style: {
'text-align': 'left',
'font-size': '14px',
}
}, {default: () => msg.commit?.message})
},
duration: 5000,
meta: "发布时间:" + formattedDate,
action: () => {
return h(NButton, {
type: 'primary',
size: 'small',
onClick: () => {
Environment().then(env => {
switch (env.platform) {
case 'windows':
window.open(msg.html_url)
break
default :
OpenURL(msg.html_url)
}
})
}
}, {default: () => '查看'})
}
})
})
EventsOn("warnMsg", async (msg) => {
notify.error({
avatar: () =>
h(NAvatar, {
size: 'small',
round: false,
src: icon.value
}),
title: '警告',
duration: 5000,
content: () => {
return h('div', {
style: {
'text-align': 'left',
'font-size': '14px',
}
}, {default: () => msg})
},
})
})
})
onMounted(() => {
message.loading("Loading...")
nextTick(() => {
initDraggableTabs();
});
// 监听分组列表变化,重新初始化拖拽
const unwatch = watch(groupList, () => {
nextTick(() => {
initDraggableTabs();
});
});
// 在组件卸载时清理监听器
onBeforeUnmount(() => {
unwatch();
});
message.loading("Loading...")
GetFollowList(currentGroupId.value).then(result => {
followList.value = result
@@ -246,7 +510,6 @@ onMounted(() => {
message.destroyAll()
})
GetVersionInfo().then((res) => {
icon.value = res.icon;
});
@@ -272,6 +535,48 @@ onMounted(() => {
//console.log('WebSocket 连接已关闭');
};
})
// 清理拖拽事件监听器
// 清理拖拽事件监听器
function cleanupDraggableTabs() {
const tabs = document.querySelectorAll('.n-tabs-tab');
tabs.forEach((tab) => {
// 移除所有可能的拖拽事件监听器
tab.removeEventListener('dragstart', handleTabDragStart);
tab.removeEventListener('dragover', handleTabDragOver);
tab.removeEventListener('dragenter', handleTabDragEnter);
tab.removeEventListener('dragleave', handleTabDragLeave);
tab.removeEventListener('drop', handleTabDrop);
tab.removeEventListener('dragend', handleTabDragEnd);
// 移除draggable属性
tab.removeAttribute('draggable');
});
}
// 初始化可拖拽选项卡
function initDraggableTabs() {
// 移除之前可能添加的事件监听器
cleanupDraggableTabs();
// 添加拖拽事件监听器到选项卡元素
setTimeout(() => {
const tabs = document.querySelectorAll('.n-tabs-tab');
tabs.forEach((tab, index) => {
const dataIndex = tab.getAttribute('data-name');
const name = parseInt(dataIndex);
// 只为分组标签name > 0添加拖拽功能
if (name > 0) {
tab.setAttribute('draggable', 'true');
tab.addEventListener('dragstart', (e) => handleTabDragStart(e, name));
tab.addEventListener('dragover', handleTabDragOver);
tab.addEventListener('dragenter', (e) => handleTabDragEnter(e, name));
tab.addEventListener('dragleave', handleTabDragLeave);
tab.addEventListener('drop', handleTabDrop);
tab.addEventListener('dragend', handleTabDragEnd);
}
});
}, 100);
}
onBeforeUnmount(() => {
// //console.log(`the component is now unmounted.`)
@@ -290,146 +595,9 @@ onBeforeUnmount(() => {
EventsOff("updateVersion")
EventsOff("warnMsg")
EventsOff("loadingDone")
})
EventsOn("loadingDone", (data) => {
message.loading("刷新股票基础数据...")
GetStockList("").then(result => {
stockList.value = result
options.value = result.map(item => {
return {
label: item.name + " - " + item.ts_code,
value: item.ts_code
}
})
})
})
cleanupDraggableTabs()
EventsOn("refresh", (data) => {
message.success(data)
})
EventsOn("showSearch", (data) => {
addBTN.value = data === 1;
})
EventsOn("stock_price", (data) => {
updateData(data)
})
EventsOn("refreshFollowList", (data) => {
WindowReload()
})
EventsOn("newChatStream", async (msg) => {
////console.log("newChatStream:->",data.airesult)
data.loading = false
////console.log(msg)
if (msg === "DONE") {
SaveAIResponseResult(data.code, data.name, data.airesult, data.chatId, data.question,data.aiConfigId)
message.info("AI分析完成")
message.destroyAll()
} else {
if (msg.chatId) {
data.chatId = msg.chatId
}
if (msg.question) {
data.question = msg.question
}
if (msg.content) {
data.airesult = data.airesult + msg.content
}
if (msg.extraContent) {
data.airesult = data.airesult + msg.extraContent
}
}
})
EventsOn("changeTab", async (msg) => {
//console.log("changeTab",msg)
currentGroupId.value = msg.ID
updateTab(currentGroupId.value)
})
EventsOn("updateVersion", async (msg) => {
const githubTimeStr = msg.published_at;
// 创建一个 Date 对象
const utcDate = new Date(githubTimeStr);
// 获取本地时间
const date = new Date(utcDate.getTime());
const year = date.getFullYear();
// getMonth 返回值是 0 - 11所以要加 1
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
//console.log("GitHub UTC 时间:", utcDate);
//console.log("转换后的本地时间:", formattedDate);
notify.info({
avatar: () =>
h(NAvatar, {
size: 'small',
round: false,
src: icon.value
}),
title: '发现新版本: ' + msg.tag_name,
content: () => {
//return h(MdPreview, {theme:'dark',modelValue:msg.commit?.message}, null)
return h('div', {
style: {
'text-align': 'left',
'font-size': '14px',
}
}, {default: () => msg.commit?.message})
},
duration: 5000,
meta: "发布时间:" + formattedDate,
action: () => {
return h(NButton, {
type: 'primary',
size: 'small',
onClick: () => {
Environment().then(env => {
switch (env.platform) {
case 'windows':
window.open(msg.html_url)
break
default :
OpenURL(msg.html_url)
}
})
}
}, {default: () => '查看'})
}
})
})
EventsOn("warnMsg", async (msg) => {
notify.error({
avatar: () =>
h(NAvatar, {
size: 'small',
round: false,
src: icon.value
}),
title: '警告',
duration: 5000,
content: () => {
return h('div', {
style: {
'text-align': 'left',
'font-size': '14px',
}
}, {default: () => msg})
},
})
})
//判断是否是A股交易时间
@@ -452,6 +620,23 @@ function isTradingTime() {
return false;
}
// 添加一个获取分组列表的函数,用于处理初始化逻辑
function fetchGroupList() {
InitializeGroupSort().then(initResult => {
if (initResult) {
GetGroupList().then(result => {
groupList.value = result
if (route.query.groupId) {
message.success("切换分组:" + route.query.groupName)
currentGroupId.value = Number(route.query.groupId)
}
})
} else {
message.error("初始化分组序号失败")
}
})
}
function AddStock() {
if (!data?.code) {
message.error("请输入有效股票代码");
@@ -1395,7 +1580,7 @@ function aiReCheckStock(stock, stockCode) {
//
//message.info("sysPromptId:"+data.sysPromptId)
NewChatStream(stock, stockCode, data.question,data.aiConfigId, data.sysPromptId, enableTools.value)
NewChatStream(stock, stockCode, data.question, data.aiConfigId, data.sysPromptId, enableTools.value)
}
function aiCheckStock(stock, stockCode) {
@@ -1499,14 +1684,14 @@ function saveAsImage(name, code) {
}
async function saveCanvasImage(name) {
const element = document.querySelector('.md-editor-preview'); // 要截图的 DOM 节点
const element = document.querySelector('.md-editor-preview'); // 要截图的 DOM 节点
const canvas = await html2canvas(element)
const dataUrl = canvas.toDataURL('image/png') // base64 格式
const base64 = dataUrl.replace(/^data:image\/png;base64,/, '')
// 调用 Go 后端保存文件Wails 绑定方法)
await SaveImage(name,base64).then(result => {
await SaveImage(name, base64).then(result => {
message.success(result)
})
}
@@ -1568,7 +1753,7 @@ AI赋能股票分析自选股行情获取成本盈亏展示涨跌报警
`
// landscape就是横着的portrait是竖着的默认是竖屏portrait。
const blob = await asBlob(value, {orientation: 'portrait'})
const { platform } = await Environment()
const {platform} = await Environment()
switch (platform) {
case 'windows':
const a = document.createElement('a')
@@ -1580,13 +1765,13 @@ AI赋能股票分析自选股行情获取成本盈亏展示涨跌报警
a.remove()
break
default:
const arrayBuffer = await blob.arrayBuffer()
const uint8Array = new Uint8Array(arrayBuffer)
const binary = uint8Array.reduce((data, byte) => data + String.fromCharCode(byte), '')
const base64 = btoa(binary)
await SaveWordFile(`${data.name}[${data.code}]-ai-analysis-result.docx`, base64).then(result => {
message.success(result)
})
const arrayBuffer = await blob.arrayBuffer()
const uint8Array = new Uint8Array(arrayBuffer)
const binary = uint8Array.reduce((data, byte) => data + String.fromCharCode(byte), '')
const base64 = btoa(binary)
await SaveWordFile(`${data.name}[${data.code}]-ai-analysis-result.docx`, base64).then(result => {
message.success(result)
})
}
}
@@ -1649,9 +1834,11 @@ function AddStockGroupInfo(groupId, code, name) {
function updateTab(name) {
stocks.value = []
currentGroupId.value = Number(name)
GetFollowList(currentGroupId.value).then(result => {
const tabId= Number(name)
currentGroupId.value = tabId;
GetFollowList(tabId).then(result => {
followList.value = result
for (const followedStock of result) {
if (followedStock.StockCode.startsWith("us")) {
followedStock.StockCode = "gb_" + followedStock.StockCode.replace("us", "").toLowerCase()
@@ -1666,8 +1853,8 @@ function updateTab(name) {
})
}
function delTab(name) {
let infos = groupList.value = groupList.value.filter(item => item.ID === Number(name))
function delTab(groupId) {
let infos = groupList.value = groupList.value.filter(item => item.ID === Number(groupId))
dialog.create({
title: '删除分组',
type: 'warning',
@@ -1675,7 +1862,7 @@ function delTab(name) {
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
RemoveGroup(name).then(result => {
RemoveGroup(Number(groupId)).then(result => {
message.info(result)
GetGroupList().then(result => {
groupList.value = result
@@ -1724,8 +1911,9 @@ function searchStockReport(stockCode) {
</template>
</vue-danmaku>
<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="'全部'">
:value="String(currentGroupId)" @add="addTab" @update:value="updateTab" placement="top" @close="(key)=>{delTab(key)}">
<n-tab-pane closable name="0" :tab="'全部'">
<n-grid :x-gap="8" :cols="3" :y-gap="8">
<n-gi :id="result['股票代码']+'_gi'" v-for="result in sortedResults" style="margin-left: 2px;">
<n-card :data-sort="result.sort" :id="result['股票代码']" :data-code="result['股票代码']" :bordered="true"
@@ -1864,7 +2052,7 @@ function searchStockReport(stockCode) {
</n-gi>
</n-grid>
</n-tab-pane>
<n-tab-pane closable v-for="group in groupList" :group-id="group.ID" :name="group.ID" :tab="group.name">
<n-tab-pane closable v-for="group in groupList" :group-id="group.ID" :name="String(group.ID)" :tab="group.name">
<n-grid :x-gap="8" :cols="3" :y-gap="8">
<n-gi :id="result['股票代码']+'_gi'" v-for="result in groupResults" style="margin-left: 2px;">
<n-card :data-sort="result.sort" :id="result['股票代码']" :data-code="result['股票代码']" :bordered="true"
@@ -2011,6 +2199,7 @@ function searchStockReport(stockCode) {
</n-grid>
</n-tab-pane>
</n-tabs>
<div style="position: fixed;bottom: 18px;right:5px;z-index: 10;width: 400px">
<!-- <n-card :bordered="false">-->
<n-input-group>
@@ -2228,4 +2417,38 @@ function searchStockReport(stockCode) {
border-color: red;
}
}
</style>
/* 所有标签的通用样式 */
:deep(.n-tabs-nav .n-tabs-tab) {
position: relative;
cursor: pointer;
}
/* 可拖拽标签的样式 */
:deep(.n-tabs-nav .n-tabs-tab[draggable="true"]) {
user-select: none;
cursor: move;
}
.tab-drag-over {
background-color: #e6f7ff !important;
border: 2px dashed #1890ff !important;
transform: scale(1.02);
transition: all 0.2s ease;
z-index: 10;
}
.tab-drag-over::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
}
.tab-dragging {
opacity: 0.5;
}
</style>

View File

@@ -9,18 +9,21 @@ import EmbeddedUrl from "./EmbeddedUrl.vue";
<n-tab-pane name="选股通" tab="选股通">
<embedded-url url="https://xuangutong.com.cn" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<!-- <n-tab-pane name="百度股市通" tab="百度股市通">-->
<!-- <embedded-url url="https://gushitong.baidu.com" :height="'calc(100vh - 252px)'"/>-->
<!-- </n-tab-pane>-->
<n-tab-pane name="百度股市通" tab="百度股市通">
<embedded-url url="https://gushitong.baidu.com" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<n-tab-pane name="东财大盘星图" tab="东财大盘星图">
<embedded-url url="https://quote.eastmoney.com/stockhotmap/" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<n-tab-pane name="TopHub" tab="TopHub(今日热榜)">
<embedded-url url="https://tophub.today/c/finance" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<!-- <n-tab-pane name="摸鱼" tab="摸鱼">-->
<!-- <embedded-url url="https://996.ninja/" :height="'calc(100vh - 252px)'"/>-->
<!-- </n-tab-pane>-->
<n-tab-pane name="摸鱼" tab="摸鱼">
<embedded-url url="https://996.ninja/" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<n-tab-pane name="财联社-行情数据" tab="财联社-行情数据">
<embedded-url url="https://www.cls.cn/quotation" :height="'calc(100vh - 252px)'"/>
</n-tab-pane>
<n-tab-pane name="欢迎推荐更多有趣的财经网页" tab="欢迎推荐更多有趣的财经网页">

View File

@@ -2,7 +2,8 @@ import {createApp} from 'vue'
import naive from 'naive-ui'
import App from './App.vue'
import router from './router/router'
// 引入组件库的少量全局样式变量
import 'tdesign-vue-next/es/style/index.css';
const app = createApp(App)
app.use(router)

View File

@@ -0,0 +1,101 @@
export class MockSSEResponse {
private controller!: ReadableStreamDefaultController<Uint8Array>;
private encoder = new TextEncoder();
private stream: ReadableStream<Uint8Array>;
private error: boolean;
private currentPhase: 'reasoning' | 'content' = 'reasoning';
constructor(
private data: {
reasoning: string; // 推理内容
content: string; // 正式内容
},
private delay: number = 100,
error = false,
) {
this.error = error;
this.stream = new ReadableStream({
start: (controller) => {
this.controller = controller;
if (!this.error) {
// 如果不是错误情况,则开始推送数据
setTimeout(() => this.pushData(), this.delay); // 延迟开始推送数据
}
},
cancel() {},
});
}
private pushData() {
try {
if (this.currentPhase === 'reasoning') {
// 推送推理内容
if (this.data.reasoning.length > 0) {
const chunk = JSON.stringify({
delta: {
reasoning_content: this.data.reasoning.slice(0, 1),
content: '',
},
finished: false,
});
this.controller.enqueue(this.encoder.encode(chunk));
this.data.reasoning = this.data.reasoning.slice(1);
// 设置下次推送
setTimeout(() => this.pushData(), this.delay);
} else {
// 推理内容推送完成,切换到正式内容
this.currentPhase = 'content';
setTimeout(() => this.pushData(), this.delay); // 立即开始推送正式内容
return;
}
}
if (this.currentPhase === 'content') {
// 推送正式内容
if (this.data.content.length > 0) {
const chunk = JSON.stringify({
delta: {
reasoning_content: '',
content: this.data.content.slice(0, 1),
},
finished: this.data.content.length === 1, // 最后一个字符时标记完成
});
this.controller.enqueue(this.encoder.encode(chunk));
this.data.content = this.data.content.slice(1);
// 设置下次推送
setTimeout(() => this.pushData(), this.delay);
} else {
// const finalPayload = JSON.stringify({
// delta: {
// reasoning_content: '',
// content: '',
// },
// finished: true,
// });
// this.controller.enqueue(this.encoder.encode(`${finalPayload}`));
// 全部内容推送完成
setTimeout(() => this.controller.close(), this.delay);
return;
}
}
} catch {}
}
getResponse(): Promise<Response> {
return new Promise((resolve) => {
// 使用setTimeout来模拟网络延迟
setTimeout(() => {
if (this.error) {
const errorResponseOptions = { status: 500, statusText: 'Internal Server Error' };
// 返回模拟的网络错误响应这里我们使用500状态码作为示例
resolve(new Response(null, errorResponseOptions));
} else {
resolve(new Response(this.stream));
}
}, this.delay); // 使用构造函数中设置的delay值作为延迟时间
});
}
}

View File

@@ -5,6 +5,7 @@ import settingsView from '../components/settings.vue'
import aboutView from "../components/about.vue";
import fundView from "../components/fund.vue";
import marketView from "../components/market.vue";
import agentChat from "../components/agent-chat.vue"
const routes = [
{ path: '/', component: stockView,name: 'stock'},
@@ -12,7 +13,7 @@ const routes = [
{ path: '/settings', component: settingsView,name: 'settings' },
{ path: '/about', component: aboutView,name: 'about' },
{ path: '/market', component: marketView,name: 'market' },
{ path: '/agent', component: agentChat,name: 'agent' },
]
const router = createRouter({

View File

@@ -1,7 +1,22 @@
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { TDesignResolver } from '@tdesign-vue-next/auto-import-resolver';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()]
plugins: [
vue(),
AutoImport({
resolvers: [TDesignResolver({
library: 'chat'
})],
}),
Components({
resolvers: [TDesignResolver({
library: 'chat'
})],
}),
]
})

View File

@@ -14,6 +14,8 @@ export function AddStockGroup(arg1:number,arg2:string):Promise<string>;
export function AnalyzeSentiment(arg1:string):Promise<data.SentimentResult>;
export function ChatWithAgent(arg1:string,arg2:number,arg3:any):Promise<void>;
export function CheckSponsorCode(arg1:string):Promise<Record<string, any>>;
export function CheckStockBaseInfo(arg1:context.Context):Promise<void>;
@@ -86,6 +88,8 @@ export function HotTopic(arg1:number):Promise<Array<any>>;
export function IndustryResearchReport(arg1:string):Promise<Array<any>>;
export function InitializeGroupSort():Promise<boolean>;
export function InvestCalendarTimeLine(arg1:string):Promise<Array<any>>;
export function LongTigerRank(arg1:string):Promise<any>;
@@ -137,3 +141,5 @@ export function UnFollow(arg1:string):Promise<string>;
export function UnFollowFund(arg1:string):Promise<string>;
export function UpdateConfig(arg1:data.SettingConfig):Promise<string>;
export function UpdateGroupSort(arg1:number,arg2:number):Promise<boolean>;

View File

@@ -22,6 +22,10 @@ export function AnalyzeSentiment(arg1) {
return window['go']['main']['App']['AnalyzeSentiment'](arg1);
}
export function ChatWithAgent(arg1, arg2, arg3) {
return window['go']['main']['App']['ChatWithAgent'](arg1, arg2, arg3);
}
export function CheckSponsorCode(arg1) {
return window['go']['main']['App']['CheckSponsorCode'](arg1);
}
@@ -166,6 +170,10 @@ export function IndustryResearchReport(arg1) {
return window['go']['main']['App']['IndustryResearchReport'](arg1);
}
export function InitializeGroupSort() {
return window['go']['main']['App']['InitializeGroupSort']();
}
export function InvestCalendarTimeLine(arg1) {
return window['go']['main']['App']['InvestCalendarTimeLine'](arg1);
}
@@ -269,3 +277,7 @@ export function UnFollowFund(arg1) {
export function UpdateConfig(arg1) {
return window['go']['main']['App']['UpdateConfig'](arg1);
}
export function UpdateGroupSort(arg1, arg2) {
return window['go']['main']['App']['UpdateGroupSort'](arg1, arg2);
}

View File

@@ -389,9 +389,11 @@ export namespace data {
browserPoolSize: number;
enableFund: boolean;
enablePushNews: boolean;
enableOnlyPushRedNews: boolean;
sponsorCode: string;
httpProxy: string;
httpProxyEnabled: boolean;
enableAgent: boolean;
aiConfigs: AIConfig[];
static createFrom(source: any = {}) {
@@ -423,9 +425,11 @@ 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.enableAgent = source["enableAgent"];
this.aiConfigs = this.convertValues(source["aiConfigs"], AIConfig);
}

57
go.mod
View File

@@ -1,10 +1,14 @@
module go-stock
go 1.23.0
go 1.25.0
require (
github.com/PuerkitoBio/goquery v1.10.1
github.com/chromedp/chromedp v0.11.2
github.com/chromedp/chromedp v0.14.1
github.com/cloudwego/eino v0.4.1
github.com/cloudwego/eino-ext/components/model/ark v0.1.19
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20250804092122-8845979a2228
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250804092122-8845979a2228
github.com/coocood/freecache v1.2.4
github.com/duke-git/lancet/v2 v2.3.4
github.com/energye/systray v1.0.2
@@ -18,12 +22,12 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/samber/lo v1.49.1
github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.14.2
github.com/tidwall/gjson v1.14.4
github.com/wailsapp/wails/v2 v2.10.1
go.uber.org/zap v1.27.0
golang.org/x/net v0.38.0
golang.org/x/sys v0.31.0
golang.org/x/text v0.23.0
golang.org/x/sys v0.36.0
golang.org/x/text v0.26.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/gorm v1.25.12
gorm.io/plugin/dbresolver v1.5.3
@@ -34,24 +38,40 @@ require (
git.sr.ht/~jackmordaunt/go-toast v1.1.2 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb // indirect
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d // indirect
github.com/chromedp/sysutil v1.1.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250804062529-6e67726a4b3f // indirect
github.com/cohesion-org/deepseek-go v1.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/esiqveland/notify v0.13.3 // indirect
github.com/evanphx/json-patch v0.5.2 // indirect
github.com/getkin/kin-openapi v0.118.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/goph/emperror v0.17.2 // indirect
github.com/invopop/yaml v0.1.0 // indirect
github.com/jackmordaunt/icns/v3 v3.0.1 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/labstack/echo/v4 v4.13.3 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
@@ -61,29 +81,48 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250723112853-3bce976e5ccc // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nikolalohinski/gonja v1.5.3 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/ollama/ollama v0.6.5 // indirect
github.com/openai/openai-go v1.10.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
github.com/sergeymakinen/go-ico v1.0.0-beta.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vcaesar/cedar v0.20.2 // indirect
github.com/volcengine/volc-sdk-golang v1.0.23 // indirect
github.com/volcengine/volcengine-go-sdk v1.1.21 // indirect
github.com/wailsapp/go-webview2 v1.0.19 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/yargevad/filepathx v1.0.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect

259
go.sum
View File

@@ -1,19 +1,50 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.sr.ht/~jackmordaunt/go-toast v1.1.2 h1:/yrfI55LRt1M7H1vkaw+NaH1+L1CDxrqDltwm5euVuE=
git.sr.ht/~jackmordaunt/go-toast v1.1.2/go.mod h1:jA4OqHKTQ4AFBdwrSnwnskUIIS3HYzlJSgdzCKqfavo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/bytedance/mockey v1.2.14 h1:KZaFgPdiUwW+jOWFieo3Lr7INM1P+6adO3hxZhDswY8=
github.com/bytedance/mockey v1.2.14/go.mod h1:1BPHF9sol5R1ud/+0VEHGQq/+i2lN+GTsr3O2Q9IENY=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb h1:noKVm2SsG4v0Yd0lHNtFYc9EUxIVvrr4kJ6hM8wvIYU=
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM=
github.com/chromedp/chromedp v0.11.2 h1:ZRHTh7DjbNTlfIv3NFTbB7eVeu5XCNkgrpcGSpn2oX0=
github.com/chromedp/chromedp v0.11.2/go.mod h1:lr8dFRLKsdTTWb75C/Ttol2vnBKOSnt0BW8R9Xaupi8=
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d h1:ZtA1sedVbEW7EW80Iz2GR3Ye6PwbJAJXjv7D74xG6HU=
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
github.com/chromedp/chromedp v0.14.1 h1:0uAbnxewy/Q+Bg7oafVePE/6EXEho9hnaC38f+TTENg=
github.com/chromedp/chromedp v0.14.1/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo=
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cloudwego/eino v0.4.1 h1:Jy9KWpCvd+Z75oIynhHsT9dEECUuCW8IPZlVjHgVu9s=
github.com/cloudwego/eino v0.4.1/go.mod h1:wUjz990apdsaOraOXdh6CdhVXq8DJsOvLsVlxNTcNfY=
github.com/cloudwego/eino-ext/components/model/ark v0.1.19 h1:XYnOeszXA28T1gxYOpTIjOjLCPO2gjexK+ShSan9u/8=
github.com/cloudwego/eino-ext/components/model/ark v0.1.19/go.mod h1:VZ7Sa1ocNiSZFiNgg1PQXYdnCJAzPy4Dxt/Ctuwlfp8=
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20250804092122-8845979a2228 h1:YIX5vk2Yx2cOiZHsU3xihVnriOMwNX5NP8g4q18yxf4=
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20250804092122-8845979a2228/go.mod h1:3XV+kHvG6IrVj4WXlquihx8i7a8fUKa09PzuS7IvF2k=
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250804092122-8845979a2228 h1:uyWZnjeUxlu4KfCKHKW5Ml22SiD+DvkbgWTBDds4ziE=
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250804092122-8845979a2228/go.mod h1:3uBZ/GzJzh1izfY2w62282FZrQG3ISs6T/jTmmPffvE=
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250804062529-6e67726a4b3f h1:J1tQBg6RDftrtm3vsv+ozlupdlNV+WGslpXiTDr/2xI=
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250804062529-6e67726a4b3f/go.mod h1:4EBgz8+68n1iuKyWC37Tu9NG1WJkPm+yLxvyLik28Us=
github.com/cohesion-org/deepseek-go v1.3.2 h1:WTZ/2346KFYca+n+DL5p+Ar1RQxF2w/wGkU4jDvyXaQ=
github.com/cohesion-org/deepseek-go v1.3.2/go.mod h1:bOVyKj38r90UEYZFrmJOzJKPxuAh8sIzHOCnLOpiXeI=
github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M=
github.com/coocood/freecache v1.2.4/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -25,22 +56,42 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/energye/systray v1.0.2 h1:63R4prQkANtpM2CIA4UrDCuwZFt+FiygG77JYCsNmXc=
github.com/energye/systray v1.0.2/go.mod h1:sp7Q/q/I4/w5ebvpSuJVep71s9Bg7L9ZVp69gBASehM=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/esiqveland/notify v0.13.3 h1:QCMw6o1n+6rl+oLUfg8P1IIDSFsDEb2WlXvVvIJbI/o=
github.com/esiqveland/notify v0.13.3/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE=
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gen2brain/beeep v0.11.1 h1:EbSIhrQZFDj1K2fzlMpAYlFOzV8YuNe721A58XcCTYI=
github.com/gen2brain/beeep v0.11.1/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9Cn2W4tvoc=
github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM=
github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-ego/gse v0.80.3 h1:YNFkjMhlhQnUeuoFcUEd1ivh6SOB764rT8GDsEbDiEg=
github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg0gU=
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 h1:02WINGfSX5w0Mn+F28UyRoSt9uvMhKguwWMlOAh6U/0=
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
@@ -52,25 +103,74 @@ github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakr
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/jackmordaunt/icns/v3 v3.0.1 h1:xxot6aNuGrU+lNgxz5I5H0qSeCjNKp8uTXB1j8D4S3o=
github.com/jackmordaunt/icns/v3 v3.0.1/go.mod h1:5sHL59nqTd2ynTnowxB/MDQFhKNqkK8X687uKNygaSQ=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
@@ -89,6 +189,8 @@ github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
@@ -102,20 +204,45 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250723112853-3bce976e5ccc h1:vdRbmKDHZMGb5SSUVAT9u+559Vr2gScV5ie/kcOvfeE=
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250723112853-3bce976e5ccc/go.mod h1:CqSFsV6AkkL2fixd25WYjRAolns+gQrY1x/Cz9c30v8=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c=
github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/ollama/ollama v0.6.5 h1:vXKkVX57ql/1ZzMw4SVK866Qfd6pjwEcITVyEpF0QXQ=
github.com/ollama/ollama v0.6.5/go.mod h1:pGgtoNyc9DdM6oZI6yMfI6jTk2Eh4c36c2GpfQCH7PY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openai/openai-go v1.10.1 h1:7VR8z1foqJDjlaFZsNH5zZIYTWKYz97tdsVSzXDHQck=
github.com/openai/openai-go v1.10.1/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -126,32 +253,63 @@ github.com/robertkrimen/otto v0.5.1 h1:avDI4ToRk8k1hppLdYFTuuzND41n37vPGJU7547dG
github.com/robertkrimen/otto v0.5.1/go.mod h1:bS433I4Q9p+E5pZLu7r17vP6FkE6/wLxBdmKjoqJXF8=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/sergeymakinen/go-bmp v1.0.0 h1:SdGTzp9WvCV0A1V0mBeaS7kQAwNLdVJbmHlqNWq0R+M=
github.com/sergeymakinen/go-bmp v1.0.0/go.mod h1:/mxlAQZRLxSvJFNIEGGLBE/m40f3ZnUifpgVDlcUIEY=
github.com/sergeymakinen/go-ico v1.0.0-beta.0 h1:m5qKH7uPKLdrygMWxbamVn+tl2HfiA3K6MFJw4GfZvQ=
github.com/sergeymakinen/go-ico v1.0.0-beta.0/go.mod h1:wQ47mTczswBO5F0NoDt7O0IXgnV4Xy3ojrroMQzyhUk=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f h1:Z2cODYsUxQPofhpYRMQVwWz4yUVpHF+vPi+eUdruUYI=
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f/go.mod h1:JqzWyvTuI2X4+9wOHmKSQCYxybB/8j6Ko43qVmXDuZg=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c h1:coVla7zpsycc+kA9NXpcvv2E4I7+ii6L5hZO2S6C3kw=
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
@@ -160,34 +318,56 @@ github.com/vcaesar/cedar v0.20.2 h1:TDx7AdZhilKcfE1WvdToTJf5VrC/FXcUOW+KY1upLZ4=
github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik=
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU=
github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9PY+4OehIk5R8=
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
github.com/volcengine/volcengine-go-sdk v1.1.21 h1:HxEaSsT+SRx0J5z5hDi+MVeYK6VRljdTjSjUnBg2Aso=
github.com/volcengine/volcengine-go-sdk v1.1.21/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY=
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc=
github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -200,6 +380,9 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -207,6 +390,9 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -214,6 +400,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -224,8 +411,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -235,6 +422,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -245,25 +434,57 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
@@ -279,6 +500,8 @@ gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU=
gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE=
gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU=
gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=

20
main.go
View File

@@ -5,6 +5,14 @@ import (
"embed"
"encoding/json"
"fmt"
"go-stock/backend/data"
"go-stock/backend/db"
log "go-stock/backend/logger"
"go-stock/backend/models"
"os"
"runtime/debug"
"strings"
"github.com/duke-git/lancet/v2/slice"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
@@ -13,13 +21,6 @@ import (
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2/pkg/options/windows"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go-stock/backend/data"
"go-stock/backend/db"
log "go-stock/backend/logger"
"go-stock/backend/models"
"os"
"runtime/debug"
"strings"
)
//go:embed frontend/dist
@@ -125,7 +126,7 @@ func main() {
err = wails.Run(&options.App{
Title: "go-stockAI赋能股票分析✨",
Width: width * 4 / 5,
Height: 950,
Height: 920,
MinWidth: minWidth,
MinHeight: minHeight,
//MaxWidth: width,
@@ -148,7 +149,7 @@ func main() {
OnShutdown: app.shutdown,
WindowStartState: options.Normal,
SingleInstanceLock: &options.SingleInstanceLock{
UniqueId: "go-stock",
UniqueId: "go-stock-dev",
OnSecondInstanceLaunch: OnSecondInstanceLaunch,
},
Bind: []interface{}{
@@ -227,6 +228,7 @@ func AutoMigrate() {
db.Dao.AutoMigrate(&models.TelegraphTags{})
db.Dao.AutoMigrate(&models.LongTigerRankData{})
db.Dao.AutoMigrate(&data.AIConfig{})
db.Dao.AutoMigrate(&models.BKDict{})
updateMultipleModel()
}