Compare commits
1 Commits
v2025.11.2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6116e9287a |
12
.github/workflows/main.yml
vendored
12
.github/workflows/main.yml
vendored
@@ -5,13 +5,10 @@ on:
|
||||
tags:
|
||||
# Match any new tag
|
||||
- '*-release'
|
||||
- '*-dev'
|
||||
|
||||
env:
|
||||
# Necessary for most environments as build failure can occur due to OOM issues
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
OFFICIAL_STATEMENT: ${{ vars.OFFICIAL_STATEMENT }}
|
||||
BUILD_KEY: ${{ vars.BUILD_KEY }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -26,9 +23,6 @@ jobs:
|
||||
# - name: 'go-stock-linux-amd64'
|
||||
# platform: 'linux/amd64'
|
||||
# os: 'ubuntu-latest'
|
||||
- name: 'go-stock-darwin-universal'
|
||||
platform: 'darwin/universal'
|
||||
os: 'macos-latest'
|
||||
|
||||
runs-on: ${{ matrix.build.os }}
|
||||
steps:
|
||||
@@ -44,15 +38,13 @@ jobs:
|
||||
echo "::set-output name=commit_message::$commit_message"
|
||||
|
||||
- name: Build wails x go-stock
|
||||
uses: ArvinLovegood/wails-build-action@v3.6
|
||||
uses: ArvinLovegood/wails-build-action@v3.4
|
||||
id: build
|
||||
with:
|
||||
build-name: ${{ matrix.build.name }}
|
||||
build-platform: ${{ matrix.build.platform }}
|
||||
package: true
|
||||
go-version: '1.25'
|
||||
go-version: '1.24'
|
||||
build-tags: ${{ github.ref_name }}
|
||||
build-commit-message: ${{ steps.get_commit_message.outputs.commit_message }}
|
||||
build-statement: ${{ env.OFFICIAL_STATEMENT }}
|
||||
build-key: ${{ env.BUILD_KEY }}
|
||||
node-version: '20.x'
|
||||
|
||||
44
README.md
44
README.md
@@ -10,9 +10,8 @@
|
||||

|
||||
|
||||
### 📈 交流群
|
||||
|
||||
[//]: # (- QQ交流群2:[点击链接加入群聊【go-stock交流群2】:892666282](https://qm.qq.com/q/5mYiy6Yxh0))
|
||||
- QQ交流群:[点击链接加入群聊【go-stock交流群】:491605333(定期清理,随缘入群)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0YQ8qD3exahsD4YLNhzQTWe5ssstWC89&authKey=usOMMRFtIQDC%2FYcatHYapcxQbJ7PwXPHK9OypTXWzNjAq%2FRVvQu9bj2lRgb%2BSZ3p&noverify=0&group_code=491605333)
|
||||
- QQ交流群2:[点击链接加入群聊【go-stock交流群2】:892666282](https://qm.qq.com/q/5mYiy6Yxh0)
|
||||
- QQ交流群:[点击链接加入群聊【go-stock交流群】:491605333(已满会定期清理,随缘入群)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0YQ8qD3exahsD4YLNhzQTWe5ssstWC89&authKey=usOMMRFtIQDC%2FYcatHYapcxQbJ7PwXPHK9OypTXWzNjAq%2FRVvQu9bj2lRgb%2BSZ3p&noverify=0&group_code=491605333)
|
||||
|
||||
### ✨ 简介
|
||||
- 本项目基于Wails和NaiveUI开发,结合AI大模型构建的股票分析工具。
|
||||
@@ -24,43 +23,32 @@
|
||||
### 📦 立即体验
|
||||
- 安装版:[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))
|
||||
|
||||
|
||||
### 💬 支持大模型/平台
|
||||
| 模型 | 状态 | 备注 |
|
||||
| --- | --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [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) |
|
||||
| 模型 | 状态 | 备注 |
|
||||
| --- | --- |---------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [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) |
|
||||
|
||||
### <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))
|
||||
- 火山方舟:新用户每个模型注册即送50万tokens,[注册链接](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ)
|
||||
- 硅基流动(siliconflow),注册即送2000万Tokens,[注册链接](https://cloud.siliconflow.cn/i/foufCerk)
|
||||
- 优云智算(by UCloud):万卡规模4090免费用10小时,新人注册另增50万tokens,海量热门源项目镜像一键部署,[注册链接](https://www.compshare.cn/image-community?ytag=GPU_YY-gh_gostock)
|
||||
- 经测试目前硅基流动(siliconflow)提供的deepSeek api 服务比较稳定,注册即送2000万Tokens,[注册链接](https://cloud.siliconflow.cn/i/foufCerk)
|
||||
- 火山方舟:每个模型注册即送50万tokens,[注册链接](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ)
|
||||
- Tushare大数据开放社区,免费提供各类金融数据,助力行业和量化研究(注意:Tushare只需要120积分即可,注册完成个人资料补充即可得120积分!!!),[注册链接](https://tushare.pro/register?reg=701944)
|
||||
- 软件快速迭代开发中,请大家优先测试和使用最新发布的版本。
|
||||
- 欢迎大家提出宝贵的建议,欢迎提issue,PR。当然更欢迎[赞助我](#都划到这了如果我的项目对您有帮助请赞助我吧)。💕
|
||||
|
||||
|
||||
### 支持开源💕计划
|
||||
| 赞助计划 | 赞助等级 | 权益说明 |
|
||||
|:--------------------------------|----------------|:-------------------------------------------------------|
|
||||
| 每月 0 RMB | vip0 | 🌟 全部功能,软件自动更新(从GitHub下载),自行解决github平台网络问题。 |
|
||||
| 每月赞助 18.8 RMB<br>每年赞助 120 RMB | vip1 | 💕 全部功能,软件自动更新(从CDN下载),更新快速便捷。AI配置指导,提示词参考等 |
|
||||
| 每月赞助 28.8 RMB<br>每年赞助 240 RMB | vip2 | 💕 💕 vip1全部功能,赠送硅基流动AI分析服务 |
|
||||
| 每月赞助 X RMB | vipX | 🧩 更多计划,视go-stock开源项目发展情况而定...(承接GitHub项目README广告推广💖) |
|
||||
|
||||
## 🧩 重大功能开发计划
|
||||
| 功能说明 | 状态 | 备注 |
|
||||
|-----------------|----|----------------------------------------------------------------------------------------------------------|
|
||||
| 股票分析知识库 | 🚧 | 未来计划 |
|
||||
| Ai智能选股 | ✅ | Ai智能选股功能(市场行情-》AI总结/AI智能体功能) |
|
||||
| Ai智能选股 | 🚧 | Ai智能选股功能开发中(下半年重点开发计划) |
|
||||
| ETF支持 | 🚧 | ETF数据支持 (目前可以查看净值和估值) |
|
||||
| 美股支持 | ✅ | 美股数据支持 |
|
||||
| 港股支持 | ✅ | 港股数据支持 |
|
||||
@@ -69,10 +57,6 @@
|
||||
| 不再强制依赖Chrome浏览器 | ✅ | 默认使用edge浏览器抓取新闻资讯 |
|
||||
|
||||
## 👀 更新日志
|
||||
### 2025.07.08 实现软件自动更新功能
|
||||
### 2025.07.07 卡片添加迷你分时图
|
||||
### 2025.07.05 MacOs支持
|
||||
### 2025.07.01 AI分析集成工具函数,AI分析将更加智能
|
||||
### 2025.06.30 添加指标选股功能
|
||||
### 2025.06.27 添加财经日历和重大事件时间轴功能
|
||||
### 2025.06.25 添加热门股票、事件和话题功能
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go-stock/backend/agent"
|
||||
"go-stock/backend/data"
|
||||
"go-stock/backend/models"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
@@ -28,66 +24,38 @@ func (a *App) StockNotice(stockCode string) []any {
|
||||
func (a *App) IndustryResearchReport(industryCode string) []any {
|
||||
return data.NewMarketNewsApi().IndustryResearchReport(industryCode, 7)
|
||||
}
|
||||
func (a *App) EMDictCode(code string) []any {
|
||||
func (a App) EMDictCode(code string) []any {
|
||||
return data.NewMarketNewsApi().EMDictCode(code, a.cache)
|
||||
}
|
||||
|
||||
func (a *App) AnalyzeSentiment(text string) data.SentimentResult {
|
||||
func (a App) AnalyzeSentiment(text string) data.SentimentResult {
|
||||
return data.AnalyzeSentiment(text)
|
||||
}
|
||||
|
||||
func (a *App) HotStock(marketType string) *[]models.HotItem {
|
||||
func (a App) HotStock(marketType string) *[]models.HotItem {
|
||||
return data.NewMarketNewsApi().XUEQIUHotStock(100, marketType)
|
||||
}
|
||||
|
||||
func (a *App) HotEvent(size int) *[]models.HotEvent {
|
||||
func (a App) HotEvent(size int) *[]models.HotEvent {
|
||||
if size <= 0 {
|
||||
size = 10
|
||||
}
|
||||
return data.NewMarketNewsApi().HotEvent(size)
|
||||
}
|
||||
func (a *App) HotTopic(size int) []any {
|
||||
func (a App) HotTopic(size int) []any {
|
||||
if size <= 0 {
|
||||
size = 10
|
||||
}
|
||||
return data.NewMarketNewsApi().HotTopic(size)
|
||||
}
|
||||
|
||||
func (a *App) InvestCalendarTimeLine(yearMonth string) []any {
|
||||
func (a App) InvestCalendarTimeLine(yearMonth string) []any {
|
||||
return data.NewMarketNewsApi().InvestCalendar(yearMonth)
|
||||
}
|
||||
func (a *App) ClsCalendar() []any {
|
||||
func (a App) ClsCalendar() []any {
|
||||
return data.NewMarketNewsApi().ClsCalendar()
|
||||
}
|
||||
|
||||
func (a *App) SearchStock(words string) map[string]any {
|
||||
return data.NewSearchStockApi(words).SearchStock(5000)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) AnalyzeSentimentWithFreqWeight(text string) map[string]any {
|
||||
if text == "" {
|
||||
telegraphs := data.NewMarketNewsApi().GetNews24HoursList("财联社电报", 1000)
|
||||
messageText := strings.Builder{}
|
||||
for _, telegraph := range *telegraphs {
|
||||
messageText.WriteString(telegraph.Content + "\n")
|
||||
}
|
||||
text = messageText.String()
|
||||
}
|
||||
result, frequencies := data.AnalyzeSentimentWithFreqWeight(text)
|
||||
// 过滤标点符号和分隔符
|
||||
cleanFrequencies := data.FilterAndSortWords(frequencies)
|
||||
return map[string]any{
|
||||
"result": result,
|
||||
"frequencies": cleanFrequencies,
|
||||
}
|
||||
func (a App) SearchStock(words string) map[string]any {
|
||||
return data.NewSearchStockApi(words).SearchStock()
|
||||
}
|
||||
|
||||
512
app_darwin.go
512
app_darwin.go
@@ -6,168 +6,303 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
"github.com/gen2brain/beeep"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"go-stock/backend/data"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"log"
|
||||
"go-stock/backend/models"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/coocood/freecache"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/mathutil"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
// startup 在应用程序启动时调用
|
||||
// App struct
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
cache *freecache.Cache
|
||||
}
|
||||
|
||||
// NewApp creates a new App application struct
|
||||
func NewApp() *App {
|
||||
cacheSize := 512 * 1024
|
||||
cache := freecache.NewCache(cacheSize)
|
||||
return &App{
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
// startup is called at application startup
|
||||
func (a *App) startup(ctx context.Context) {
|
||||
defer PanicHandler()
|
||||
runtime.EventsOn(ctx, "frontendError", func(optionalData ...interface{}) {
|
||||
logger.SugaredLogger.Errorf("Frontend error: %v\n", optionalData)
|
||||
})
|
||||
logger.SugaredLogger.Infof("Version:%s", Version)
|
||||
// Perform your setup here
|
||||
a.ctx = ctx
|
||||
|
||||
// 监听设置更新事件
|
||||
runtime.EventsOn(ctx, "updateSettings", func(optionalData ...interface{}) {
|
||||
config := data.GetSettingConfig()
|
||||
//setMap := optionalData[0].(map[string]interface{})
|
||||
//
|
||||
//// 将 map 转换为 JSON 字节切片
|
||||
//jsonData, err := json.Marshal(setMap)
|
||||
//if err != nil {
|
||||
// logger.SugaredLogger.Errorf("Marshal error:%s", err.Error())
|
||||
// return
|
||||
//}
|
||||
//// 将 JSON 字节切片解析到结构体中
|
||||
//err = json.Unmarshal(jsonData, config)
|
||||
//if err != nil {
|
||||
// logger.SugaredLogger.Errorf("Unmarshal error:%s", err.Error())
|
||||
// return
|
||||
//}
|
||||
// TODO 创建系统托盘
|
||||
|
||||
logger.SugaredLogger.Infof("updateSettings config:%+v", config)
|
||||
if config.DarkTheme {
|
||||
runtime.WindowSetBackgroundColour(ctx, 27, 38, 54, 1)
|
||||
runtime.WindowSetDarkTheme(ctx)
|
||||
} else {
|
||||
runtime.WindowSetBackgroundColour(ctx, 255, 255, 255, 1)
|
||||
runtime.WindowSetLightTheme(ctx)
|
||||
}
|
||||
runtime.WindowReloadApp(ctx)
|
||||
})
|
||||
|
||||
// 创建 macOS 托盘
|
||||
go func() {
|
||||
// 使用 Beeep 库替代 Windows 的托盘库
|
||||
err := beeep.Notify("go-stock", "应用程序已启动", "")
|
||||
if err != nil {
|
||||
log.Fatalf("系统通知失败: %v", err)
|
||||
}
|
||||
}()
|
||||
go setUpScreen(a)
|
||||
logger.SugaredLogger.Infof(" application startup Version:%s", Version)
|
||||
}
|
||||
|
||||
func setUpScreen(a *App) {
|
||||
screens, _ := runtime.ScreenGetAll(a.ctx)
|
||||
if len(screens) == 0 {
|
||||
func checkUpdate(a *App) {
|
||||
releaseVersion := &models.GitHubReleaseVersion{}
|
||||
_, err := resty.New().R().
|
||||
SetResult(releaseVersion).
|
||||
Get("https://api.github.com/repos/ArvinLovegood/go-stock/releases/latest")
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("get github release version error:%s", err.Error())
|
||||
return
|
||||
}
|
||||
screen := screens[0]
|
||||
sw, sh := screen.Width, screen.Height
|
||||
|
||||
// macOS 菜单栏 + Dock 留出空间
|
||||
topBarHeight := 22
|
||||
dockHeight := 56
|
||||
verticalMargin := topBarHeight + dockHeight
|
||||
|
||||
// 设置窗口为屏幕 80% 宽 × 可用高度 90%
|
||||
w := int(float64(sw) * 0.8)
|
||||
h := int(float64(sh-verticalMargin) * 0.9)
|
||||
|
||||
runtime.WindowSetSize(a.ctx, w, h)
|
||||
runtime.WindowCenter(a.ctx)
|
||||
logger.SugaredLogger.Infof("releaseVersion:%+v", releaseVersion.TagName)
|
||||
if releaseVersion.TagName != Version {
|
||||
go runtime.EventsEmit(a.ctx, "updateVersion", releaseVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// OnSecondInstanceLaunch 处理第二实例启动时的通知
|
||||
func OnSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) {
|
||||
err := beeep.Notify("go-stock", "程序已经在运行了", "")
|
||||
// domReady is called after front-end resources have been loaded
|
||||
func (a *App) domReady(ctx context.Context) {
|
||||
// Add your action here
|
||||
//定时更新数据
|
||||
go func() {
|
||||
config := data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||||
interval := config.RefreshInterval
|
||||
if interval <= 0 {
|
||||
interval = 1
|
||||
}
|
||||
ticker := time.NewTicker(time.Second * time.Duration(interval))
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
if isTradingTime(time.Now()) {
|
||||
MonitorStockPrices(a)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second * time.Duration(60))
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
telegraph := refreshTelegraphList()
|
||||
if telegraph != nil {
|
||||
go runtime.EventsEmit(a.ctx, "telegraph", telegraph)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
go runtime.EventsEmit(a.ctx, "telegraph", refreshTelegraphList())
|
||||
go MonitorStockPrices(a)
|
||||
|
||||
//检查新版本
|
||||
go func() {
|
||||
checkUpdate(a)
|
||||
}()
|
||||
}
|
||||
|
||||
func refreshTelegraphList() *[]string {
|
||||
url := "https://www.cls.cn/telegraph"
|
||||
response, err := resty.New().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))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err)
|
||||
return &[]string{}
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
//logger.SugaredLogger.Info(string(response.Body()))
|
||||
document, err := goquery.NewDocumentFromReader(strings.NewReader(string(response.Body())))
|
||||
if err != nil {
|
||||
return &[]string{}
|
||||
}
|
||||
var telegraph []string
|
||||
document.Find("div.telegraph-content-box").Each(func(i int, selection *goquery.Selection) {
|
||||
//logger.SugaredLogger.Info(selection.Text())
|
||||
telegraph = append(telegraph, selection.Text())
|
||||
})
|
||||
return &telegraph
|
||||
}
|
||||
|
||||
// isTradingDay 判断是否是交易日
|
||||
func isTradingDay(date time.Time) bool {
|
||||
weekday := date.Weekday()
|
||||
// 判断是否是周末
|
||||
if weekday == time.Saturday || weekday == time.Sunday {
|
||||
return false
|
||||
}
|
||||
// 这里可以添加具体的节假日判断逻辑
|
||||
// 例如:判断是否是春节、国庆节等
|
||||
return true
|
||||
}
|
||||
|
||||
// isTradingTime 判断是否是交易时间
|
||||
func isTradingTime(date time.Time) bool {
|
||||
if !isTradingDay(date) {
|
||||
return false
|
||||
}
|
||||
|
||||
hour, minute, _ := date.Clock()
|
||||
|
||||
// 判断是否在9:15到11:30之间
|
||||
if (hour == 9 && minute >= 15) || (hour == 10) || (hour == 11 && minute <= 30) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 判断是否在13:00到15:00之间
|
||||
if (hour == 13) || (hour == 14) || (hour == 15 && minute <= 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func MonitorStockPrices(a *App) {
|
||||
dest := &[]data.FollowedStock{}
|
||||
db.Dao.Model(&data.FollowedStock{}).Find(dest)
|
||||
total := float64(0)
|
||||
//for _, follow := range *dest {
|
||||
// stockData := getStockInfo(follow)
|
||||
// total += stockData.ProfitAmountToday
|
||||
// price, _ := convertor.ToFloat(stockData.Price)
|
||||
// if stockData.PrePrice != price {
|
||||
// go runtime.EventsEmit(a.ctx, "stock_price", stockData)
|
||||
// }
|
||||
//}
|
||||
|
||||
// 股票信息处理逻辑
|
||||
stockInfos := GetStockInfos(*dest...)
|
||||
for _, stockInfo := range *stockInfos {
|
||||
if strutil.HasPrefixAny(stockInfo.Code, []string{"SZ", "SH", "sh", "sz"}) && (!isTradingTime(time.Now())) {
|
||||
continue
|
||||
}
|
||||
if strutil.HasPrefixAny(stockInfo.Code, []string{"hk", "HK"}) && (!IsHKTradingTime(time.Now())) {
|
||||
continue
|
||||
}
|
||||
if strutil.HasPrefixAny(stockInfo.Code, []string{"us", "US", "gb_"}) && (!IsUSTradingTime(time.Now())) {
|
||||
continue
|
||||
}
|
||||
|
||||
total += stockInfo.ProfitAmountToday
|
||||
price, _ := convertor.ToFloat(stockInfo.Price)
|
||||
|
||||
if stockInfo.PrePrice != price {
|
||||
go runtime.EventsEmit(a.ctx, "stock_price", stockInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算总收益并更新状态
|
||||
if total != 0 {
|
||||
// 使用通知替代 systray 更新 Tooltip
|
||||
title := "go-stock " + time.Now().Format(time.DateTime) + fmt.Sprintf(" %.2f¥", total)
|
||||
// title := "go-stock " + time.Now().Format(time.DateTime) + fmt.Sprintf(" %.2f¥", total)
|
||||
// systray.SetTooltip(title)
|
||||
}
|
||||
|
||||
// 发送通知显示实时数据
|
||||
err := beeep.Notify("go-stock", title, "")
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("发送通知失败: %v", err)
|
||||
go runtime.EventsEmit(a.ctx, "realtime_profit", fmt.Sprintf(" %.2f", total))
|
||||
//runtime.WindowSetTitle(a.ctx, title)
|
||||
|
||||
}
|
||||
func GetStockInfos(follows ...data.FollowedStock) *[]data.StockInfo {
|
||||
stockCodes := make([]string, 0)
|
||||
for _, follow := range follows {
|
||||
stockCodes = append(stockCodes, follow.StockCode)
|
||||
}
|
||||
stockData, err := data.NewStockDataApi().GetStockCodeRealTimeData(stockCodes...)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("get stock code real time data error:%s", err.Error())
|
||||
return nil
|
||||
}
|
||||
stockInfos := make([]data.StockInfo, 0)
|
||||
for _, info := range *stockData {
|
||||
v, ok := slice.FindBy(follows, func(idx int, follow data.FollowedStock) bool {
|
||||
return follow.StockCode == info.Code
|
||||
})
|
||||
if ok {
|
||||
addStockFollowData(v, &info)
|
||||
stockInfos = append(stockInfos, info)
|
||||
}
|
||||
}
|
||||
|
||||
// 触发实时利润事件
|
||||
go runtime.EventsEmit(a.ctx, "realtime_profit", fmt.Sprintf(" %.2f", total))
|
||||
return &stockInfos
|
||||
}
|
||||
func getStockInfo(follow data.FollowedStock) *data.StockInfo {
|
||||
stockCode := follow.StockCode
|
||||
stockDatas, err := data.NewStockDataApi().GetStockCodeRealTimeData(stockCode)
|
||||
if err != nil || len(*stockDatas) == 0 {
|
||||
return &data.StockInfo{}
|
||||
}
|
||||
stockData := (*stockDatas)[0]
|
||||
addStockFollowData(follow, &stockData)
|
||||
return &stockData
|
||||
}
|
||||
|
||||
// onReady 在应用程序准备好时调用
|
||||
func onReady(a *App) {
|
||||
// 初始化操作
|
||||
logger.SugaredLogger.Infof("onReady")
|
||||
func addStockFollowData(follow data.FollowedStock, stockData *data.StockInfo) {
|
||||
stockData.PrePrice = follow.Price //上次当前价格
|
||||
stockData.Sort = follow.Sort
|
||||
stockData.CostPrice = follow.CostPrice //成本价
|
||||
stockData.CostVolume = follow.Volume //成本量
|
||||
stockData.AlarmChangePercent = follow.AlarmChangePercent
|
||||
stockData.AlarmPrice = follow.AlarmPrice
|
||||
|
||||
// 使用 Beeep 发送通知
|
||||
err := beeep.Notify("go-stock", "应用程序已准备就绪", "")
|
||||
if err != nil {
|
||||
log.Fatalf("系统通知失败: %v", err)
|
||||
//当前价格
|
||||
price, _ := convertor.ToFloat(stockData.Price)
|
||||
//当前价格为0 时 使用卖一价格作为当前价格
|
||||
if price == 0 {
|
||||
price, _ = convertor.ToFloat(stockData.A1P)
|
||||
}
|
||||
//当前价格依然为0 时 使用买一报价作为当前价格
|
||||
if price == 0 {
|
||||
price, _ = convertor.ToFloat(stockData.B1P)
|
||||
}
|
||||
|
||||
// 显示应用窗口
|
||||
runtime.WindowShow(a.ctx)
|
||||
//昨日收盘价
|
||||
preClosePrice, _ := convertor.ToFloat(stockData.PreClose)
|
||||
|
||||
// 在 macOS 上没有系统托盘图标菜单,通常我们通过通知或其他方式提供与用户交互的界面
|
||||
//当前价格依然为0 时 使用昨日收盘价为当前价格
|
||||
if price == 0 {
|
||||
price = preClosePrice
|
||||
}
|
||||
|
||||
//今日最高价
|
||||
highPrice, _ := convertor.ToFloat(stockData.High)
|
||||
if highPrice == 0 {
|
||||
highPrice, _ = convertor.ToFloat(stockData.Open)
|
||||
}
|
||||
|
||||
//今日最低价
|
||||
lowPrice, _ := convertor.ToFloat(stockData.Low)
|
||||
if lowPrice == 0 {
|
||||
lowPrice, _ = convertor.ToFloat(stockData.Open)
|
||||
}
|
||||
//开盘价
|
||||
//openPrice, _ := convertor.ToFloat(stockData.Open)
|
||||
|
||||
if price > 0 {
|
||||
stockData.ChangePrice = mathutil.RoundToFloat(price-preClosePrice, 2)
|
||||
stockData.ChangePercent = mathutil.RoundToFloat(mathutil.Div(price-preClosePrice, preClosePrice)*100, 3)
|
||||
}
|
||||
if highPrice > 0 {
|
||||
stockData.HighRate = mathutil.RoundToFloat(mathutil.Div(highPrice-preClosePrice, preClosePrice)*100, 3)
|
||||
}
|
||||
if lowPrice > 0 {
|
||||
stockData.LowRate = mathutil.RoundToFloat(mathutil.Div(lowPrice-preClosePrice, preClosePrice)*100, 3)
|
||||
}
|
||||
if follow.CostPrice > 0 && follow.Volume > 0 {
|
||||
if price > 0 {
|
||||
stockData.Profit = mathutil.RoundToFloat(mathutil.Div(price-follow.CostPrice, follow.CostPrice)*100, 3)
|
||||
stockData.ProfitAmount = mathutil.RoundToFloat((price-follow.CostPrice)*float64(follow.Volume), 2)
|
||||
stockData.ProfitAmountToday = mathutil.RoundToFloat((price-preClosePrice)*float64(follow.Volume), 2)
|
||||
} else {
|
||||
//未开盘时当前价格为昨日收盘价
|
||||
stockData.Profit = mathutil.RoundToFloat(mathutil.Div(preClosePrice-follow.CostPrice, follow.CostPrice)*100, 3)
|
||||
stockData.ProfitAmount = mathutil.RoundToFloat((preClosePrice-follow.CostPrice)*float64(follow.Volume), 2)
|
||||
// 未开盘时,今日盈亏为 0
|
||||
stockData.ProfitAmountToday = 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//logger.SugaredLogger.Debugf("stockData:%+v", stockData)
|
||||
if follow.Price != price && price > 0 {
|
||||
go db.Dao.Model(follow).Where("stock_code = ?", follow.StockCode).Updates(map[string]interface{}{
|
||||
"price": price,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// beforeClose 在应用程序关闭前调用,显示确认对话框
|
||||
// beforeClose is called when the application is about to quit,
|
||||
// either by clicking the window close button or calling runtime.Quit.
|
||||
// Returning true will cause the application to continue, false will continue shutdown as normal.
|
||||
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
||||
defer PanicHandler()
|
||||
|
||||
// 在 macOS 上使用 MessageDialog 显示确认窗口
|
||||
dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
||||
Type: runtime.QuestionDialog,
|
||||
Title: "go-stock",
|
||||
Message: "确定关闭吗?",
|
||||
Buttons: []string{"确定", "取消"},
|
||||
Buttons: []string{"确定"},
|
||||
Icon: icon,
|
||||
CancelButton: "取消",
|
||||
})
|
||||
@@ -176,27 +311,150 @@ func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
||||
logger.SugaredLogger.Errorf("dialog error:%s", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
logger.SugaredLogger.Debugf("dialog:%s", dialog)
|
||||
if dialog == "取消" {
|
||||
return true // 如果选择了取消,不关闭应用
|
||||
} else {
|
||||
// 在 macOS 上应用退出时执行清理工作
|
||||
a.cron.Stop() // 停止定时任务
|
||||
return false // 如果选择了确定,继续关闭应用
|
||||
if dialog == "No" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func getFrameless() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getScreenResolution() (int, int, int, int, error) {
|
||||
//user32 := syscall.NewLazyDLL("user32.dll")
|
||||
//getSystemMetrics := user32.NewProc("GetSystemMetrics")
|
||||
//
|
||||
//width, _, _ := getSystemMetrics.Call(0)
|
||||
//height, _, _ := getSystemMetrics.Call(1)
|
||||
|
||||
return int(1200), int(800), 0, 0, nil
|
||||
// shutdown is called at application termination
|
||||
func (a *App) shutdown(ctx context.Context) {
|
||||
// Perform your teardown here
|
||||
// systray.Quit()
|
||||
}
|
||||
|
||||
// Greet returns a greeting for the given name
|
||||
func (a *App) Greet(stockCode string) *data.StockInfo {
|
||||
//stockInfo, _ := data.NewStockDataApi().GetStockCodeRealTimeData(stockCode)
|
||||
|
||||
follow := &data.FollowedStock{
|
||||
StockCode: stockCode,
|
||||
}
|
||||
db.Dao.Model(follow).Where("stock_code = ?", stockCode).First(follow)
|
||||
stockInfo := getStockInfo(*follow)
|
||||
return stockInfo
|
||||
}
|
||||
|
||||
func (a *App) Follow(stockCode string) string {
|
||||
return data.NewStockDataApi().Follow(stockCode)
|
||||
}
|
||||
|
||||
func (a *App) UnFollow(stockCode string) string {
|
||||
return data.NewStockDataApi().UnFollow(stockCode)
|
||||
}
|
||||
|
||||
func (a *App) GetFollowList() []data.FollowedStock {
|
||||
return data.NewStockDataApi().GetFollowList()
|
||||
}
|
||||
|
||||
func (a *App) GetStockList(key string) []data.StockBasic {
|
||||
return data.NewStockDataApi().GetStockList(key)
|
||||
}
|
||||
|
||||
func (a *App) SetCostPriceAndVolume(stockCode string, price float64, volume int64) string {
|
||||
return data.NewStockDataApi().SetCostPriceAndVolume(price, volume, stockCode)
|
||||
}
|
||||
|
||||
func (a *App) SetAlarmChangePercent(val, alarmPrice float64, stockCode string) string {
|
||||
return data.NewStockDataApi().SetAlarmChangePercent(val, alarmPrice, stockCode)
|
||||
}
|
||||
func (a *App) SetStockSort(sort int64, stockCode string) {
|
||||
data.NewStockDataApi().SetStockSort(sort, stockCode)
|
||||
}
|
||||
func (a *App) SendDingDingMessage(message string, stockCode string) string {
|
||||
ttl, _ := a.cache.TTL([]byte(stockCode))
|
||||
logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
|
||||
if ttl > 0 {
|
||||
return ""
|
||||
}
|
||||
err := a.cache.Set([]byte(stockCode), []byte("1"), 60*5)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
|
||||
return ""
|
||||
}
|
||||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
||||
}
|
||||
|
||||
// SendDingDingMessageByType msgType 报警类型: 1 涨跌报警;2 股价报警 3 成本价报警
|
||||
func (a *App) SendDingDingMessageByType(message string, stockCode string, msgType int) string {
|
||||
ttl, _ := a.cache.TTL([]byte(stockCode))
|
||||
logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
|
||||
if ttl > 0 {
|
||||
return ""
|
||||
}
|
||||
err := a.cache.Set([]byte(stockCode), []byte("1"), getMsgTypeTTL(msgType))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
|
||||
return ""
|
||||
}
|
||||
stockInfo := &data.StockInfo{}
|
||||
db.Dao.Model(stockInfo).Where("code = ?", stockCode).First(stockInfo)
|
||||
go data.NewAlertWindowsApi("go-stock消息通知", getMsgTypeName(msgType), GenNotificationMsg(stockInfo), "").SendNotification()
|
||||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
||||
}
|
||||
|
||||
func (a *App) NewChat(stock string) string {
|
||||
return data.NewDeepSeekOpenAi().NewChat(stock)
|
||||
}
|
||||
|
||||
func (a *App) NewChatStream(stock, stockCode string) {
|
||||
msgs := data.NewDeepSeekOpenAi().NewChatStream(stock, stockCode)
|
||||
for msg := range msgs {
|
||||
runtime.EventsEmit(a.ctx, "newChatStream", msg)
|
||||
}
|
||||
runtime.EventsEmit(a.ctx, "newChatStream", "DONE")
|
||||
}
|
||||
|
||||
func GenNotificationMsg(stockInfo *data.StockInfo) string {
|
||||
Price, err := convertor.ToFloat(stockInfo.Price)
|
||||
if err != nil {
|
||||
Price = 0
|
||||
}
|
||||
PreClose, err := convertor.ToFloat(stockInfo.PreClose)
|
||||
if err != nil {
|
||||
PreClose = 0
|
||||
}
|
||||
var RF float64
|
||||
if PreClose > 0 {
|
||||
RF = mathutil.RoundToFloat(((Price-PreClose)/PreClose)*100, 2)
|
||||
}
|
||||
|
||||
return "[" + stockInfo.Name + "] " + stockInfo.Price + " " + convertor.ToString(RF) + "% " + stockInfo.Date + " " + stockInfo.Time
|
||||
}
|
||||
|
||||
// msgType : 1 涨跌报警(5分钟);2 股价报警(30分钟) 3 成本价报警(30分钟)
|
||||
func getMsgTypeTTL(msgType int) int {
|
||||
switch msgType {
|
||||
case 1:
|
||||
return 60 * 5
|
||||
case 2:
|
||||
return 60 * 30
|
||||
case 3:
|
||||
return 60 * 30
|
||||
default:
|
||||
return 60 * 5
|
||||
}
|
||||
}
|
||||
|
||||
func getMsgTypeName(msgType int) string {
|
||||
switch msgType {
|
||||
case 1:
|
||||
return "涨跌报警"
|
||||
case 2:
|
||||
return "股价报警"
|
||||
case 3:
|
||||
return "成本价报警"
|
||||
default:
|
||||
return "未知类型"
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) UpdateConfig(settings *data.Settings) string {
|
||||
logger.SugaredLogger.Infof("UpdateConfig:%+v", settings)
|
||||
return data.NewSettingsApi(settings).UpdateConfig()
|
||||
}
|
||||
|
||||
func (a *App) GetConfig() *data.Settings {
|
||||
return data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||||
}
|
||||
|
||||
@@ -184,10 +184,10 @@ func getMsgTypeName(msgType int) string {
|
||||
return "未知类型"
|
||||
}
|
||||
}
|
||||
func (a *App) UpdateConfig(settingConfig *data.SettingConfig) string {
|
||||
return data.UpdateConfig(settingConfig)
|
||||
func (a *App) UpdateConfig(settings *data.Settings) string {
|
||||
return data.NewSettingsApi(settings).UpdateConfig()
|
||||
}
|
||||
|
||||
func (a *App) GetConfig() *data.SettingConfig {
|
||||
return data.GetSettingConfig()
|
||||
func (a *App) GetConfig() *data.Settings {
|
||||
return data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||||
}
|
||||
|
||||
22
app_test.go
22
app_test.go
@@ -1,11 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"go-stock/backend/models"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -27,21 +23,3 @@ func TestIsUSTradingTime(t *testing.T) {
|
||||
|
||||
t.Log(IsUSTradingTime(time.Now()))
|
||||
}
|
||||
|
||||
func TestCheckStockBaseInfo(t *testing.T) {
|
||||
db.Init("./data/stock.db")
|
||||
NewApp().CheckStockBaseInfo(context.Background())
|
||||
}
|
||||
|
||||
func TestJson(t *testing.T) {
|
||||
db.Init("./data/stock.db")
|
||||
|
||||
jsonStr := "{\n\t\t\"id\" : 3334,\n\t\t\"created_at\" : \"2025-02-28 16:49:31.8342514+08:00\",\n\t\t\"updated_at\" : \"2025-02-28 16:49:31.8342514+08:00\",\n\t\t\"deleted_at\" : null,\n\t\t\"code\" : \"PUK.US\",\n\t\t\"name\" : \"英国保诚集团\",\n\t\t\"full_name\" : \"\",\n\t\t\"e_name\" : \"\",\n\t\t\"exchange\" : \"NASDAQ\",\n\t\t\"type\" : \"stock\",\n\t\t\"is_del\" : 0,\n\t\t\"bk_name\" : null,\n\t\t\"bk_code\" : null\n\t}"
|
||||
|
||||
v := &models.StockInfoUS{}
|
||||
json.Unmarshal([]byte(jsonStr), v)
|
||||
logger.SugaredLogger.Infof("v:%+v", v)
|
||||
|
||||
db.Dao.Model(v).Updates(v)
|
||||
|
||||
}
|
||||
|
||||
214
app_windows.go
214
app_windows.go
@@ -1,214 +0,0 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
"github.com/energye/systray"
|
||||
"github.com/go-toast/toast"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"go-stock/backend/data"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"time"
|
||||
)
|
||||
|
||||
// startup is called at application startup
|
||||
func (a *App) startup(ctx context.Context) {
|
||||
defer PanicHandler()
|
||||
runtime.EventsOn(ctx, "frontendError", func(optionalData ...interface{}) {
|
||||
logger.SugaredLogger.Errorf("Frontend error: %v\n", optionalData)
|
||||
})
|
||||
logger.SugaredLogger.Infof("Version:%s", Version)
|
||||
// Perform your setup here
|
||||
a.ctx = ctx
|
||||
|
||||
// 创建系统托盘
|
||||
//systray.RunWithExternalLoop(func() {
|
||||
// onReady(a)
|
||||
//}, func() {
|
||||
// onExit(a)
|
||||
//})
|
||||
runtime.EventsOn(ctx, "updateSettings", func(optionalData ...interface{}) {
|
||||
logger.SugaredLogger.Infof("updateSettings : %v\n", optionalData)
|
||||
config := data.GetSettingConfig()
|
||||
//setMap := optionalData[0].(map[string]interface{})
|
||||
//
|
||||
//// 将 map 转换为 JSON 字节切片
|
||||
//jsonData, err := json.Marshal(setMap)
|
||||
//if err != nil {
|
||||
// logger.SugaredLogger.Errorf("Marshal error:%s", err.Error())
|
||||
// return
|
||||
//}
|
||||
//// 将 JSON 字节切片解析到结构体中
|
||||
//err = json.Unmarshal(jsonData, config)
|
||||
//if err != nil {
|
||||
// logger.SugaredLogger.Errorf("Unmarshal error:%s", err.Error())
|
||||
// return
|
||||
//}
|
||||
|
||||
logger.SugaredLogger.Infof("updateSettings config:%+v", config)
|
||||
if config.DarkTheme {
|
||||
runtime.WindowSetBackgroundColour(ctx, 27, 38, 54, 1)
|
||||
runtime.WindowSetDarkTheme(ctx)
|
||||
} else {
|
||||
runtime.WindowSetBackgroundColour(ctx, 255, 255, 255, 1)
|
||||
runtime.WindowSetLightTheme(ctx)
|
||||
}
|
||||
runtime.WindowReloadApp(ctx)
|
||||
|
||||
})
|
||||
go systray.Run(func() {
|
||||
onReady(a)
|
||||
}, func() {
|
||||
onExit(a)
|
||||
})
|
||||
|
||||
logger.SugaredLogger.Infof(" application startup Version:%s", Version)
|
||||
}
|
||||
|
||||
func OnSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) {
|
||||
notification := toast.Notification{
|
||||
AppID: "go-stock",
|
||||
Title: "go-stock",
|
||||
Message: "程序已经在运行了",
|
||||
Icon: "",
|
||||
Duration: "short",
|
||||
Audio: toast.Default,
|
||||
}
|
||||
err := notification.Push()
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err)
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
|
||||
func MonitorStockPrices(a *App) {
|
||||
dest := &[]data.FollowedStock{}
|
||||
db.Dao.Model(&data.FollowedStock{}).Find(dest)
|
||||
total := float64(0)
|
||||
//for _, follow := range *dest {
|
||||
// stockData := getStockInfo(follow)
|
||||
// total += stockData.ProfitAmountToday
|
||||
// price, _ := convertor.ToFloat(stockData.Price)
|
||||
// if stockData.PrePrice != price {
|
||||
// go runtime.EventsEmit(a.ctx, "stock_price", stockData)
|
||||
// }
|
||||
//}
|
||||
|
||||
stockInfos := GetStockInfos(*dest...)
|
||||
for _, stockInfo := range *stockInfos {
|
||||
if strutil.HasPrefixAny(stockInfo.Code, []string{"SZ", "SH", "sh", "sz"}) && (!isTradingTime(time.Now())) {
|
||||
continue
|
||||
}
|
||||
if strutil.HasPrefixAny(stockInfo.Code, []string{"hk", "HK"}) && (!IsHKTradingTime(time.Now())) {
|
||||
continue
|
||||
}
|
||||
if strutil.HasPrefixAny(stockInfo.Code, []string{"us", "US", "gb_"}) && (!IsUSTradingTime(time.Now())) {
|
||||
continue
|
||||
}
|
||||
|
||||
total += stockInfo.ProfitAmountToday
|
||||
price, _ := convertor.ToFloat(stockInfo.Price)
|
||||
|
||||
if stockInfo.PrePrice != price {
|
||||
//logger.SugaredLogger.Infof("-----------sz------------股票代码: %s, 股票名称: %s, 股票价格: %s,盘前盘后:%s", stockInfo.Code, stockInfo.Name, stockInfo.Price, stockInfo.BA)
|
||||
go runtime.EventsEmit(a.ctx, "stock_price", stockInfo)
|
||||
}
|
||||
|
||||
}
|
||||
if total != 0 {
|
||||
title := "go-stock " + time.Now().Format(time.DateTime) + fmt.Sprintf(" %.2f¥", total)
|
||||
systray.SetTooltip(title)
|
||||
}
|
||||
|
||||
go runtime.EventsEmit(a.ctx, "realtime_profit", fmt.Sprintf(" %.2f", total))
|
||||
//runtime.WindowSetTitle(a.ctx, title)
|
||||
|
||||
}
|
||||
|
||||
func onReady(a *App) {
|
||||
|
||||
// 初始化操作
|
||||
logger.SugaredLogger.Infof("systray onReady")
|
||||
systray.SetIcon(icon2)
|
||||
systray.SetTitle("go-stock")
|
||||
systray.SetTooltip("go-stock 股票行情实时获取")
|
||||
// 创建菜单项
|
||||
show := systray.AddMenuItem("显示", "显示应用程序")
|
||||
show.Click(func() {
|
||||
//logger.SugaredLogger.Infof("显示应用程序")
|
||||
runtime.WindowShow(a.ctx)
|
||||
})
|
||||
hide := systray.AddMenuItem("隐藏", "隐藏应用程序")
|
||||
hide.Click(func() {
|
||||
//logger.SugaredLogger.Infof("隐藏应用程序")
|
||||
runtime.WindowHide(a.ctx)
|
||||
})
|
||||
systray.AddSeparator()
|
||||
mQuitOrig := systray.AddMenuItem("退出", "退出应用程序")
|
||||
mQuitOrig.Click(func() {
|
||||
//logger.SugaredLogger.Infof("退出应用程序")
|
||||
runtime.Quit(a.ctx)
|
||||
})
|
||||
systray.SetOnRClick(func(menu systray.IMenu) {
|
||||
menu.ShowMenu()
|
||||
//logger.SugaredLogger.Infof("SetOnRClick")
|
||||
})
|
||||
systray.SetOnClick(func(menu systray.IMenu) {
|
||||
//logger.SugaredLogger.Infof("SetOnClick")
|
||||
menu.ShowMenu()
|
||||
})
|
||||
systray.SetOnDClick(func(menu systray.IMenu) {
|
||||
menu.ShowMenu()
|
||||
//logger.SugaredLogger.Infof("SetOnDClick")
|
||||
})
|
||||
}
|
||||
|
||||
// beforeClose is called when the application is about to quit,
|
||||
// either by clicking the window close button or calling runtime.Quit.
|
||||
// Returning true will cause the application to continue, false will continue shutdown as normal.
|
||||
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
||||
defer PanicHandler()
|
||||
|
||||
dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
||||
Type: runtime.QuestionDialog,
|
||||
Title: "go-stock",
|
||||
Message: "确定关闭吗?",
|
||||
Buttons: []string{"确定"},
|
||||
Icon: icon,
|
||||
CancelButton: "取消",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("dialog error:%s", err.Error())
|
||||
return false
|
||||
}
|
||||
logger.SugaredLogger.Debugf("dialog:%s", dialog)
|
||||
if dialog == "No" {
|
||||
return true
|
||||
} else {
|
||||
systray.Quit()
|
||||
a.cron.Stop()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func getFrameless() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func getScreenResolution() (int, int, int, int, error) {
|
||||
//user32 := syscall.NewLazyDLL("user32.dll")
|
||||
//getSystemMetrics := user32.NewProc("GetSystemMetrics")
|
||||
//
|
||||
//width, _, _ := getSystemMetrics.Call(0)
|
||||
//height, _, _ := getSystemMetrics.Call(1)
|
||||
|
||||
return int(1366), int(768), 1456, 768, nil
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
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())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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{}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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])
|
||||
}
|
||||
|
||||
//北京证券交易所 8(83、87、88 等) 创新型中小企业(专精特新为主)
|
||||
//上海证券交易所 6(60、688 等) 大盘蓝筹、科创板(高新技术)
|
||||
//深圳证券交易所 0、3(000、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
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func NewAlertWindowsApi(AppID string, Title string, Content string, Icon string)
|
||||
}
|
||||
|
||||
func (a AlertWindowsApi) SendNotification() bool {
|
||||
if GetSettingConfig().LocalPushEnable == false {
|
||||
if GetConfig().LocalPushEnable == false {
|
||||
logger.SugaredLogger.Error("本地推送未开启")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"go-stock/backend/logger"
|
||||
|
||||
"github.com/go-toast/toast"
|
||||
"go-stock/backend/logger"
|
||||
)
|
||||
|
||||
// AlertWindowsApi @Author spark
|
||||
@@ -33,7 +31,7 @@ func NewAlertWindowsApi(AppID string, Title string, Content string, Icon string)
|
||||
}
|
||||
|
||||
func (a AlertWindowsApi) SendNotification() bool {
|
||||
if GetSettingConfig().LocalPushEnable == false {
|
||||
if GetConfig().LocalPushEnable == false {
|
||||
logger.SugaredLogger.Error("本地推送未开启")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
//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
|
||||
|
||||
@@ -27,7 +27,7 @@ func (c *CrawlerApi) NewCrawler(ctx context.Context, crawlerBaseInfo CrawlerBase
|
||||
return CrawlerApi{
|
||||
crawlerCtx: ctx,
|
||||
crawlerBaseInfo: crawlerBaseInfo,
|
||||
pool: NewBrowserPool(GetSettingConfig().BrowserPoolSize),
|
||||
pool: NewBrowserPool(GetConfig().BrowserPoolSize),
|
||||
}
|
||||
}
|
||||
func (c *CrawlerApi) GetHtml(url, waitVisible string, headless bool) (string, bool) {
|
||||
@@ -39,7 +39,7 @@ func (c *CrawlerApi) GetHtml(url, waitVisible string, headless bool) (string, bo
|
||||
}
|
||||
func (c *CrawlerApi) GetHtml_old(url, waitVisible string, headless bool) (string, bool) {
|
||||
htmlContent := ""
|
||||
path := GetSettingConfig().BrowserPath
|
||||
path := GetConfig().BrowserPath
|
||||
//logger.SugaredLogger.Infof("Browser path:%s", path)
|
||||
if path != "" {
|
||||
pctx, pcancel := chromedp.NewExecAllocator(
|
||||
@@ -102,7 +102,7 @@ func (c *CrawlerApi) GetHtml_old(url, waitVisible string, headless bool) (string
|
||||
|
||||
func (c *CrawlerApi) GetHtmlWithNoCancel(url, waitVisible string, headless bool) (html string, ok bool, parent context.CancelFunc, child context.CancelFunc) {
|
||||
htmlContent := ""
|
||||
path := GetSettingConfig().BrowserPath
|
||||
path := GetConfig().BrowserPath
|
||||
//logger.SugaredLogger.Infof("BrowserPath :%s", path)
|
||||
var parentCancel context.CancelFunc
|
||||
var childCancel context.CancelFunc
|
||||
@@ -170,7 +170,7 @@ func (c *CrawlerApi) GetHtmlWithActions(actions *[]chromedp.Action, headless boo
|
||||
htmlContent := ""
|
||||
*actions = append(*actions, chromedp.InnerHTML("body", &htmlContent))
|
||||
|
||||
path := GetSettingConfig().BrowserPath
|
||||
path := GetConfig().BrowserPath
|
||||
//logger.SugaredLogger.Infof("GetHtmlWithActions path:%s", path)
|
||||
if path != "" {
|
||||
pctx, pcancel := chromedp.NewExecAllocator(
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Some dict/zh data is from [github.com/fxsjy/jieba](https://github.com/fxsjy/jieba)
|
||||
@@ -1,746 +0,0 @@
|
||||
# 金融股票全场景分词字典(去重优化版)
|
||||
# 格式:单词 权重 词性 | 权重280-350分,核心术语优先匹配,无重复词汇
|
||||
# 覆盖:净买卖、股指、财务指标、交易操作、政策宏观、热点概念等全场景
|
||||
|
||||
# 一、净买卖与资金流向(核心交易表述)
|
||||
净卖出 340 v
|
||||
净买入 340 v
|
||||
净卖出额 330 n
|
||||
净买入额 330 n
|
||||
净卖出量 330 n
|
||||
净买入量 330 n
|
||||
资金净流出 340 n
|
||||
资金净流入 340 n
|
||||
净额 330 n
|
||||
买卖净额 330 n
|
||||
资金净额 330 n
|
||||
北向资金净买入 330 n
|
||||
北向资金净卖出 330 n
|
||||
南向资金净买入 320 n
|
||||
南向资金净卖出 320 n
|
||||
主力资金净买入 330 n
|
||||
主力资金净卖出 330 n
|
||||
散户资金净买入 320 n
|
||||
散户资金净卖出 320 n
|
||||
机构资金净买入 330 n
|
||||
机构资金净卖出 330 n
|
||||
游资净买入 320 n
|
||||
游资净卖出 320 n
|
||||
大单净买入 320 n
|
||||
大单净卖出 320 n
|
||||
中单净买入 320 n
|
||||
中单净卖出 320 n
|
||||
小单净买入 320 n
|
||||
小单净卖出 320 n
|
||||
净买入占比 320 n
|
||||
净卖出占比 320 n
|
||||
净买入率 320 n
|
||||
净卖出率 320 n
|
||||
连续净买入 320 v
|
||||
连续净卖出 320 v
|
||||
单日净买入 320 n
|
||||
单日净卖出 320 n
|
||||
累计净买入 320 n
|
||||
累计净卖出 320 n
|
||||
净买入创纪录 310 adj
|
||||
净卖出创纪录 310 adj
|
||||
净买入放量 310 v
|
||||
净卖出放量 310 v
|
||||
净买入缩量 310 v
|
||||
净卖出缩量 310 v
|
||||
净多 310 n
|
||||
净空 310 n
|
||||
净多头 310 n
|
||||
净空头 310 n
|
||||
净多头头寸 310 n
|
||||
净空头头寸 310 n
|
||||
跌超 310 n
|
||||
跌逾 310 n
|
||||
|
||||
# 二、金融资讯与市场分析
|
||||
金融资讯 350 n
|
||||
市场快讯 340 n
|
||||
财经新闻 340 n
|
||||
政策解读 330 n
|
||||
市场分析 330 n
|
||||
行业研报 320 n
|
||||
宏观经济 330 n
|
||||
微观层面 310 n
|
||||
基本面 320 n
|
||||
技术面 320 n
|
||||
资金面 320 n
|
||||
政策面 320 n
|
||||
市场情绪 320 n
|
||||
风险偏好 310 n
|
||||
流动性 320 n
|
||||
估值修复 310 n
|
||||
价值投资 310 n
|
||||
趋势投资 310 n
|
||||
波段操作 310 n
|
||||
左侧交易 290 n
|
||||
右侧交易 290 n
|
||||
止损止盈 300 n
|
||||
仓位管理 300 n
|
||||
资产配置 310 n
|
||||
分散投资 290 n
|
||||
集中投资 290 n
|
||||
风险控制 310 n
|
||||
系统性风险 300 n
|
||||
非系统性风险 290 n
|
||||
黑天鹅事件 310 n
|
||||
灰犀牛事件 300 n
|
||||
熔断机制 300 n
|
||||
市场监管 310 n
|
||||
信息披露 310 n
|
||||
内幕交易 300 n
|
||||
操纵市场 300 n
|
||||
|
||||
# 三、全球主要股指(含中英文缩写)
|
||||
# 中国市场
|
||||
A股 350 n
|
||||
港股 350 n
|
||||
上证指数 350 n
|
||||
深证成指 350 n
|
||||
创业板指 340 n
|
||||
科创板指 330 n
|
||||
北证50 330 n
|
||||
沪深300 350 n
|
||||
沪深300指数 350 n
|
||||
中证500 340 n
|
||||
中证500指数 340 n
|
||||
中证1000 330 n
|
||||
中证1000指数 330 n
|
||||
上证50 340 n
|
||||
上证50指数 340 n
|
||||
科创50 330 n
|
||||
科创50指数 330 n
|
||||
上证综指 350 n
|
||||
富时中国A50指数 340 n
|
||||
FTSE China A50 330 n
|
||||
恒生指数 340 n
|
||||
HSI 330 n
|
||||
恒生科技指数 340 n
|
||||
恒生国企指数 330 n
|
||||
H股指数 330 n
|
||||
# 美洲市场
|
||||
道琼斯工业平均指数 350 n
|
||||
DJIA 340 n
|
||||
标普500指数 350 n
|
||||
S&P 500 340 n
|
||||
纳斯达克综合指数 340 n
|
||||
纳斯达克100指数 340 n
|
||||
Nasdaq 100 330 n
|
||||
罗素2000指数 320 n
|
||||
Russell 2000 310 n
|
||||
标普400中型股指数 310 n
|
||||
标普600小型股指数 310 n
|
||||
纽约证交所综合指数 310 n
|
||||
NYSE Composite 300 n
|
||||
# 欧洲市场
|
||||
德国DAX指数 330 n
|
||||
DAX 30 320 n
|
||||
法国CAC40指数 330 n
|
||||
CAC 40 320 n
|
||||
富时100指数 330 n
|
||||
FTSE 100 320 n
|
||||
欧元斯托克50指数 320 n
|
||||
Euro Stoxx 50 310 n
|
||||
英国富时250指数 310 n
|
||||
FTSE 250 300 n
|
||||
意大利富时MIB指数 310 n
|
||||
FTSE MIB 300 n
|
||||
西班牙IBEX 35指数 310 n
|
||||
IBEX 35 300 n
|
||||
# 亚太其他市场
|
||||
日经225指数 330 n
|
||||
Nikkei 225 320 n
|
||||
日经500指数 310 n
|
||||
韩国综合股价指数 320 n
|
||||
韩国kospi指数 320 n
|
||||
KOSPI 310 n
|
||||
澳洲标普200指数 310 n
|
||||
S&P/ASX 200 300 n
|
||||
印度孟买敏感指数 310 n
|
||||
Sensex 300 n
|
||||
印度Nifty 50指数 310 n
|
||||
Nifty 50 300 n
|
||||
# 全球综合指数
|
||||
MSCI指数 320 n
|
||||
MSCI全球指数 330 n
|
||||
MSCI World Index 320 n
|
||||
MSCI新兴市场指数 330 n
|
||||
MSCI Emerging Markets 320 n
|
||||
富时罗素全球指数 320 n
|
||||
FTSE Russell Global Index 310 n
|
||||
摩根大通全球债券指数 310 n
|
||||
全球股指 300 n
|
||||
发达市场指数 300 n
|
||||
新兴市场指数 300 n
|
||||
金砖国家指数 300 n
|
||||
G20国家指数 300 n
|
||||
# 股指衍生工具
|
||||
指数期货 320 n
|
||||
股指期货 320 n
|
||||
富时中国A50指数期货 320 n
|
||||
沪深300股指期货 320 n
|
||||
标普500股指期货 320 n
|
||||
纳斯达克100股指期货 310 n
|
||||
指数成分股 320 n
|
||||
指数权重股 320 n
|
||||
指数涨幅 320 n
|
||||
指数跌幅 320 n
|
||||
指数反弹 310 n
|
||||
指数回调 310 n
|
||||
指数创新高 310 v
|
||||
指数创新低 310 v
|
||||
指数估值 310 n
|
||||
指数市盈率 310 n
|
||||
|
||||
# 四、A股龙头公司(资讯高频)
|
||||
贵州茅台 310 n
|
||||
宁德时代 310 n
|
||||
比亚迪 300 n
|
||||
隆基绿能 300 n
|
||||
长江电力 290 n
|
||||
中国平安 300 n
|
||||
招商银行 300 n
|
||||
五粮液 290 n
|
||||
美的集团 290 n
|
||||
格力电器 290 n
|
||||
海康威视 290 n
|
||||
迈瑞医疗 290 n
|
||||
恒瑞医药 290 n
|
||||
中芯国际 300 n
|
||||
中兴通讯 290 n
|
||||
东方财富 290 n
|
||||
爱尔眼科 290 n
|
||||
通威股份 290 n
|
||||
药明康德 290 n
|
||||
阳光电源 290 n
|
||||
天齐锂业 290 n
|
||||
赣锋锂业 290 n
|
||||
中国中免 290 n
|
||||
海螺水泥 280 n
|
||||
万科A 280 n
|
||||
保利发展 280 n
|
||||
招商蛇口 280 n
|
||||
上汽集团 280 n
|
||||
宝钢股份 280 n
|
||||
|
||||
# 五、财务与估值核心指标
|
||||
市盈率 350 n
|
||||
PE 350 n
|
||||
动态市盈率 340 n
|
||||
静态市盈率 340 n
|
||||
滚动市盈率 340 n
|
||||
市净率 350 n
|
||||
PB 350 n
|
||||
市销率 330 n
|
||||
PS 330 n
|
||||
市现率 320 n
|
||||
PCF 320 n
|
||||
净资产收益率 350 n
|
||||
ROE 350 n
|
||||
总资产收益率 330 n
|
||||
ROA 330 n
|
||||
毛利率 340 n
|
||||
净利率 340 n
|
||||
销售净利率 330 n
|
||||
资产负债率 340 n
|
||||
营收 340 n
|
||||
营业收入 340 n
|
||||
净利润 350 n
|
||||
归母净利润 340 n
|
||||
扣非净利润 340 n
|
||||
EPS 330 n
|
||||
每股收益 330 n
|
||||
现金流 340 n
|
||||
经营活动现金流 330 n
|
||||
自由现金流 330 n
|
||||
营收增长率 330 n
|
||||
净利润增长率 330 n
|
||||
股息率 320 n
|
||||
分红率 320 n
|
||||
换手率 330 n
|
||||
成交量 340 n
|
||||
成交额 340 n
|
||||
量比 320 n
|
||||
振幅 320 n
|
||||
|
||||
# 六、政策与宏观经济
|
||||
货币政策 330 n
|
||||
财政政策 330 n
|
||||
稳健货币政策 320 n
|
||||
积极财政政策 320 n
|
||||
宽松政策 320 n
|
||||
紧缩政策 320 n
|
||||
利率 330 n
|
||||
基准利率 320 n
|
||||
LPR 330 n
|
||||
贷款市场报价利率 320 n
|
||||
存款准备金率 320 n
|
||||
MLF 320 n
|
||||
中期借贷便利 310 n
|
||||
逆回购 320 n
|
||||
正回购 310 n
|
||||
汇率 330 n
|
||||
人民币汇率 330 n
|
||||
美元汇率 320 n
|
||||
通胀 320 n
|
||||
CPI 330 n
|
||||
PPI 330 n
|
||||
GDP 330 n
|
||||
国内生产总值 320 n
|
||||
PMI 330 n
|
||||
采购经理人指数 320 n
|
||||
行业政策 320 n
|
||||
产业政策 320 n
|
||||
税收政策 310 n
|
||||
补贴政策 310 n
|
||||
关税 310 n
|
||||
贸易政策 310 n
|
||||
地缘政治 310 n
|
||||
大宗商品 320 n
|
||||
原油价格 310 n
|
||||
黄金价格 310 n
|
||||
有色金属价格 300 n
|
||||
|
||||
# 七、金融产品与机构
|
||||
股票 320 n
|
||||
基金 320 n
|
||||
公募基金 310 n
|
||||
私募基金 310 n
|
||||
ETF 320 n
|
||||
指数基金 310 n
|
||||
混合型基金 300 n
|
||||
股票型基金 310 n
|
||||
债券型基金 300 n
|
||||
货币基金 290 n
|
||||
REITs 310 n
|
||||
可转债 310 n
|
||||
可交换债 300 n
|
||||
期货 310 n
|
||||
股指期货 310 n
|
||||
国债期货 300 n
|
||||
商品期货 300 n
|
||||
期权 300 n
|
||||
融资融券 310 n
|
||||
两融余额 300 n
|
||||
北向资金 320 n
|
||||
南向资金 310 n
|
||||
沪股通 310 n
|
||||
深股通 310 n
|
||||
陆股通 310 n
|
||||
证券公司 310 n
|
||||
券商 320 n
|
||||
基金公司 300 n
|
||||
保险公司 300 n
|
||||
银行 310 n
|
||||
监管机构 310 n
|
||||
证监会 320 n
|
||||
交易所 320 n
|
||||
上交所 320 n
|
||||
深交所 320 n
|
||||
北交所 310 n
|
||||
港交所 310 n
|
||||
社保基金 310 n
|
||||
养老金 300 n
|
||||
QFII 300 n
|
||||
RQFII 290 n
|
||||
北向资金机构 300 n
|
||||
|
||||
# 八、热点概念与行业
|
||||
AI 330 n
|
||||
人工智能 330 n
|
||||
算力 330 n
|
||||
大数据 320 n
|
||||
云计算 320 n
|
||||
半导体 320 n
|
||||
芯片 320 n
|
||||
集成电路 310 n
|
||||
新能源 320 n
|
||||
光伏 320 n
|
||||
锂电 320 n
|
||||
储能 320 n
|
||||
充电桩 310 n
|
||||
新能源车 320 n
|
||||
智能汽车 310 n
|
||||
自动驾驶 310 n
|
||||
军工 310 n
|
||||
国防军工 300 n
|
||||
医药 310 n
|
||||
创新药 310 n
|
||||
医疗器械 300 n
|
||||
CXO 300 n
|
||||
白酒 310 n
|
||||
消费 320 n
|
||||
可选消费 300 n
|
||||
必选消费 300 n
|
||||
食品饮料 310 n
|
||||
家电 300 n
|
||||
地产 300 n
|
||||
房地产 300 n
|
||||
基建 300 n
|
||||
新基建 310 n
|
||||
数字经济 320 n
|
||||
数字货币 310 n
|
||||
区块链 300 n
|
||||
元宇宙 300 n
|
||||
低空经济 310 n
|
||||
人形机器人 310 n
|
||||
工业互联网 300 n
|
||||
物联网 300 n
|
||||
5G 300 n
|
||||
6G 300 n
|
||||
|
||||
# 九、交易操作与行情
|
||||
上涨 310 v
|
||||
下跌 310 v
|
||||
涨停 310 v
|
||||
跌停 310 v
|
||||
反弹 300 v
|
||||
反转 300 v
|
||||
回调 300 v
|
||||
横盘 290 v
|
||||
震荡 290 v
|
||||
跳水 300 v
|
||||
拉升 300 v
|
||||
砸盘 300 v
|
||||
护盘 290 v
|
||||
建仓 300 v
|
||||
加仓 300 v
|
||||
减仓 300 v
|
||||
清仓 300 v
|
||||
平仓 300 v
|
||||
抄底 300 v
|
||||
逃顶 300 v
|
||||
追涨 290 v
|
||||
杀跌 290 v
|
||||
套牢 280 v
|
||||
解套 280 v
|
||||
净流入 300 n
|
||||
净流出 300 n
|
||||
主力资金 300 n
|
||||
资金流入 290 v
|
||||
资金流出 290 v
|
||||
放量 290 v
|
||||
缩量 290 v
|
||||
高换手 290 n
|
||||
低换手 280 n
|
||||
高估值 290 n
|
||||
低估值 290 n
|
||||
超预期 300 v
|
||||
不及预期 300 v
|
||||
符合预期 290 v
|
||||
利好 310 n
|
||||
利空 310 n
|
||||
政策利好 310 n
|
||||
业绩利好 310 n
|
||||
风险警示 300 n
|
||||
涨停板 300 n
|
||||
跌停板 300 n
|
||||
一字涨停 290 n
|
||||
一字跌停 290 n
|
||||
打开涨停 280 v
|
||||
打开跌停 280 v
|
||||
集合竞价 290 n
|
||||
连续竞价 280 n
|
||||
开盘价 340 n
|
||||
收盘价 340 n
|
||||
最高价 330 n
|
||||
最低价 330 n
|
||||
均价 330 n
|
||||
昨日收盘价 320 n
|
||||
涨跌额 330 n
|
||||
涨跌幅 340 n
|
||||
涨幅 340 n
|
||||
跌幅 340 n
|
||||
涨停价 330 n
|
||||
跌停价 330 n
|
||||
打开涨停 320 v
|
||||
打开跌停 320 v
|
||||
熔断 330 n
|
||||
临时停牌 320 n
|
||||
复牌 320 v
|
||||
停牌 320 n
|
||||
量价齐升 320 n
|
||||
量价背离 320 n
|
||||
高开 320 n
|
||||
低开 320 n
|
||||
平开 320 n
|
||||
高走 320 v
|
||||
低走 320 v
|
||||
震荡上行 320 v
|
||||
震荡下行 320 v
|
||||
|
||||
# 十、委托交易与规则
|
||||
限价委托 340 n
|
||||
市价委托 340 n
|
||||
止损委托 330 n
|
||||
止盈委托 330 n
|
||||
预埋单 320 n
|
||||
条件单 330 n
|
||||
触发委托 320 n
|
||||
追涨委托 320 n
|
||||
抄底委托 320 n
|
||||
挂单 330 n
|
||||
撤单 330 v
|
||||
成交 340 v
|
||||
未成交 320 adj
|
||||
部分成交 320 adj
|
||||
委托价 320 n
|
||||
成交价 320 n
|
||||
委托量 320 n
|
||||
买单 330 n
|
||||
卖单 330 n
|
||||
买入 340 v
|
||||
卖出 340 v
|
||||
做多 330 v
|
||||
做空 330 v
|
||||
开仓 330 v
|
||||
满仓 330 v
|
||||
空仓 330 v
|
||||
半仓 320 v
|
||||
轻仓 320 v
|
||||
重仓 320 v
|
||||
底仓 320 n
|
||||
补仓 320 v
|
||||
T+1交易 330 n
|
||||
T+0交易 330 n
|
||||
日内交易 320 n
|
||||
短线交易 320 n
|
||||
中线交易 320 n
|
||||
长线交易 320 n
|
||||
集合竞价交易 320 n
|
||||
连续竞价交易 320 n
|
||||
保证金 320 n
|
||||
杠杆 320 n
|
||||
融资 320 n
|
||||
融券 320 n
|
||||
融资买入 320 v
|
||||
融券卖出 320 v
|
||||
融资余额 320 n
|
||||
融券余额 320 n
|
||||
两融业务 320 n
|
||||
信用账户 320 n
|
||||
普通账户 320 n
|
||||
资金账户 320 n
|
||||
证券账户 320 n
|
||||
持仓 330 n
|
||||
持仓股 320 n
|
||||
持仓数量 320 n
|
||||
可用资金 320 n
|
||||
可取资金 320 n
|
||||
冻结资金 320 n
|
||||
交易成本 320 n
|
||||
手续费 320 n
|
||||
佣金 320 n
|
||||
印花税 320 n
|
||||
过户费 320 n
|
||||
交易规费 320 n
|
||||
B股 310 n
|
||||
H股 310 n
|
||||
美股 310 n
|
||||
创业板 320 n
|
||||
科创板 320 n
|
||||
主板 320 n
|
||||
纳斯达克 310 n
|
||||
纽交所 310 n
|
||||
标普500 310 n
|
||||
道琼斯 310 n
|
||||
成分股 310 n
|
||||
权重股 310 n
|
||||
龙头股 310 n
|
||||
中小盘股 310 n
|
||||
大盘股 310 n
|
||||
小盘股 310 n
|
||||
ST股 320 n
|
||||
*ST股 320 n
|
||||
退市股 320 n
|
||||
次新股 320 n
|
||||
新股 320 n
|
||||
打新 320 v
|
||||
新股申购 320 n
|
||||
中签 320 v
|
||||
新股上市 320 n
|
||||
限售股 310 n
|
||||
解禁 310 v
|
||||
股权登记日 310 n
|
||||
除权除息日 310 n
|
||||
派息 310 n
|
||||
分红 310 n
|
||||
送股 310 n
|
||||
转增股 310 n
|
||||
配股 310 n
|
||||
除权 310 n
|
||||
除息 310 n
|
||||
填权 310 v
|
||||
贴权 310 v
|
||||
筹码分析 310 n
|
||||
盘口分析 310 n
|
||||
K线图 310 n
|
||||
均线 310 n
|
||||
日均线 310 n
|
||||
周均线 310 n
|
||||
月均线 310 n
|
||||
MACD 310 n
|
||||
KDJ 310 n
|
||||
RSI 310 n
|
||||
布林带 310 n
|
||||
成交量均线 310 n
|
||||
支撑位 310 n
|
||||
压力位 310 n
|
||||
阻力位 310 n
|
||||
突破 310 v
|
||||
跌破 310 v
|
||||
站稳 310 v
|
||||
回落 310 v
|
||||
横盘整理 310 n
|
||||
震荡整理 310 n
|
||||
洗盘 310 n
|
||||
吸筹 310 n
|
||||
出货 310 n
|
||||
建仓成本 310 n
|
||||
持仓周期 310 n
|
||||
交易频率 310 n
|
||||
长线持有 310 v
|
||||
高抛低吸 310 v
|
||||
追涨杀跌 310 v
|
||||
低吸高抛 310 v
|
||||
顺势而为 310 n
|
||||
逆向投资 310 n
|
||||
交易软件 300 n
|
||||
行情软件 300 n
|
||||
交易终端 300 n
|
||||
手机炒股 300 n
|
||||
电脑炒股 300 n
|
||||
网上交易 300 n
|
||||
电话委托 290 n
|
||||
营业部交易 290 n
|
||||
交易系统 300 n
|
||||
行情系统 300 n
|
||||
Level-2行情 300 n
|
||||
实时行情 300 n
|
||||
延时行情 290 n
|
||||
交易接口 300 n
|
||||
量化交易 310 n
|
||||
算法交易 300 n
|
||||
程序化交易 300 n
|
||||
自动交易 300 n
|
||||
智能投顾 300 n
|
||||
券商APP 300 n
|
||||
交易佣金 300 n
|
||||
开户 300 v
|
||||
销户 290 v
|
||||
转户 290 v
|
||||
绑定银行卡 290 v
|
||||
银证转账 300 n
|
||||
银证通 290 n
|
||||
第三方存管 290 n
|
||||
赛道股 330 n
|
||||
抱团股 310 n
|
||||
妖股 310 n
|
||||
庄股 310 n
|
||||
# 主要财经网站与机构名词(格式:单词 权重 词性)
|
||||
# 权重320-350分,与核心金融术语优先级一致,确保精准识别
|
||||
|
||||
# 一、国内财经网站(资讯高频来源)
|
||||
东方财富网 350 n
|
||||
同花顺财经 340 n
|
||||
财新网 340 n
|
||||
新浪财经 340 n
|
||||
第一财经 330 n
|
||||
金融界 330 n
|
||||
华尔街见闻 330 n
|
||||
每日经济新闻 330 n
|
||||
证券时报网 330 n
|
||||
财联社 330 n
|
||||
和讯网 320 n
|
||||
证券之星 320 n
|
||||
中国证券报 330 n
|
||||
上海证券报 330 n
|
||||
证券日报 320 n
|
||||
界面新闻 320 n
|
||||
澎湃新闻财经 320 n
|
||||
腾讯财经 320 n
|
||||
网易财经 320 n
|
||||
凤凰财经 320 n
|
||||
|
||||
# 二、国际财经媒体(全球市场资讯来源)
|
||||
彭博社 350 n
|
||||
路透社 350 n
|
||||
金融时报 340 n
|
||||
华尔街日报 340 n
|
||||
雅虎财经 320 n
|
||||
CNBC 320 n
|
||||
路透财经 330 n
|
||||
彭博财经 330 n
|
||||
英国金融时报 330 n
|
||||
美国消费者新闻与商业频道 320 n
|
||||
日经新闻 320 n
|
||||
韩国经济新闻 310 n
|
||||
|
||||
# 三、国际金融机构(投行/基金/银行)
|
||||
高盛集团 350 n
|
||||
摩根士丹利 350 n
|
||||
摩根大通 350 n
|
||||
瑞银集团 340 n
|
||||
汇丰银行 340 n
|
||||
野村证券 330 n
|
||||
贝莱德 350 n
|
||||
桥水基金 340 n
|
||||
黑石集团 340 n
|
||||
橡树资本 330 n
|
||||
花旗集团 330 n
|
||||
美银美林 330 n
|
||||
德意志银行 320 n
|
||||
瑞士信贷 320 n
|
||||
法国巴黎银行 320 n
|
||||
三菱日联金融集团 310 n
|
||||
|
||||
# 四、国内外金融监管与交易机构
|
||||
中国证监会 350 n
|
||||
美联储 350 n
|
||||
英国金融行为管理局 330 n
|
||||
香港证监会 330 n
|
||||
纽约证券交易所 350 n
|
||||
纳斯达克 350 n
|
||||
香港交易所 340 n
|
||||
伦敦证券交易所 340 n
|
||||
芝商所集团 330 n
|
||||
泛欧证券交易所 330 n
|
||||
上海证券交易所 340 n
|
||||
深圳证券交易所 340 n
|
||||
北京证券交易所 330 n
|
||||
中国人民银行 350 n
|
||||
银保监会 340 n
|
||||
国家金融监督管理总局 340 n
|
||||
财政部 340 n
|
||||
发改委 330 n
|
||||
石油输出国组织 340 n
|
||||
国际能源署 330 n
|
||||
美国能源信息署 320 n
|
||||
世界银行 330 n
|
||||
国际货币基金组织 330 n
|
||||
|
||||
# 五、国内核心金融机构(券商/基金/银行)
|
||||
中国工商银行 340 n
|
||||
中国建设银行 340 n
|
||||
中国农业银行 340 n
|
||||
中国银行 340 n
|
||||
交通银行 330 n
|
||||
招商银行 330 n
|
||||
兴业银行 320 n
|
||||
浦发银行 320 n
|
||||
中信证券 340 n
|
||||
华泰证券 330 n
|
||||
中金公司 330 n
|
||||
中信建投 330 n
|
||||
国泰君安 330 n
|
||||
广发证券 320 n
|
||||
东方证券 320 n
|
||||
南方基金 330 n
|
||||
易方达基金 330 n
|
||||
华夏基金 330 n
|
||||
嘉实基金 320 n
|
||||
博时基金 320 n
|
||||
@@ -1 +0,0 @@
|
||||
dict.txt 通过内部工具生成, Copyright 2017 ego authors. 商用和拷贝请注明来源和版权
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,88 +0,0 @@
|
||||
,
|
||||
.
|
||||
?
|
||||
!
|
||||
"
|
||||
@
|
||||
,
|
||||
。
|
||||
、
|
||||
?
|
||||
!
|
||||
:
|
||||
“
|
||||
”
|
||||
;
|
||||
|
||||
(
|
||||
)
|
||||
《
|
||||
》
|
||||
~
|
||||
*
|
||||
<
|
||||
>
|
||||
/
|
||||
\
|
||||
|
|
||||
-
|
||||
_
|
||||
+
|
||||
=
|
||||
&
|
||||
^
|
||||
%
|
||||
#
|
||||
`
|
||||
;
|
||||
$
|
||||
¥
|
||||
‘
|
||||
’
|
||||
〉
|
||||
〈
|
||||
…
|
||||
>
|
||||
<
|
||||
@
|
||||
#
|
||||
$
|
||||
%
|
||||
︿
|
||||
&
|
||||
*
|
||||
+
|
||||
~
|
||||
|
|
||||
[
|
||||
]
|
||||
{
|
||||
}
|
||||
啊
|
||||
阿
|
||||
哎
|
||||
哎呀
|
||||
哎哟
|
||||
唉
|
||||
俺
|
||||
俺们
|
||||
按
|
||||
按照
|
||||
吧
|
||||
吧哒
|
||||
把
|
||||
罢了
|
||||
被
|
||||
本
|
||||
本着
|
||||
比
|
||||
比方
|
||||
比如
|
||||
鄙人
|
||||
彼
|
||||
彼此
|
||||
边
|
||||
别
|
||||
别的
|
||||
别说
|
||||
并
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ func NewDingDingAPI() *DingDingAPI {
|
||||
}
|
||||
|
||||
func (DingDingAPI) SendDingDingMessage(message string) string {
|
||||
if GetSettingConfig().DingPushEnable == false {
|
||||
if GetConfig().DingPushEnable == false {
|
||||
//logger.SugaredLogger.Info("钉钉推送未开启")
|
||||
return "钉钉推送未开启"
|
||||
}
|
||||
@@ -37,9 +37,11 @@ func (DingDingAPI) SendDingDingMessage(message string) string {
|
||||
logger.SugaredLogger.Infof("send dingding message: %s", resp.String())
|
||||
return "发送钉钉消息成功"
|
||||
}
|
||||
|
||||
func GetConfig() *Settings {
|
||||
return NewSettingsApi(&Settings{}).GetConfig()
|
||||
}
|
||||
func getApiURL() string {
|
||||
return GetSettingConfig().DingRobot
|
||||
return GetConfig().DingRobot
|
||||
}
|
||||
|
||||
func (DingDingAPI) SendToDingDing(title, message string) string {
|
||||
|
||||
@@ -20,13 +20,13 @@ import (
|
||||
|
||||
type FundApi struct {
|
||||
client *resty.Client
|
||||
config *SettingConfig
|
||||
config *Settings
|
||||
}
|
||||
|
||||
func NewFundApi() *FundApi {
|
||||
return &FundApi{
|
||||
client: resty.New(),
|
||||
config: GetSettingConfig(),
|
||||
config: GetConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,6 @@ 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"
|
||||
@@ -20,6 +12,12 @@ 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
@@ -33,79 +31,12 @@ func NewMarketNewsApi() *MarketNewsApi {
|
||||
return &MarketNewsApi{}
|
||||
}
|
||||
|
||||
func (m MarketNewsApi) TelegraphList(crawlTimeOut int64) *[]models.Telegraph {
|
||||
//https://www.cls.cn/nodeapi/telegraphList
|
||||
url := "https://www.cls.cn/nodeapi/telegraphList"
|
||||
res := map[string]any{}
|
||||
_, _ = 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").
|
||||
SetResult(&res).
|
||||
Get(url)
|
||||
var telegraphs []models.Telegraph
|
||||
|
||||
if v, _ := convertor.ToInt(res["error"]); v == 0 {
|
||||
if res["data"] == nil {
|
||||
return m.GetNewTelegraph(30)
|
||||
}
|
||||
data := res["data"].(map[string]any)
|
||||
rollData := data["roll_data"].([]any)
|
||||
for _, v := range rollData {
|
||||
news := v.(map[string]any)
|
||||
ctime, _ := convertor.ToInt(news["ctime"])
|
||||
dataTime := time.Unix(ctime, 0)
|
||||
logger.SugaredLogger.Debugf("dataTime: %s", dataTime)
|
||||
telegraph := models.Telegraph{
|
||||
Content: news["content"].(string),
|
||||
Time: dataTime.Format("15:04:05"),
|
||||
DataTime: &dataTime,
|
||||
Url: news["shareurl"].(string),
|
||||
Source: "财联社电报",
|
||||
IsRed: GetLevel(news["level"].(string)),
|
||||
SentimentResult: AnalyzeSentiment(news["content"].(string)).Description,
|
||||
}
|
||||
cnt := int64(0)
|
||||
db.Dao.Model(telegraph).Where("time=? and content=?", telegraph.Time, telegraph.Content).Count(&cnt)
|
||||
if cnt > 0 {
|
||||
continue
|
||||
}
|
||||
telegraphs = append(telegraphs, telegraph)
|
||||
db.Dao.Model(&models.Telegraph{}).Create(&telegraph)
|
||||
logger.SugaredLogger.Debugf("telegraph: %+v", &telegraph)
|
||||
if news["subjects"] == nil {
|
||||
continue
|
||||
}
|
||||
subjects := news["subjects"].([]any)
|
||||
for _, subject := range subjects {
|
||||
name := subject.(map[string]any)["subject_name"].(string)
|
||||
tag := &models.Tags{
|
||||
Name: name,
|
||||
Type: "subject",
|
||||
}
|
||||
db.Dao.Model(tag).Where("name=? and type=?", name, "subject").FirstOrCreate(&tag)
|
||||
db.Dao.Model(models.TelegraphTags{}).Where("telegraph_id=? and tag_id=?", telegraph.ID, tag.ID).FirstOrCreate(&models.TelegraphTags{
|
||||
TelegraphId: telegraph.ID,
|
||||
TagId: tag.ID,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
//db.Dao.Model(&models.Telegraph{}).Create(&telegraphs)
|
||||
//logger.SugaredLogger.Debugf("telegraphs: %+v", &telegraphs)
|
||||
}
|
||||
|
||||
return &telegraphs
|
||||
}
|
||||
func GetLevel(s string) bool {
|
||||
return s > "C"
|
||||
}
|
||||
|
||||
func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
|
||||
url := "https://www.cls.cn/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(url)
|
||||
Get(fmt.Sprintf(url))
|
||||
var telegraphs []models.Telegraph
|
||||
//logger.SugaredLogger.Info(string(response.Body()))
|
||||
document, _ := goquery.NewDocumentFromReader(strings.NewReader(string(response.Body())))
|
||||
@@ -144,7 +75,7 @@ func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
|
||||
if telegraph.Content != "" {
|
||||
telegraph.SentimentResult = AnalyzeSentiment(telegraph.Content).Description
|
||||
cnt := int64(0)
|
||||
db.Dao.Model(telegraph).Where("time=? and content=?", telegraph.Time, telegraph.Content).Count(&cnt)
|
||||
db.Dao.Model(telegraph).Where("time=? and source=?", telegraph.Time, telegraph.Source).Count(&cnt)
|
||||
if cnt == 0 {
|
||||
db.Dao.Create(&telegraph)
|
||||
telegraphs = append(telegraphs, telegraph)
|
||||
@@ -184,28 +115,6 @@ func (m MarketNewsApi) GetNewsList(source string, limit int) *[]*models.Telegrap
|
||||
}
|
||||
return news
|
||||
}
|
||||
func (m MarketNewsApi) GetNewsList2(source string, limit int) *[]*models.Telegraph {
|
||||
NewMarketNewsApi().TelegraphList(30)
|
||||
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 != "" {
|
||||
@@ -641,14 +550,9 @@ func (m MarketNewsApi) EMDictCode(code string, cache *freecache.Cache) []any {
|
||||
}
|
||||
|
||||
func (m MarketNewsApi) TradingViewNews() *[]models.TVNews {
|
||||
client := resty.New()
|
||||
config := GetSettingConfig()
|
||||
if config.HttpProxyEnabled && config.HttpProxy != "" {
|
||||
client.SetProxy(config.HttpProxy)
|
||||
}
|
||||
TVNews := &[]models.TVNews{}
|
||||
url := "https://news-mediator.tradingview.com/news-flow/v2/news?filter=lang:zh-Hans&filter=provider:panews,reuters&client=screener&streaming=false"
|
||||
resp, err := client.SetTimeout(time.Duration(5)*time.Second).R().
|
||||
resp, err := resty.New().SetProxy("http://127.0.0.1:10809").SetTimeout(time.Duration(30)*time.Second).R().
|
||||
SetHeader("Host", "news-mediator.tradingview.com").
|
||||
SetHeader("Origin", "https://cn.tradingview.com").
|
||||
SetHeader("Referer", "https://cn.tradingview.com/").
|
||||
@@ -695,7 +599,7 @@ func (m MarketNewsApi) XUEQIUHotStock(size int, marketType string) *[]models.Hot
|
||||
logger.SugaredLogger.Errorf("XUEQIUHotStock err:%s", err.Error())
|
||||
return &[]models.HotItem{}
|
||||
}
|
||||
//logger.SugaredLogger.Infof("XUEQIUHotStock:%+v", res)
|
||||
logger.SugaredLogger.Infof("XUEQIUHotStock:%+v", res)
|
||||
return &res.Data.Items
|
||||
}
|
||||
|
||||
@@ -797,263 +701,3 @@ func (m MarketNewsApi) ClsCalendar() []any {
|
||||
err = json.Unmarshal(resp.Body(), &respMap)
|
||||
return respMap["data"].([]any)
|
||||
}
|
||||
|
||||
func (m MarketNewsApi) GetGDP() *models.GDPResp {
|
||||
res := &models.GDPResp{}
|
||||
|
||||
url := "https://datacenter-web.eastmoney.com/api/data/v1/get?callback=data&columns=REPORT_DATE%2CTIME%2CDOMESTICL_PRODUCT_BASE%2CFIRST_PRODUCT_BASE%2CSECOND_PRODUCT_BASE%2CTHIRD_PRODUCT_BASE%2CSUM_SAME%2CFIRST_SAME%2CSECOND_SAME%2CTHIRD_SAME&pageNumber=1&pageSize=20&sortColumns=REPORT_DATE&sortTypes=-1&source=WEB&client=WEB&reportName=RPT_ECONOMY_GDP&p=1&pageNo=1&pageNum=1&_=" + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
|
||||
SetHeader("Host", "datacenter-web.eastmoney.com").
|
||||
SetHeader("Origin", "https://datacenter.eastmoney.com").
|
||||
SetHeader("Referer", "https://data.eastmoney.com/cjsj/gdp.html").
|
||||
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())
|
||||
return res
|
||||
}
|
||||
body := resp.Body()
|
||||
logger.SugaredLogger.Debugf("GDP:%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())
|
||||
return res
|
||||
}
|
||||
data, _ := val.Object().Value().Export()
|
||||
logger.SugaredLogger.Infof("GDP:%v", data)
|
||||
marshal, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
json.Unmarshal(marshal, &res)
|
||||
logger.SugaredLogger.Infof("GDP:%+v", res)
|
||||
return res
|
||||
}
|
||||
|
||||
func (m MarketNewsApi) GetCPI() *models.CPIResp {
|
||||
res := &models.CPIResp{}
|
||||
|
||||
url := "https://datacenter-web.eastmoney.com/api/data/v1/get?callback=data&columns=REPORT_DATE%2CTIME%2CNATIONAL_SAME%2CNATIONAL_BASE%2CNATIONAL_SEQUENTIAL%2CNATIONAL_ACCUMULATE%2CCITY_SAME%2CCITY_BASE%2CCITY_SEQUENTIAL%2CCITY_ACCUMULATE%2CRURAL_SAME%2CRURAL_BASE%2CRURAL_SEQUENTIAL%2CRURAL_ACCUMULATE&pageNumber=1&pageSize=20&sortColumns=REPORT_DATE&sortTypes=-1&source=WEB&client=WEB&reportName=RPT_ECONOMY_CPI&p=1&pageNo=1&pageNum=1&_=" + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
|
||||
SetHeader("Host", "datacenter-web.eastmoney.com").
|
||||
SetHeader("Origin", "https://datacenter.eastmoney.com").
|
||||
SetHeader("Referer", "https://data.eastmoney.com/cjsj/gdp.html").
|
||||
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("GetCPI err:%s", err.Error())
|
||||
return res
|
||||
}
|
||||
body := resp.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("GetCPI err:%s", err.Error())
|
||||
return res
|
||||
}
|
||||
data, _ := val.Object().Value().Export()
|
||||
logger.SugaredLogger.Infof("GetCPI:%v", data)
|
||||
marshal, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
json.Unmarshal(marshal, &res)
|
||||
logger.SugaredLogger.Infof("GetCPI:%+v", res)
|
||||
return res
|
||||
}
|
||||
|
||||
// GetPPI PPI
|
||||
func (m MarketNewsApi) GetPPI() *models.PPIResp {
|
||||
res := &models.PPIResp{}
|
||||
url := "https://datacenter-web.eastmoney.com/api/data/v1/get?callback=data&columns=REPORT_DATE,TIME,BASE,BASE_SAME,BASE_ACCUMULATE&pageNumber=1&pageSize=20&sortColumns=REPORT_DATE&sortTypes=-1&source=WEB&client=WEB&reportName=RPT_ECONOMY_PPI&p=1&pageNo=1&pageNum=1&_=" + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
|
||||
SetHeader("Host", "datacenter-web.eastmoney.com").
|
||||
SetHeader("Origin", "https://datacenter.eastmoney.com").
|
||||
SetHeader("Referer", "https://data.eastmoney.com/cjsj/gdp.html").
|
||||
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("GetPPI err:%s", err.Error())
|
||||
return res
|
||||
}
|
||||
body := resp.Body()
|
||||
vm := otto.New()
|
||||
vm.Run("function data(res){return res};")
|
||||
|
||||
val, err := vm.Run(body)
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
data, _ := val.Object().Value().Export()
|
||||
marshal, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
json.Unmarshal(marshal, &res)
|
||||
return res
|
||||
}
|
||||
|
||||
func (m MarketNewsApi) GetPMI() *models.PMIResp {
|
||||
res := &models.PMIResp{}
|
||||
url := "https://datacenter-web.eastmoney.com/api/data/v1/get?callback=data&columns=REPORT_DATE%2CTIME%2CMAKE_INDEX%2CMAKE_SAME%2CNMAKE_INDEX%2CNMAKE_SAME&pageNumber=1&pageSize=20&sortColumns=REPORT_DATE&sortTypes=-1&source=WEB&client=WEB&reportName=RPT_ECONOMY_PMI&p=1&pageNo=1&pageNum=1&_=" + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
|
||||
SetHeader("Host", "datacenter-web.eastmoney.com").
|
||||
SetHeader("Origin", "https://datacenter.eastmoney.com").
|
||||
SetHeader("Referer", "https://data.eastmoney.com/cjsj/gdp.html").
|
||||
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 {
|
||||
return res
|
||||
}
|
||||
body := resp.Body()
|
||||
vm := otto.New()
|
||||
vm.Run("function data(res){return res};")
|
||||
|
||||
val, err := vm.Run(body)
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
data, _ := val.Object().Value().Export()
|
||||
marshal, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
json.Unmarshal(marshal, &res)
|
||||
return res
|
||||
|
||||
}
|
||||
func (m MarketNewsApi) GetIndustryReportInfo(infoCode string) string {
|
||||
url := "https://data.eastmoney.com/report/zw_industry.jshtml?infocode=" + infoCode
|
||||
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
|
||||
SetHeader("Host", "data.eastmoney.com").
|
||||
SetHeader("Origin", "https://data.eastmoney.com").
|
||||
SetHeader("Referer", "https://data.eastmoney.com/report/industry.jshtml").
|
||||
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("GetIndustryReportInfo err:%s", err.Error())
|
||||
return ""
|
||||
}
|
||||
body := resp.Body()
|
||||
//logger.SugaredLogger.Debugf("GetIndustryReportInfo:%s", body)
|
||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))
|
||||
title, _ := doc.Find("div.c-title").Html()
|
||||
content, _ := doc.Find("div.ctx-content").Html()
|
||||
//logger.SugaredLogger.Infof("GetIndustryReportInfo:\n%s\n%s", title, content)
|
||||
markdown, err := util.HTMLToMarkdown(title + content)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
logger.SugaredLogger.Infof("GetIndustryReportInfo markdown:\n%s", markdown)
|
||||
return markdown
|
||||
}
|
||||
|
||||
func (m MarketNewsApi) ReutersNew() *models.ReutersNews {
|
||||
client := resty.New()
|
||||
config := GetSettingConfig()
|
||||
if config.HttpProxyEnabled && config.HttpProxy != "" {
|
||||
client.SetProxy(config.HttpProxy)
|
||||
}
|
||||
news := &models.ReutersNews{}
|
||||
url := "https://www.reuters.com/pf/api/v3/content/fetch/articles-by-section-alias-or-id-v1?query={\"arc-site\":\"reuters\",\"fetch_type\":\"collection\",\"offset\":0,\"section_id\":\"/world/\",\"size\":9,\"uri\":\"/world/\",\"website\":\"reuters\"}&d=300&mxId=00000000&_website=reuters"
|
||||
_, err := client.SetTimeout(time.Duration(5)*time.Second).R().
|
||||
SetHeader("Host", "www.reuters.com").
|
||||
SetHeader("Origin", "https://www.reuters.com").
|
||||
SetHeader("Referer", "https://www.reuters.com/world/china/").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
|
||||
SetResult(news).
|
||||
Get(url)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("ReutersNew err:%s", err.Error())
|
||||
return news
|
||||
}
|
||||
logger.SugaredLogger.Infof("Articles:%+v", news.Result.Articles)
|
||||
return news
|
||||
}
|
||||
|
||||
func (m MarketNewsApi) InteractiveAnswer(page int, pageSize int, keyWord string) *models.InteractiveAnswer {
|
||||
client := resty.New()
|
||||
config := GetSettingConfig()
|
||||
if config.HttpProxyEnabled && config.HttpProxy != "" {
|
||||
client.SetProxy(config.HttpProxy)
|
||||
}
|
||||
url := fmt.Sprintf("https://irm.cninfo.com.cn/newircs/index/search?_t=%d", time.Now().Unix())
|
||||
answers := &models.InteractiveAnswer{}
|
||||
logger.SugaredLogger.Infof("请求url:%s", url)
|
||||
resp, err := client.SetTimeout(time.Duration(5)*time.Second).R().
|
||||
SetHeader("Host", "irm.cninfo.com.cn").
|
||||
SetHeader("Origin", "https://irm.cninfo.com.cn").
|
||||
SetHeader("Referer", "https://irm.cninfo.com.cn/views/interactiveAnswer").
|
||||
SetHeader("handleError", "true").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/142.0").
|
||||
SetFormData(map[string]string{
|
||||
"pageNo": convertor.ToString(page),
|
||||
"pageSize": convertor.ToString(pageSize),
|
||||
"searchTypes": "11",
|
||||
"highLight": "true",
|
||||
"keyWord": keyWord,
|
||||
}).
|
||||
SetResult(answers).
|
||||
Post(url)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("InteractiveAnswer-err:%+v", err)
|
||||
}
|
||||
logger.SugaredLogger.Debugf("InteractiveAnswer-resp:%s", resp.Body())
|
||||
return answers
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (m MarketNewsApi) GetNews24HoursList(source string, limit int) *[]*models.Telegraph {
|
||||
news := &[]*models.Telegraph{}
|
||||
if source != "" {
|
||||
db.Dao.Model(news).Preload("TelegraphTags").Where("source=? and created_at>?", source, time.Now().Add(-24*time.Hour)).Order("id desc,is_red desc").Limit(limit).Find(news)
|
||||
} else {
|
||||
db.Dao.Model(news).Preload("TelegraphTags").Where("created_at>?", time.Now().Add(-24*time.Hour)).Order("id desc,is_red desc").Limit(limit).Find(news)
|
||||
}
|
||||
// 内容去重
|
||||
uniqueNews := make([]*models.Telegraph, 0)
|
||||
seenContent := make(map[string]bool)
|
||||
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)
|
||||
// 使用内容作为去重键值,可以考虑只使用内容的前几个字符或哈希值
|
||||
contentKey := strings.TrimSpace(item.Content)
|
||||
if contentKey != "" && !seenContent[contentKey] {
|
||||
seenContent[contentKey] = true
|
||||
uniqueNews = append(uniqueNews, item)
|
||||
}
|
||||
}
|
||||
return &uniqueNews
|
||||
}
|
||||
|
||||
@@ -2,16 +2,10 @@ package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/coocood/freecache"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"go-stock/backend/models"
|
||||
"go-stock/backend/util"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coocood/freecache"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
@@ -39,6 +33,7 @@ func TestGetIndustryRank(t *testing.T) {
|
||||
res := NewMarketNewsApi().GetIndustryRank("0", 10)
|
||||
for s, a := range res["data"].([]any) {
|
||||
logger.SugaredLogger.Debugf("key: %+v, value: %+v", s, a)
|
||||
|
||||
}
|
||||
}
|
||||
func TestGetIndustryMoneyRankSina(t *testing.T) {
|
||||
@@ -73,24 +68,19 @@ func TestLongTiger(t *testing.T) {
|
||||
|
||||
func TestStockResearchReport(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
resp := NewMarketNewsApi().StockResearchReport("688082", 7)
|
||||
resp := NewMarketNewsApi().StockResearchReport("600584.sh", 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("456", 7)
|
||||
resp := NewMarketNewsApi().IndustryResearchReport("", 7)
|
||||
for _, a := range resp {
|
||||
logger.SugaredLogger.Debugf("value: %+v", a)
|
||||
data := a.(map[string]any)
|
||||
logger.SugaredLogger.Debugf("value: %s infoCode:%s", data["title"], data["infoCode"])
|
||||
NewMarketNewsApi().GetIndustryReportInfo(data["infoCode"].(string))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStockNotice(t *testing.T) {
|
||||
@@ -108,15 +98,6 @@ func TestEMDictCode(t *testing.T) {
|
||||
for _, a := range resp {
|
||||
logger.SugaredLogger.Debugf("value: %+v", a)
|
||||
}
|
||||
bytes, err := json.Marshal(resp)
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
@@ -159,93 +140,14 @@ func TestInvestCalendar(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
res := NewMarketNewsApi().InvestCalendar("2025-06")
|
||||
for _, a := range res {
|
||||
bytes, err := json.Marshal(a)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
date := gjson.Get(string(bytes), "date")
|
||||
list := gjson.Get(string(bytes), "list")
|
||||
|
||||
logger.SugaredLogger.Debugf("value: %+v,list: %+v", date.String(), list)
|
||||
logger.SugaredLogger.Debugf("value: %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClsCalendar(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
res := NewMarketNewsApi().ClsCalendar()
|
||||
md := strings.Builder{}
|
||||
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
|
||||
})
|
||||
logger.SugaredLogger.Debugf("value: %+v", a)
|
||||
}
|
||||
logger.SugaredLogger.Debugf("md:\n %s", md.String())
|
||||
}
|
||||
|
||||
func TestGetGDP(t *testing.T) {
|
||||
res := NewMarketNewsApi().GetGDP()
|
||||
md := util.MarkdownTableWithTitle("国内生产总值(GDP)", res.GDPResult.Data)
|
||||
logger.SugaredLogger.Debugf(md)
|
||||
}
|
||||
func TestGetCPI(t *testing.T) {
|
||||
res := NewMarketNewsApi().GetCPI()
|
||||
md := util.MarkdownTableWithTitle("居民消费价格指数(CPI)", res.CPIResult.Data)
|
||||
logger.SugaredLogger.Debugf(md)
|
||||
}
|
||||
|
||||
// PPI
|
||||
func TestGetPPI(t *testing.T) {
|
||||
res := NewMarketNewsApi().GetPPI()
|
||||
md := util.MarkdownTableWithTitle("工业品出厂价格指数(PPI)", res.PPIResult.Data)
|
||||
logger.SugaredLogger.Debugf(md)
|
||||
}
|
||||
|
||||
// PMI
|
||||
func TestGetPMI(t *testing.T) {
|
||||
res := NewMarketNewsApi().GetPMI()
|
||||
md := util.MarkdownTableWithTitle("采购经理人指数(PMI)", res.PMIResult.Data)
|
||||
logger.SugaredLogger.Debugf(md)
|
||||
}
|
||||
func TestGetIndustryReportInfo(t *testing.T) {
|
||||
NewMarketNewsApi().GetIndustryReportInfo("AP202507151709216483")
|
||||
}
|
||||
|
||||
func TestReutersNew(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
NewMarketNewsApi().ReutersNew()
|
||||
}
|
||||
|
||||
func TestInteractiveAnswer(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
datas := NewMarketNewsApi().InteractiveAnswer(1, 100, "")
|
||||
logger.SugaredLogger.Debugf("PageSize:%d", datas.PageSize)
|
||||
md := util.MarkdownTableWithTitle("投资互动", datas.Results)
|
||||
logger.SugaredLogger.Debugf(md)
|
||||
|
||||
}
|
||||
func TestGetNewsList2(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
|
||||
messageText := strings.Builder{}
|
||||
for _, telegraph := range *news {
|
||||
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
|
||||
messageText.WriteString("### " + telegraph.Content + "\n")
|
||||
}
|
||||
logger.SugaredLogger.Debugf("value: %s", messageText.String())
|
||||
}
|
||||
|
||||
func TestTelegraphList(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
NewMarketNewsApi().TelegraphList(30)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,45 +3,17 @@ package data
|
||||
import (
|
||||
"context"
|
||||
"go-stock/backend/db"
|
||||
log "go-stock/backend/logger"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewDeepSeekOpenAiConfig(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
|
||||
var tools []Tool
|
||||
tools = append(tools, Tool{
|
||||
Type: "function",
|
||||
Function: ToolFunction{
|
||||
Name: "SearchStockByIndicators",
|
||||
Description: "根据自然语言筛选股票,返回自然语言选股条件要求的股票所有相关数据",
|
||||
Parameters: FunctionParameters{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"words": map[string]any{
|
||||
"type": "string",
|
||||
"description": "选股自然语言,并且条件使用;分隔,或者条件使用,分隔。例如:创新药;PE<30;净利润增长率>50%;",
|
||||
},
|
||||
},
|
||||
Required: []string{"words"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
ai := NewDeepSeekOpenAi(context.TODO(), 1)
|
||||
//res := ai.NewChatStream("长电科技", "sh600584", "长电科技分析和总结", nil)
|
||||
res := ai.NewSummaryStockNewsStreamWithTools("总结市场资讯,发掘潜力标的/行业/板块/概念,控制风险。调用工具函数验证", nil, tools)
|
||||
|
||||
ai := NewDeepSeekOpenAi(context.TODO())
|
||||
res := ai.NewChatStream("长电科技", "sh600584", "长电科技分析和总结", nil)
|
||||
for {
|
||||
select {
|
||||
case msg := <-res:
|
||||
if len(msg) > 0 {
|
||||
t.Log(msg)
|
||||
if msg["content"] == "DONE" {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Log(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,17 +25,8 @@ func TestGetTopNewsList(t *testing.T) {
|
||||
|
||||
func TestSearchGuShiTongStockInfo(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
//SearchGuShiTongStockInfo("hk01810", 60)
|
||||
msgs := SearchGuShiTongStockInfo("sh600745", 60)
|
||||
for _, msg := range *msgs {
|
||||
log.SugaredLogger.Infof("%s", msg)
|
||||
}
|
||||
//SearchGuShiTongStockInfo("gb_goog", 60)
|
||||
SearchGuShiTongStockInfo("hk01810", 60)
|
||||
SearchGuShiTongStockInfo("sh600745", 60)
|
||||
SearchGuShiTongStockInfo("gb_goog", 60)
|
||||
|
||||
}
|
||||
|
||||
func TestGetZSInfo(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
GetZSInfo("中证银行", "sz399986", 30)
|
||||
GetZSInfo("上海贝岭", "sh600171", 30)
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ type BrowserPool struct {
|
||||
func NewBrowserPool(size int) *BrowserPool {
|
||||
pool := make(chan *context.Context, size)
|
||||
for i := 0; i < size; i++ {
|
||||
path := GetSettingConfig().BrowserPath
|
||||
crawlTimeOut := GetSettingConfig().CrawlTimeOut
|
||||
path := GetConfig().BrowserPath
|
||||
crawlTimeOut := GetConfig().CrawlTimeOut
|
||||
if crawlTimeOut < 15 {
|
||||
crawlTimeOut = 30
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@ 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
|
||||
@@ -20,31 +19,31 @@ type SearchStockApi struct {
|
||||
func NewSearchStockApi(words string) *SearchStockApi {
|
||||
return &SearchStockApi{words: words}
|
||||
}
|
||||
func (s SearchStockApi) SearchStock(pageSize int) map[string]any {
|
||||
func (s SearchStockApi) SearchStock() map[string]any {
|
||||
url := "https://np-tjxg-g.eastmoney.com/api/smart-tag/stock/v3/pw/search-code"
|
||||
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
|
||||
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:145.0) Gecko/20100101 Firefox/145.0").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(fmt.Sprintf(`{
|
||||
"keyWord": "%s",
|
||||
"pageSize": %d,
|
||||
"pageSize": 50000,
|
||||
"pageNo": 1,
|
||||
"fingerprint": "02efa8944b1f90fbfe050e1e695a480d",
|
||||
"fingerprint": "e38b5faabf9378c8238e57219f0ebc9b",
|
||||
"gids": [],
|
||||
"matchWord": "",
|
||||
"timestamp": "%d",
|
||||
"timestamp": "1751113883290349",
|
||||
"shareToGuba": false,
|
||||
"requestId": "RMd3Y76AJI98axPvdhdbKvbBDVwLlUK61761559950168",
|
||||
"requestId": "8xTWgCDAjvQ5lmvz5mDA3Ydk2AE4yoiJ1751113883290",
|
||||
"needCorrect": true,
|
||||
"removedConditionIdList": [],
|
||||
"xcId": "xc0d61279aad33008260",
|
||||
"xcId": "xc0af28549ab330013ed",
|
||||
"ownSelectAll": false,
|
||||
"dxInfo": [],
|
||||
"extraCondition": ""
|
||||
}`, s.words, pageSize, time.Now().Unix())).Post(url)
|
||||
}`, s.words)).Post(url)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("SearchStock-err:%+v", err)
|
||||
return map[string]any{}
|
||||
@@ -54,20 +53,3 @@ func (s SearchStockApi) SearchStock(pageSize int) map[string]any {
|
||||
//logger.SugaredLogger.Infof("resp:%+v", respMap["data"])
|
||||
return respMap
|
||||
}
|
||||
|
||||
func (s SearchStockApi) HotStrategy() map[string]any {
|
||||
url := fmt.Sprintf("https://np-ipick.eastmoney.com/recommend/stock/heat/ranking?count=20&trace=%d&client=web&biz=web_smart_tag", time.Now().Unix())
|
||||
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
|
||||
SetHeader("Host", "np-ipick.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").
|
||||
Get(url)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("HotStrategy-err:%+v", err)
|
||||
return map[string]any{}
|
||||
}
|
||||
respMap := map[string]any{}
|
||||
json.Unmarshal(resp.Body(), &respMap)
|
||||
return respMap
|
||||
}
|
||||
|
||||
@@ -1,61 +1,25 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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("量比大于2,基本面优秀,2025年三季报已披露,主力连续3日净流入,非创业板非科创板非ST").SearchStock(20)
|
||||
logger.SugaredLogger.Infof("res:%+v", res)
|
||||
res := NewSearchStockApi("算力股;净利润连续3年增长").SearchStock()
|
||||
data := res["data"].(map[string]any)
|
||||
result := data["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 {
|
||||
//logger.SugaredLogger.Infof("v:%+v", v)
|
||||
d := v.(map[string]any)
|
||||
tmp := map[string]any{}
|
||||
for key, title := range headers {
|
||||
//logger.SugaredLogger.Infof("%s:%s", title, convertor.ToString(d[key]))
|
||||
tmp[title] = convertor.ToString(d[key])
|
||||
}
|
||||
*table = append(*table, tmp)
|
||||
//logger.SugaredLogger.Infof("--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------")
|
||||
logger.SugaredLogger.Infof("%s:%s", d["INDUSTRY"], d["SECURITY_SHORT_NAME"])
|
||||
}
|
||||
jsonData, _ := json.Marshal(*table)
|
||||
markdownTable, _ := JSONToMarkdownTable(jsonData)
|
||||
logger.SugaredLogger.Infof("markdownTable=\n%s", markdownTable)
|
||||
}
|
||||
//columns := result["columns"].([]any)
|
||||
//for _, v := range columns {
|
||||
// logger.SugaredLogger.Infof("v:%+v", v)
|
||||
//}
|
||||
|
||||
func TestSearchStockApi_HotStrategy(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
res := NewSearchStockApi("").HotStrategy()
|
||||
logger.SugaredLogger.Infof("res:%+v", res)
|
||||
dataList := res["data"].([]any)
|
||||
for _, v := range dataList {
|
||||
d := v.(map[string]any)
|
||||
logger.SugaredLogger.Infof("v:%+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,8 @@ package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -19,200 +15,110 @@ type Settings struct {
|
||||
DingRobot string `json:"dingRobot"`
|
||||
UpdateBasicInfoOnStart bool `json:"updateBasicInfoOnStart"`
|
||||
RefreshInterval int64 `json:"refreshInterval"`
|
||||
OpenAiEnable bool `json:"openAiEnable"`
|
||||
Prompt string `json:"prompt"`
|
||||
CheckUpdate bool `json:"checkUpdate"`
|
||||
QuestionTemplate string `json:"questionTemplate"`
|
||||
CrawlTimeOut int64 `json:"crawlTimeOut"`
|
||||
KDays int64 `json:"kDays"`
|
||||
EnableDanmu bool `json:"enableDanmu"`
|
||||
BrowserPath string `json:"browserPath"`
|
||||
EnableNews bool `json:"enableNews"`
|
||||
DarkTheme bool `json:"darkTheme"`
|
||||
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"`
|
||||
|
||||
OpenAiEnable bool `json:"openAiEnable"`
|
||||
OpenAiBaseUrl string `json:"openAiBaseUrl"`
|
||||
OpenAiApiKey string `json:"openAiApiKey"`
|
||||
OpenAiModelName string `json:"openAiModelName"`
|
||||
OpenAiMaxTokens int `json:"openAiMaxTokens"`
|
||||
OpenAiTemperature float64 `json:"openAiTemperature"`
|
||||
OpenAiApiTimeOut int `json:"openAiApiTimeOut"`
|
||||
Prompt string `json:"prompt"`
|
||||
CheckUpdate bool `json:"checkUpdate"`
|
||||
QuestionTemplate string `json:"questionTemplate"`
|
||||
CrawlTimeOut int64 `json:"crawlTimeOut"`
|
||||
KDays int64 `json:"kDays"`
|
||||
EnableDanmu bool `json:"enableDanmu"`
|
||||
BrowserPath string `json:"browserPath"`
|
||||
EnableNews bool `json:"enableNews"`
|
||||
DarkTheme bool `json:"darkTheme"`
|
||||
BrowserPoolSize int `json:"browserPoolSize"`
|
||||
EnableFund bool `json:"enableFund"`
|
||||
EnablePushNews bool `json:"enablePushNews"`
|
||||
}
|
||||
|
||||
func (receiver Settings) TableName() string {
|
||||
return "settings"
|
||||
}
|
||||
|
||||
type AIConfig struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `json:"name"`
|
||||
BaseUrl string `json:"baseUrl"`
|
||||
ApiKey string `json:"apiKey" `
|
||||
ModelName string `json:"modelName"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
TimeOut int `json:"timeOut"`
|
||||
}
|
||||
|
||||
func (AIConfig) TableName() string {
|
||||
return "ai_config"
|
||||
}
|
||||
|
||||
type SettingConfig struct {
|
||||
*Settings
|
||||
AiConfigs []*AIConfig `json:"aiConfigs"`
|
||||
}
|
||||
|
||||
type SettingsApi struct {
|
||||
Config *SettingConfig
|
||||
Config Settings
|
||||
}
|
||||
|
||||
func NewSettingsApi() *SettingsApi {
|
||||
func NewSettingsApi(settings *Settings) *SettingsApi {
|
||||
return &SettingsApi{
|
||||
Config: GetSettingConfig(),
|
||||
Config: *settings,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SettingsApi) Export() string {
|
||||
d, _ := json.MarshalIndent(s.Config, "", " ")
|
||||
return string(d)
|
||||
}
|
||||
|
||||
func UpdateConfig(s *SettingConfig) string {
|
||||
func (s SettingsApi) UpdateConfig() string {
|
||||
count := int64(0)
|
||||
db.Dao.Model(&Settings{}).Count(&count)
|
||||
db.Dao.Model(s.Config).Count(&count)
|
||||
if count > 0 {
|
||||
db.Dao.Model(&Settings{}).Where("id=?", s.ID).Updates(map[string]any{
|
||||
"local_push_enable": s.LocalPushEnable,
|
||||
"ding_push_enable": s.DingPushEnable,
|
||||
"ding_robot": s.DingRobot,
|
||||
"update_basic_info_on_start": s.UpdateBasicInfoOnStart,
|
||||
"refresh_interval": s.RefreshInterval,
|
||||
"open_ai_enable": s.OpenAiEnable,
|
||||
"tushare_token": s.TushareToken,
|
||||
"prompt": s.Prompt,
|
||||
"check_update": s.CheckUpdate,
|
||||
"question_template": s.QuestionTemplate,
|
||||
"crawl_time_out": s.CrawlTimeOut,
|
||||
"k_days": s.KDays,
|
||||
"enable_danmu": s.EnableDanmu,
|
||||
"browser_path": s.BrowserPath,
|
||||
"enable_news": s.EnableNews,
|
||||
"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,
|
||||
db.Dao.Model(s.Config).Where("id=?", s.Config.ID).Updates(map[string]any{
|
||||
"local_push_enable": s.Config.LocalPushEnable,
|
||||
"ding_push_enable": s.Config.DingPushEnable,
|
||||
"ding_robot": s.Config.DingRobot,
|
||||
"update_basic_info_on_start": s.Config.UpdateBasicInfoOnStart,
|
||||
"refresh_interval": s.Config.RefreshInterval,
|
||||
"open_ai_enable": s.Config.OpenAiEnable,
|
||||
"open_ai_base_url": s.Config.OpenAiBaseUrl,
|
||||
"open_ai_api_key": s.Config.OpenAiApiKey,
|
||||
"open_ai_model_name": s.Config.OpenAiModelName,
|
||||
"open_ai_max_tokens": s.Config.OpenAiMaxTokens,
|
||||
"open_ai_temperature": s.Config.OpenAiTemperature,
|
||||
"tushare_token": s.Config.TushareToken,
|
||||
"prompt": s.Config.Prompt,
|
||||
"check_update": s.Config.CheckUpdate,
|
||||
"open_ai_api_time_out": s.Config.OpenAiApiTimeOut,
|
||||
"question_template": s.Config.QuestionTemplate,
|
||||
"crawl_time_out": s.Config.CrawlTimeOut,
|
||||
"k_days": s.Config.KDays,
|
||||
"enable_danmu": s.Config.EnableDanmu,
|
||||
"browser_path": s.Config.BrowserPath,
|
||||
"enable_news": s.Config.EnableNews,
|
||||
"dark_theme": s.Config.DarkTheme,
|
||||
"enable_fund": s.Config.EnableFund,
|
||||
"enable_push_news": s.Config.EnablePushNews,
|
||||
})
|
||||
|
||||
//更新AiConfig
|
||||
err := updateAiConfigs(s.AiConfigs)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("更新AI模型服务配置失败: %v", err)
|
||||
return "更新AI模型服务配置失败: " + err.Error()
|
||||
}
|
||||
} else {
|
||||
logger.SugaredLogger.Infof("未找到配置,创建默认配置")
|
||||
// 创建主配置
|
||||
result := db.Dao.Model(&Settings{}).Create(&Settings{})
|
||||
if result.Error != nil {
|
||||
logger.SugaredLogger.Error("创建配置失败:", result.Error)
|
||||
return "创建配置失败: " + result.Error.Error()
|
||||
}
|
||||
logger.SugaredLogger.Infof("未找到配置,创建默认配置:%+v", s.Config)
|
||||
db.Dao.Model(s.Config).Create(&Settings{
|
||||
LocalPushEnable: s.Config.LocalPushEnable,
|
||||
DingPushEnable: s.Config.DingPushEnable,
|
||||
DingRobot: s.Config.DingRobot,
|
||||
UpdateBasicInfoOnStart: s.Config.UpdateBasicInfoOnStart,
|
||||
RefreshInterval: s.Config.RefreshInterval,
|
||||
OpenAiEnable: s.Config.OpenAiEnable,
|
||||
OpenAiBaseUrl: s.Config.OpenAiBaseUrl,
|
||||
OpenAiApiKey: s.Config.OpenAiApiKey,
|
||||
OpenAiModelName: s.Config.OpenAiModelName,
|
||||
OpenAiMaxTokens: s.Config.OpenAiMaxTokens,
|
||||
OpenAiTemperature: s.Config.OpenAiTemperature,
|
||||
TushareToken: s.Config.TushareToken,
|
||||
Prompt: s.Config.Prompt,
|
||||
CheckUpdate: s.Config.CheckUpdate,
|
||||
OpenAiApiTimeOut: s.Config.OpenAiApiTimeOut,
|
||||
QuestionTemplate: s.Config.QuestionTemplate,
|
||||
CrawlTimeOut: s.Config.CrawlTimeOut,
|
||||
KDays: s.Config.KDays,
|
||||
EnableDanmu: s.Config.EnableDanmu,
|
||||
BrowserPath: s.Config.BrowserPath,
|
||||
EnableNews: s.Config.EnableNews,
|
||||
DarkTheme: s.Config.DarkTheme,
|
||||
EnableFund: s.Config.EnableFund,
|
||||
EnablePushNews: s.Config.EnablePushNews,
|
||||
})
|
||||
}
|
||||
return "保存成功!"
|
||||
}
|
||||
|
||||
func updateAiConfigs(aiConfigs []*AIConfig) error {
|
||||
if len(aiConfigs) == 0 {
|
||||
err := db.Dao.Exec("DELETE FROM ai_config").Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Dao.Exec("DELETE FROM sqlite_sequence WHERE name='ai_config'").Error
|
||||
}
|
||||
var ids []uint
|
||||
lo.ForEach(aiConfigs, func(item *AIConfig, index int) {
|
||||
ids = append(ids, item.ID)
|
||||
})
|
||||
var existAiConfigs []*AIConfig
|
||||
err := db.Dao.Model(&AIConfig{}).Select("id").Where("id in (?) ", ids).Find(&existAiConfigs).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
idMap := make(map[uint]bool)
|
||||
lo.ForEach(existAiConfigs, func(item *AIConfig, index int) {
|
||||
idMap[item.ID] = true
|
||||
})
|
||||
var addAiConfigs []*AIConfig
|
||||
var notDeleteIds []uint
|
||||
var e error
|
||||
lo.ForEach(aiConfigs, func(item *AIConfig, index int) {
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
if !idMap[item.ID] {
|
||||
addAiConfigs = append(addAiConfigs, item)
|
||||
} else {
|
||||
notDeleteIds = append(notDeleteIds, item.ID)
|
||||
e = db.Dao.Model(&AIConfig{}).Where("id=?", item.ID).Updates(map[string]interface{}{
|
||||
"name": item.Name,
|
||||
"base_url": item.BaseUrl,
|
||||
"api_key": item.ApiKey,
|
||||
"model_name": item.ModelName,
|
||||
"max_tokens": item.MaxTokens,
|
||||
"temperature": item.Temperature,
|
||||
"time_out": item.TimeOut,
|
||||
}).Error
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
//删除旧的配置
|
||||
if len(notDeleteIds) > 0 {
|
||||
err = db.Dao.Exec("DELETE FROM ai_config WHERE id NOT IN ?", notDeleteIds).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
logger.SugaredLogger.Infof("更新aiConfigs +%d", len(addAiConfigs))
|
||||
//批量新增的配置
|
||||
err = db.Dao.CreateInBatches(addAiConfigs, len(addAiConfigs)).Error
|
||||
return err
|
||||
}
|
||||
|
||||
func GetSettingConfig() *SettingConfig {
|
||||
settingConfig := &SettingConfig{}
|
||||
settings := &Settings{}
|
||||
aiConfigs := make([]*AIConfig, 0)
|
||||
// 处理数据库查询可能返回的空结果
|
||||
result := db.Dao.Model(&Settings{}).First(settings)
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
// 初始化默认设置并保存到数据库
|
||||
settings = &Settings{OpenAiEnable: false, CrawlTimeOut: 60}
|
||||
db.Dao.Create(settings)
|
||||
}
|
||||
func (s SettingsApi) GetConfig() *Settings {
|
||||
var settings Settings
|
||||
db.Dao.Model(&Settings{}).First(&settings)
|
||||
|
||||
if settings.OpenAiEnable {
|
||||
// 处理AI配置查询可能出现的错误
|
||||
result = db.Dao.Model(&AIConfig{}).Find(&aiConfigs)
|
||||
if result.Error != nil {
|
||||
logger.SugaredLogger.Error("查询AI配置失败:", result.Error)
|
||||
} else if len(aiConfigs) > 0 {
|
||||
lo.ForEach(aiConfigs, func(item *AIConfig, index int) {
|
||||
if item.TimeOut <= 0 {
|
||||
item.TimeOut = 60 * 5
|
||||
}
|
||||
})
|
||||
if settings.OpenAiApiTimeOut <= 0 {
|
||||
settings.OpenAiApiTimeOut = 60 * 5
|
||||
}
|
||||
if settings.CrawlTimeOut <= 0 {
|
||||
settings.CrawlTimeOut = 60
|
||||
@@ -222,13 +128,15 @@ func GetSettingConfig() *SettingConfig {
|
||||
}
|
||||
}
|
||||
if settings.BrowserPath == "" {
|
||||
settings.BrowserPath, _ = CheckBrowser()
|
||||
settings.BrowserPath, _ = CheckBrowserOnWindows()
|
||||
}
|
||||
if settings.BrowserPoolSize <= 0 {
|
||||
settings.BrowserPoolSize = 1
|
||||
}
|
||||
settingConfig.Settings = settings
|
||||
settingConfig.AiConfigs = aiConfigs
|
||||
|
||||
return settingConfig
|
||||
return &settings
|
||||
}
|
||||
|
||||
func (s SettingsApi) Export() string {
|
||||
d, _ := json.MarshalIndent(s.GetConfig(), "", " ")
|
||||
return string(d)
|
||||
}
|
||||
|
||||
@@ -9,14 +9,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"go-stock/backend/models"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/chromedp/chromedp"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
@@ -25,10 +17,18 @@ import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/robertkrimen/otto"
|
||||
"github.com/samber/lo"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"go-stock/backend/models"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/plugin/soft_delete"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const sinaStockUrl = "http://hq.sinajs.cn/rn=%d&list=%s"
|
||||
@@ -38,7 +38,7 @@ const tushareApiUrl = "http://api.tushare.pro"
|
||||
|
||||
type StockDataApi struct {
|
||||
client *resty.Client
|
||||
config *SettingConfig
|
||||
config *Settings
|
||||
}
|
||||
type StockInfo struct {
|
||||
gorm.Model
|
||||
@@ -154,8 +154,6 @@ type StockBasic struct {
|
||||
IsHs string `json:"is_hs"`
|
||||
ActName string `json:"act_name"`
|
||||
ActEntType string `json:"act_ent_type"`
|
||||
BKName string `json:"bk_name"`
|
||||
BKCode string `json:"bk_code"`
|
||||
}
|
||||
|
||||
type FollowedStock struct {
|
||||
@@ -173,7 +171,6 @@ type FollowedStock struct {
|
||||
Cron *string
|
||||
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
|
||||
Groups []GroupStock `gorm:"foreignKey:StockCode;references:StockCode"`
|
||||
AiConfigId int
|
||||
}
|
||||
|
||||
func (receiver FollowedStock) TableName() string {
|
||||
@@ -198,7 +195,7 @@ func (receiver StockBasic) TableName() string {
|
||||
func NewStockDataApi() *StockDataApi {
|
||||
return &StockDataApi{
|
||||
client: resty.New(),
|
||||
config: GetSettingConfig(),
|
||||
config: GetConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,9 +376,6 @@ func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
if stockData == nil {
|
||||
continue
|
||||
}
|
||||
stockInfos = append(stockInfos, *stockData)
|
||||
|
||||
go func() {
|
||||
@@ -420,15 +414,6 @@ 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)
|
||||
|
||||
@@ -1178,7 +1163,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{
|
||||
@@ -1203,10 +1188,6 @@ 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)
|
||||
@@ -1317,6 +1298,50 @@ func SearchStockInfoByCode(stock string) *[]string {
|
||||
return &messages
|
||||
}
|
||||
|
||||
// checkChromeOnWindows 在 Windows 系统上检查谷歌浏览器是否安装
|
||||
func checkChromeOnWindows() (string, bool) {
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
||||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
defer key.Close()
|
||||
}
|
||||
defer key.Close()
|
||||
path, _, err := key.GetStringValue("Path")
|
||||
//logger.SugaredLogger.Infof("Chrome安装路径:%s", path)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return path + "\\chrome.exe", true
|
||||
}
|
||||
|
||||
// CheckBrowserOnWindows 在 Windows 系统上检查Edge浏览器是否安装,并返回安装路径
|
||||
func CheckBrowserOnWindows() (string, bool) {
|
||||
if path, ok := checkChromeOnWindows(); ok {
|
||||
return path, true
|
||||
}
|
||||
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
||||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
defer key.Close()
|
||||
}
|
||||
defer key.Close()
|
||||
path, _, err := key.GetStringValue("Path")
|
||||
//logger.SugaredLogger.Infof("Edge安装路径:%s", path)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return path + "\\msedge.exe", true
|
||||
}
|
||||
|
||||
// 分时数据
|
||||
func (receiver StockDataApi) GetStockMinutePriceData(stockCode string) (*[]MinuteData, string) {
|
||||
url := fmt.Sprintf("https://web.ifzq.gtimg.cn/appstock/app/minute/query?code=%s", stockCode)
|
||||
@@ -1474,7 +1499,7 @@ func getSinaStockInfo(receiver StockDataApi, page, pageSize int) *[]models.SinaS
|
||||
func (receiver StockDataApi) getDCStockInfo(market string, page, pageSize int) {
|
||||
//m:105,m:106,m:107 //美股
|
||||
//m:128+t:3,m:128+t:4,m:128+t:1,m:128+t:2 //港股
|
||||
fs := "m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23,m:0+t:81+s:2048"
|
||||
fs := ""
|
||||
switch market {
|
||||
case "hk":
|
||||
fs = "m:128+t:3,m:128+t:4,m:128+t:1,m:128+t:2"
|
||||
@@ -1482,108 +1507,62 @@ func (receiver StockDataApi) getDCStockInfo(market string, page, pageSize int) {
|
||||
fs = "m:105,m:106,m:107"
|
||||
}
|
||||
|
||||
url := "https://push2.eastmoney.com/api/qt/clist/get?np=1&fltt=1&invt=2&cb=data&fs=%s&fields=f12,f13,f14,f1,f2,f4,f3,f152,f5,f6,f7,f15,f18,f16,f17,f10,f8,f9,f23,f100,f265&fid=f3&pn=%d&pz=%d&po=1&dect=1&wbp2u=|0|0|0|web&_=%d"
|
||||
sprintfUrl := fmt.Sprintf(url, fs, page, pageSize, time.Now().UnixMilli())
|
||||
logger.SugaredLogger.Infof("url:%s", sprintfUrl)
|
||||
url := "https://push2.eastmoney.com/api/qt/clist/get?cb=jQuery371047843066631541353_1745889398012&fs=%s&fields=f12,f13,f14,f19,f1,f2,f4,f3,f152,f17,f18,f15,f16,f5,f6&fid=f3&pn=%d&pz=%d&po=1&dect=1"
|
||||
resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R().
|
||||
SetHeader("Host", "push2.eastmoney.com").
|
||||
SetHeader("Referer", "https://quote.eastmoney.com/center/gridlist.html").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
|
||||
Get(sprintfUrl)
|
||||
Get(fmt.Sprintf(url, fs, page, pageSize))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("err:%s", err.Error())
|
||||
return
|
||||
}
|
||||
body := string(resp.Body())
|
||||
logger.SugaredLogger.Infof("resp:%s", body)
|
||||
body = strutil.ReplaceWithMap(body, map[string]string{
|
||||
"jQuery371047843066631541353_1745889398012(": "",
|
||||
");": "",
|
||||
})
|
||||
js := "var d=" + body
|
||||
vm := otto.New()
|
||||
vm.Run("function data(res){return res};")
|
||||
val, err := vm.Run(body)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("vm.Run error:%v", err.Error())
|
||||
}
|
||||
value, _ := val.Object().Value().Export()
|
||||
marshal, err := json.Marshal(value)
|
||||
_, err = vm.Run(js)
|
||||
_, err = vm.Run("var data = JSON.stringify(d);")
|
||||
value, err := vm.Get("data")
|
||||
data := make(map[string]any)
|
||||
err = json.Unmarshal(marshal, &data)
|
||||
err = json.Unmarshal([]byte(value.String()), &data)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("json:%s", value.String())
|
||||
logger.SugaredLogger.Errorf("json.Unmarshal error:%v", err.Error())
|
||||
}
|
||||
logger.SugaredLogger.Infof("resp:%s", data)
|
||||
if data["data"] != nil {
|
||||
datas := data["data"].(map[string]any)
|
||||
total := datas["total"].(float64)
|
||||
diff := datas["diff"].([]any)
|
||||
diff := datas["diff"].(map[string]any)
|
||||
logger.SugaredLogger.Infof("total:%d", int(total))
|
||||
for k, item := range diff {
|
||||
stock := item.(map[string]any)
|
||||
logger.SugaredLogger.Infof("k:%d,%s:%s:%s %s:%s", k, stock["f14"], stock["f12"], DCToTsCode(stock["f12"].(string)), stock["f100"], stock["f265"])
|
||||
|
||||
if market == "" {
|
||||
stockInfo := &StockBasic{
|
||||
Symbol: stock["f12"].(string),
|
||||
TsCode: DCToTsCode(stock["f12"].(string)),
|
||||
Name: stock["f14"].(string),
|
||||
BKName: stock["f100"].(string),
|
||||
BKCode: stock["f265"].(string),
|
||||
}
|
||||
db.Dao.Model(&StockBasic{}).Where("symbol = ?", stockInfo.Symbol).First(stockInfo)
|
||||
logger.SugaredLogger.Infof("stockInfo:%+v", stockInfo)
|
||||
if stockInfo.ID == 0 {
|
||||
db.Dao.Model(&StockBasic{}).Create(stockInfo)
|
||||
} else {
|
||||
stockInfo = &StockBasic{
|
||||
Symbol: stock["f12"].(string),
|
||||
TsCode: DCToTsCode(stock["f12"].(string)),
|
||||
Name: stock["f14"].(string),
|
||||
BKName: stock["f100"].(string),
|
||||
BKCode: stock["f265"].(string),
|
||||
}
|
||||
db.Dao.Model(&StockBasic{}).Where("symbol = ?", stockInfo.Symbol).Updates(stockInfo)
|
||||
}
|
||||
}
|
||||
logger.SugaredLogger.Infof("k:%s,%s:%s", k, stock["f14"], stock["f12"])
|
||||
|
||||
if market == "hk" {
|
||||
stockInfo := &models.StockInfoHK{
|
||||
Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".HK",
|
||||
Name: stock["f14"].(string),
|
||||
BKName: stock["f100"].(string),
|
||||
BKCode: stock["f265"].(string),
|
||||
Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".HK",
|
||||
Name: stock["f14"].(string),
|
||||
}
|
||||
db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stockInfo.Code).First(stockInfo)
|
||||
logger.SugaredLogger.Infof("stockInfo:%+v", stockInfo)
|
||||
if stockInfo.ID == 0 {
|
||||
db.Dao.Model(&models.StockInfoHK{}).Create(stockInfo)
|
||||
} else {
|
||||
stockInfo = &models.StockInfoHK{
|
||||
Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".HK",
|
||||
Name: stock["f14"].(string),
|
||||
BKName: stock["f100"].(string),
|
||||
BKCode: stock["f265"].(string),
|
||||
}
|
||||
db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stockInfo.Code).Updates(stockInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if market == "us" {
|
||||
stockInfo := &models.StockInfoUS{
|
||||
Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".US",
|
||||
Name: stock["f14"].(string),
|
||||
BKName: stock["f100"].(string),
|
||||
BKCode: stock["f265"].(string),
|
||||
Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".US",
|
||||
Name: stock["f14"].(string),
|
||||
}
|
||||
db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stockInfo.Code).First(stockInfo)
|
||||
logger.SugaredLogger.Infof("stockInfo:%+v", stockInfo)
|
||||
if stockInfo.ID == 0 {
|
||||
db.Dao.Model(&models.StockInfoUS{}).Create(stockInfo)
|
||||
} else {
|
||||
stockInfo = &models.StockInfoUS{
|
||||
Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".US",
|
||||
Name: stock["f14"].(string),
|
||||
BKName: stock["f100"].(string),
|
||||
BKCode: stock["f265"].(string),
|
||||
}
|
||||
db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stockInfo.Code).Updates(stockInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1592,25 +1571,6 @@ func (receiver StockDataApi) getDCStockInfo(market string, page, pageSize int) {
|
||||
}
|
||||
}
|
||||
|
||||
func DCToTsCode(dcCode string) string {
|
||||
//北京证券交易所 8(83、87、88 等) 创新型中小企业(专精特新为主)
|
||||
//上海证券交易所 6(60、688 等) 大盘蓝筹、科创板(高新技术)
|
||||
//深圳证券交易所 0、3(000、002、30 等) 中小盘、创业板(成长型创新企业)
|
||||
switch dcCode[0:1] {
|
||||
case "8":
|
||||
return dcCode + ".BJ"
|
||||
case "9":
|
||||
return dcCode + ".BJ"
|
||||
case "6":
|
||||
return dcCode + ".SH"
|
||||
case "0":
|
||||
return dcCode + ".SZ"
|
||||
case "3":
|
||||
return dcCode + ".SZ"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (receiver StockDataApi) GetHKStockInfo(pageSize int) {
|
||||
url := "https://stock.gtimg.cn/data/hk_rank.php?board=main_all&metric=price&pageSize=%d&reqPage=1&order=desc&var_name=list_data"
|
||||
resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R().
|
||||
@@ -1745,11 +1705,6 @@ func (receiver StockDataApi) GetCommonKLineData(stockCode string, kLineType stri
|
||||
return K
|
||||
}
|
||||
|
||||
// GetStockHistoryMoneyData 获取股票历史资金流向数据
|
||||
func (receiver StockDataApi) GetStockHistoryMoneyData() {
|
||||
|
||||
}
|
||||
|
||||
// JSONToMarkdownTable 将JSON数据转换为Markdown表格
|
||||
func JSONToMarkdownTable(jsonData []byte) (string, error) {
|
||||
var data []map[string]interface{}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package data
|
||||
|
||||
import "os"
|
||||
|
||||
// CheckChrome 检查 macOS 是否安装了 Chrome 浏览器
|
||||
func CheckChrome() (string, bool) {
|
||||
// 检查 /Applications 目录下是否存在 Chrome
|
||||
locations := []string{
|
||||
// Mac
|
||||
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
}
|
||||
path := ""
|
||||
for _, location := range locations {
|
||||
_, err := os.Stat(location)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
path = location
|
||||
}
|
||||
if path == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return path, true
|
||||
}
|
||||
|
||||
// CheckBrowser 检查 macOS 是否安装了浏览器,并返回安装路径
|
||||
func CheckBrowser() (string, bool) {
|
||||
if path, ok := CheckChrome(); ok {
|
||||
return path, ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
@@ -4,19 +4,17 @@ 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
|
||||
@@ -50,23 +48,14 @@ 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(searchWords, "telegram", 30)
|
||||
messages := SearchStockInfo("谷歌", "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")
|
||||
@@ -74,7 +63,7 @@ func TestSearchStockInfoByCode(t *testing.T) {
|
||||
|
||||
func TestSearchStockPriceInfo(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
SearchStockPriceInfo("博安生物", "hk06955", 30)
|
||||
//SearchStockPriceInfo("中信证券", "hk06030", 30)
|
||||
SearchStockPriceInfo("上海贝岭", "sh600171", 30)
|
||||
//SearchStockPriceInfo("苹果公司", "gb_aapl", 30)
|
||||
//SearchStockPriceInfo("微创光电", "bj430198", 30)
|
||||
@@ -121,8 +110,7 @@ func TestGetHKStockInfo(t *testing.T) {
|
||||
//NewStockDataApi().GetSinaHKStockInfo()
|
||||
//m:105,m:106,m:107 //美股
|
||||
//m:128+t:3,m:128+t:4,m:128+t:1,m:128+t:2 //港股
|
||||
//287 224 605
|
||||
for i := 1; i <= 605; i++ {
|
||||
for i := 1; i <= 592; i++ {
|
||||
NewStockDataApi().getDCStockInfo("us", i, 20)
|
||||
time.Sleep(time.Duration(random.RandInt(1, 3)) * time.Second)
|
||||
}
|
||||
@@ -265,20 +253,3 @@ func TestStockDataApi_GetIndexBasic(t *testing.T) {
|
||||
stockDataApi := NewStockDataApi()
|
||||
stockDataApi.GetIndexBasic()
|
||||
}
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
|
||||
stockBasics := &[]StockBasic{}
|
||||
resty.New().R().
|
||||
SetHeader("user", "go-stock").
|
||||
SetResult(stockBasics).
|
||||
Get("http://8.134.249.145:18080/go-stock/stock_basic.json")
|
||||
|
||||
db.Dao.Unscoped().Model(&StockBasic{}).Where("1=1").Delete(&StockBasic{})
|
||||
err := db.Dao.CreateInBatches(stockBasics, 400).Error
|
||||
if err != nil {
|
||||
t.Log(err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package data
|
||||
|
||||
import "golang.org/x/sys/windows/registry"
|
||||
|
||||
// CheckChrome 在 Windows 系统上检查谷歌浏览器是否安装
|
||||
func CheckChrome() (string, bool) {
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
||||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
defer key.Close()
|
||||
}
|
||||
defer key.Close()
|
||||
path, _, err := key.GetStringValue("Path")
|
||||
//logger.SugaredLogger.Infof("Chrome安装路径:%s", path)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return path + "\\chrome.exe", true
|
||||
}
|
||||
|
||||
// CheckBrowser 在 Windows 系统上检查Edge浏览器是否安装,并返回安装路径
|
||||
func CheckBrowser() (string, bool) {
|
||||
if path, ok := CheckChrome(); ok {
|
||||
return path, true
|
||||
}
|
||||
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
||||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
defer key.Close()
|
||||
}
|
||||
defer key.Close()
|
||||
path, _, err := key.GetStringValue("Path")
|
||||
//logger.SugaredLogger.Infof("Edge安装路径:%s", path)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return path + "\\msedge.exe", true
|
||||
}
|
||||
@@ -39,74 +39,17 @@ func NewStockGroupApi(dao *gorm.DB) *StockGroupApi {
|
||||
}
|
||||
|
||||
func (receiver StockGroupApi) AddGroup(group Group) bool {
|
||||
// 检查是否已存在相同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
|
||||
err := receiver.dao.Where("name = ?", group.Name).FirstOrCreate(&group).Updates(&Group{
|
||||
Name: group.Name,
|
||||
Sort: group.Sort,
|
||||
}).Error
|
||||
return err == nil
|
||||
}
|
||||
func (receiver StockGroupApi) GetGroupList() []Group {
|
||||
var groups []Group
|
||||
receiver.dao.Order("sort ASC").Find(&groups)
|
||||
receiver.dao.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(¤tGroup, 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)
|
||||
|
||||
@@ -2,20 +2,11 @@ package data
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"go-stock/backend/models"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/duke-git/lancet/v2/fileutil"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
"github.com/go-ego/gse"
|
||||
"go-stock/backend/logger"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 金融情感词典,包含股票市场相关的专业词汇
|
||||
@@ -24,7 +15,7 @@ var (
|
||||
|
||||
// 正面金融词汇及其权重
|
||||
positiveFinanceWords = map[string]float64{
|
||||
"涨": 1.0, "上涨": 2.0, "涨停": 3.0, "牛市": 3.0, "反弹": 2.0, "新高": 2.5,
|
||||
"上涨": 2.0, "涨停": 3.0, "牛市": 3.0, "反弹": 2.0, "新高": 2.5,
|
||||
"利好": 2.5, "增持": 2.0, "买入": 2.0, "推荐": 1.5, "看多": 2.0,
|
||||
"盈利": 2.0, "增长": 2.0, "超预期": 2.5, "强劲": 1.5, "回升": 1.5,
|
||||
"复苏": 2.0, "突破": 2.0, "创新高": 3.0, "回暖": 1.5, "上扬": 1.5,
|
||||
@@ -35,13 +26,13 @@ var (
|
||||
|
||||
// 负面金融词汇及其权重
|
||||
negativeFinanceWords = map[string]float64{
|
||||
"跌": 1.0, "下跌": 2.0, "跌停": 3.0, "熊市": 3.0, "回调": 1.5, "新低": 2.5,
|
||||
"下跌": 2.0, "跌停": 3.0, "熊市": 3.0, "回调": 1.5, "新低": 2.5,
|
||||
"利空": 2.5, "减持": 2.0, "卖出": 2.0, "看空": 2.0, "亏损": 2.5,
|
||||
"下滑": 2.0, "萎缩": 2.0, "不及预期": 2.5, "疲软": 1.5, "恶化": 2.0,
|
||||
"衰退": 2.0, "跌破": 2.0, "创新低": 3.0, "走弱": 1.5, "下挫": 1.5,
|
||||
"利空消息": 3.0, "收益下降": 2.5, "利润下滑": 2.5, "业绩不佳": 2.5,
|
||||
"垃圾股": 2.0, "风险股": 2.0, "弱势": 1.5, "走低": 1.5, "缩量": 2.5,
|
||||
"大跌": 2.5, "暴跌": 3.0, "崩盘": 3.0, "跳水": 3.0, "重挫": 3.0, "跌超": 2.5, "跌逾": 2.5,
|
||||
"大跌": 2.5, "暴跌": 3.0, "崩盘": 3.0, "跳水": 3.0, "重挫": 3.0,
|
||||
}
|
||||
|
||||
// 否定词,用于反转情感极性
|
||||
@@ -53,7 +44,7 @@ var (
|
||||
degreeWords = map[string]float64{
|
||||
"非常": 1.8, "极其": 2.2, "太": 1.8, "很": 1.5,
|
||||
"比较": 0.8, "稍微": 0.6, "有点": 0.7, "显著": 1.5,
|
||||
"大幅": 1.8, "急剧": 2.0, "轻微": 0.6, "小幅": 0.7, "逾": 1.8,
|
||||
"大幅": 1.8, "急剧": 2.0, "轻微": 0.6, "小幅": 0.7,
|
||||
}
|
||||
|
||||
// 转折词,用于识别情感转折
|
||||
@@ -62,194 +53,12 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
//go:embed data/dict/base.txt
|
||||
var baseDict string
|
||||
|
||||
//go:embed data/dict/zh/s_1.txt
|
||||
var zhDict string
|
||||
|
||||
func InitAnalyzeSentiment() {
|
||||
logger.SugaredLogger.Infof("初始化词典库路径:%s", fileutil.CurrentPath())
|
||||
//加载默认词典
|
||||
err := seg.LoadDictEmbed(zhDict)
|
||||
err = seg.LoadDictEmbed(baseDict)
|
||||
func init() {
|
||||
// 加载默认词典
|
||||
err := seg.LoadDict()
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
}
|
||||
stocks := &[]StockBasic{}
|
||||
db.Dao.Model(&StockBasic{}).Find(stocks)
|
||||
for _, stock := range *stocks {
|
||||
if strutil.Trim(stock.Name) == "" {
|
||||
continue
|
||||
}
|
||||
err := seg.AddToken(stock.Name, 188888, "n")
|
||||
if strutil.Trim(stock.BKName) != "" {
|
||||
err = seg.AddToken(stock.BKName, 188888, "n")
|
||||
}
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("添加%s失败:%s", stock.Name, err.Error())
|
||||
}
|
||||
}
|
||||
stockhks := &[]models.StockInfoHK{}
|
||||
db.Dao.Model(&models.StockInfoHK{}).Find(stockhks)
|
||||
for _, stock := range *stockhks {
|
||||
if strutil.Trim(stock.Name) == "" {
|
||||
continue
|
||||
}
|
||||
err := seg.AddToken(stock.Name, 188888, "n")
|
||||
if strutil.Trim(stock.BKName) != "" {
|
||||
err = seg.AddToken(stock.BKName, 188888, "n")
|
||||
}
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("添加%s失败:%s", stock.Name, err.Error())
|
||||
}
|
||||
}
|
||||
//stockus := &[]models.StockInfoUS{}
|
||||
//db.Dao.Model(&models.StockInfoUS{}).Where("trim(name) != ?", "").Find(stockus)
|
||||
//for _, stock := range *stockus {
|
||||
// err := seg.AddToken(stock.Name, 500)
|
||||
// if err != nil {
|
||||
// logger.SugaredLogger.Errorf("添加%s失败:%s", stock.Name, err.Error())
|
||||
// }
|
||||
//}
|
||||
tags := &[]models.Tags{}
|
||||
db.Dao.Model(&models.Tags{}).Find(tags)
|
||||
for _, tag := range *tags {
|
||||
err := seg.AddToken(tag.Name, 188888, "n")
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("添加%s失败:%s", tag.Name, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
logger.SugaredLogger.Info("加载词典成功")
|
||||
}
|
||||
|
||||
// WordFreqWithWeight 词频统计结果,包含权重信息
|
||||
type WordFreqWithWeight struct {
|
||||
Word string
|
||||
Frequency int
|
||||
Weight float64
|
||||
}
|
||||
|
||||
// getWordWeight 获取词汇权重
|
||||
func getWordWeight(word string) float64 {
|
||||
// 从分词器获取词汇权重
|
||||
freq, pos, _ := seg.Find(word)
|
||||
if pos == "n" {
|
||||
return freq
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// SortByWeightAndFrequency 按权重和频次排序词频结果
|
||||
func SortByWeightAndFrequency(frequencies map[string]WordFreqWithWeight) []WordFreqWithWeight {
|
||||
// 将map转换为slice以便排序
|
||||
freqSlice := make([]WordFreqWithWeight, 0, len(frequencies))
|
||||
for _, freq := range frequencies {
|
||||
freqSlice = append(freqSlice, freq)
|
||||
}
|
||||
|
||||
// 按权重降序排列,如果权重相同则按频次降序排列
|
||||
sort.Slice(freqSlice, func(i, j int) bool {
|
||||
if freqSlice[i].Weight != freqSlice[j].Weight {
|
||||
return freqSlice[i].Weight > freqSlice[j].Weight // 权重高的排前面
|
||||
}
|
||||
return freqSlice[i].Frequency > freqSlice[j].Frequency // 权重相同时频次高的排前面
|
||||
})
|
||||
|
||||
return freqSlice
|
||||
}
|
||||
|
||||
// FilterAndSortWords 过滤标点符号并按权重频次排序
|
||||
func FilterAndSortWords(frequencies map[string]WordFreqWithWeight) []WordFreqWithWeight {
|
||||
// 先过滤标点符号和分隔符
|
||||
cleanFrequencies := FilterPunctuationAndSeparators(frequencies)
|
||||
|
||||
// 再按权重和频次排序
|
||||
sortedFrequencies := SortByWeightAndFrequency(cleanFrequencies)
|
||||
|
||||
return sortedFrequencies
|
||||
}
|
||||
func FilterPunctuationAndSeparators(frequencies map[string]WordFreqWithWeight) map[string]WordFreqWithWeight {
|
||||
filteredWords := make(map[string]WordFreqWithWeight)
|
||||
|
||||
for word, freqInfo := range frequencies {
|
||||
// 过滤纯标点符号和分隔符
|
||||
if !isPunctuationOrSeparator(word) {
|
||||
filteredWords[word] = freqInfo
|
||||
}
|
||||
}
|
||||
return filteredWords
|
||||
}
|
||||
|
||||
// isPunctuationOrSeparator 判断是否为标点符号或分隔符
|
||||
func isPunctuationOrSeparator(word string) bool {
|
||||
// 空字符串
|
||||
if strings.TrimSpace(word) == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否全部由标点符号组成
|
||||
for _, r := range word {
|
||||
if !unicode.IsPunct(r) && !unicode.IsSymbol(r) && !unicode.IsSpace(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// FilterWithRegex 使用正则表达式过滤标点和特殊字符
|
||||
func FilterWithRegex(frequencies map[string]WordFreqWithWeight) map[string]WordFreqWithWeight {
|
||||
filteredWords := make(map[string]WordFreqWithWeight)
|
||||
|
||||
// 匹配标点符号、特殊字符的正则表达式
|
||||
punctuationRegex := regexp.MustCompile(`^[[:punct:][:space:]]+$`)
|
||||
|
||||
for word, freqInfo := range frequencies {
|
||||
// 过滤纯标点符号
|
||||
if !punctuationRegex.MatchString(word) && strings.TrimSpace(word) != "" {
|
||||
filteredWords[word] = freqInfo
|
||||
}
|
||||
}
|
||||
return filteredWords
|
||||
}
|
||||
|
||||
// countWordFrequencyWithWeight 统计词频并包含权重信息
|
||||
func countWordFrequencyWithWeight(text string) map[string]WordFreqWithWeight {
|
||||
words := splitWords(text)
|
||||
freqMap := make(map[string]WordFreqWithWeight)
|
||||
|
||||
// 统计词频
|
||||
wordCount := make(map[string]int)
|
||||
for _, word := range words {
|
||||
wordCount[word]++
|
||||
}
|
||||
|
||||
// 构建包含权重的结果
|
||||
for word, frequency := range wordCount {
|
||||
weight := getWordWeight(word)
|
||||
if weight > 100 {
|
||||
freqMap[word] = WordFreqWithWeight{
|
||||
Word: word,
|
||||
Frequency: frequency,
|
||||
Weight: weight,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return freqMap
|
||||
}
|
||||
|
||||
// AnalyzeSentimentWithFreqWeight 带权重词频统计的情感分析
|
||||
func AnalyzeSentimentWithFreqWeight(text string) (SentimentResult, map[string]WordFreqWithWeight) {
|
||||
// 原有情感分析逻辑
|
||||
result := AnalyzeSentiment(text)
|
||||
|
||||
// 带权重的词频统计
|
||||
frequencies := countWordFrequencyWithWeight(text)
|
||||
|
||||
return result, frequencies
|
||||
}
|
||||
|
||||
// SentimentResult 情感分析结果类型
|
||||
|
||||
@@ -2,11 +2,8 @@ package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-stock/backend/logger"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
@@ -15,28 +12,25 @@ import (
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
func TestAnalyzeSentiment(t *testing.T) {
|
||||
|
||||
news := NewMarketNewsApi().GetNewsList2("", random.RandInt(500, 1000))
|
||||
messageText := strings.Builder{}
|
||||
for _, telegraph := range *news {
|
||||
messageText.WriteString(telegraph.Content + "\n")
|
||||
}
|
||||
|
||||
text := messageText.String()
|
||||
// 分析情感
|
||||
text := " 【调查:韩国近两成中小学生过度使用智能手机或互联网】财联社6月19日电,韩国女性家族部18日公布的一项年度调查结果显示,接受调查的韩国中小学生中,共计约17.3%、即超过21万人使用智能手机或互联网的程度达到了“危险水平”,这意味着他们因过度依赖智能手机或互联网而需要关注或干预,这一比例引人担忧。 (新华社)\n"
|
||||
text = "消息人士称,联合利华(Unilever)正在为Graze零食品牌寻找买家。\n"
|
||||
text = "【韩国未来5年将投入51万亿韩元发展文化产业】 据韩联社,韩国文化体育观光部(文体部)今后5年将投入51万亿韩元(约合人民币2667亿元)预算,落实总统李在明在竞选时期提出的“将韩国打造成全球五大文化强国之一”的承诺。\n"
|
||||
//text = "【油气股持续拉升 国际实业午后涨停】财联社6月19日电,油气股午后持续拉升,国际实业、宝莫股份午后涨停,准油股份、山东墨龙。茂化实华此前涨停,通源石油、海默科技、贝肯能源、中曼石油、科力股份等多股涨超5%。\n"
|
||||
//text = " 【三大指数均跌逾1% 下跌个股近4800只】财联社6月19日电,指数持续走弱,沪指下挫跌逾1.00%,深成指跌1.25%,创业板指跌1.39%。核聚变、风电、军工、食品消费等板块指数跌幅居前,沪深京三市下跌个股近4800只。\n"
|
||||
text = "【银行理财首单网下打新落地】财联社6月20日电,记者从多渠道获悉,光大理财以申报价格17元参与信通电子网下打新,并成功入围有效报价,成为行业内首家参与网下打新的银行理财公司。光大理财工作人员向证券时报记者表示,本次光大理财是以其管理的混合类产品“阳光橙增盈绝对收益策略”参与了此次网下打新,该产品为光大理财“固收+”银行理财产品。资料显示,信通电子成立于1996年,核心产品包括输电线路智能巡检系统、变电站智能辅控系统、移动智能终端及其他产品。根据其招股说明书,信通电子2023、2024年营业收入分别较上年增长19.08%和7.97%,净利润分别较上年增长5.6%和15.11%。 (证券时报)"
|
||||
text = " 【以军称拦截数枚伊朗导弹】财联社6月20日电,据央视新闻报道,以军在贝尔谢巴及周边区域拦截了数枚伊朗导弹,但仍有导弹或拦截残骸落地。以色列国防军发文表示,搜救队伍正在一处“空中物体落地”的所在区域开展工作,公众目前可以离开避难场所。伊朗方面对上述说法暂无回应。"
|
||||
|
||||
words := splitWords(text)
|
||||
fmt.Println(strings.Join(words, " "))
|
||||
|
||||
result := AnalyzeSentiment(text)
|
||||
result, frequencies := AnalyzeSentimentWithFreqWeight(text)
|
||||
// 过滤标点符号和分隔符
|
||||
cleanFrequencies := FilterPunctuationAndSeparators(frequencies)
|
||||
|
||||
// 输出结果
|
||||
logger.SugaredLogger.Infof("情感分析结果: %s (得分: %.2f, 正面词:%d, 负面词:%d)\n 词频统计结果: %v",
|
||||
fmt.Printf("情感分析结果: %s (得分: %.2f, 正面词:%d, 负面词:%d)\n",
|
||||
result.Description,
|
||||
result.Score,
|
||||
result.PositiveCount,
|
||||
result.NegativeCount,
|
||||
cleanFrequencies,
|
||||
)
|
||||
result.NegativeCount)
|
||||
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ import (
|
||||
|
||||
type TushareApi struct {
|
||||
client *resty.Client
|
||||
config *SettingConfig
|
||||
config *Settings
|
||||
}
|
||||
|
||||
func NewTushareApi(config *SettingConfig) *TushareApi {
|
||||
func NewTushareApi(config *Settings) *TushareApi {
|
||||
return &TushareApi{
|
||||
client: resty.New(),
|
||||
config: config,
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// -----------------------------------------------------------------------------------
|
||||
func TestGetDaily(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
tushareApi := NewTushareApi(GetSettingConfig())
|
||||
tushareApi := NewTushareApi(GetConfig())
|
||||
res := tushareApi.GetDaily("00927.HK", "20250101", "20250217", 30)
|
||||
t.Log(res)
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestGetDaily(t *testing.T) {
|
||||
|
||||
func TestGetUSDaily(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
tushareApi := NewTushareApi(GetSettingConfig())
|
||||
tushareApi := NewTushareApi(GetConfig())
|
||||
|
||||
res := tushareApi.GetDaily("gb_AAPL", "20250101", "20250217", 30)
|
||||
t.Log(res)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/plugin/soft_delete"
|
||||
"time"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
@@ -151,15 +150,13 @@ func (receiver AIResponseResult) TableName() string {
|
||||
|
||||
type VersionInfo struct {
|
||||
gorm.Model
|
||||
Version string `json:"version"`
|
||||
Content string `json:"content"`
|
||||
Icon string `json:"icon"`
|
||||
Alipay string `json:"alipay"`
|
||||
Wxpay string `json:"wxpay"`
|
||||
Wxgzh string `json:"wxgzh"`
|
||||
BuildTimeStamp int64 `json:"buildTimeStamp"`
|
||||
OfficialStatement string `json:"officialStatement"`
|
||||
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
|
||||
Version string `json:"version"`
|
||||
Content string `json:"content"`
|
||||
Icon string `json:"icon"`
|
||||
Alipay string `json:"alipay"`
|
||||
Wxpay string `json:"wxpay"`
|
||||
BuildTimeStamp int64 `json:"buildTimeStamp"`
|
||||
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
|
||||
}
|
||||
|
||||
func (receiver VersionInfo) TableName() string {
|
||||
@@ -173,8 +170,6 @@ type StockInfoHK struct {
|
||||
FullName string `json:"fullName"`
|
||||
EName string `json:"eName"`
|
||||
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
|
||||
BKName string `json:"bk_name"`
|
||||
BKCode string `json:"bk_code"`
|
||||
}
|
||||
|
||||
func (receiver StockInfoHK) TableName() string {
|
||||
@@ -190,8 +185,6 @@ type StockInfoUS struct {
|
||||
Exchange string `json:"exchange"`
|
||||
Type string `json:"type"`
|
||||
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
|
||||
BKName string `json:"bk_name"`
|
||||
BKCode string `json:"bk_code"`
|
||||
}
|
||||
|
||||
func (receiver StockInfoUS) TableName() string {
|
||||
@@ -201,12 +194,6 @@ func (receiver StockInfoUS) TableName() string {
|
||||
type Resp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Error struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Param string `json:"param"`
|
||||
Type string `json:"type"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
type PromptTemplate struct {
|
||||
@@ -232,7 +219,6 @@ type Prompt struct {
|
||||
type Telegraph struct {
|
||||
gorm.Model
|
||||
Time string `json:"time"`
|
||||
DataTime *time.Time `json:"dataTime"`
|
||||
Content string `json:"content"`
|
||||
SubjectTags []string `json:"subjects" gorm:"-:all"`
|
||||
StocksTags []string `json:"stocks" gorm:"-:all"`
|
||||
@@ -240,7 +226,7 @@ type Telegraph struct {
|
||||
Url string `json:"url"`
|
||||
Source string `json:"source"`
|
||||
TelegraphTags []TelegraphTags `json:"tags" gorm:"-:migration;foreignKey:TelegraphId"`
|
||||
SentimentResult string `json:"sentimentResult"`
|
||||
SentimentResult string `json:"sentimentResult" gorm:"-:all"`
|
||||
}
|
||||
type TelegraphTags struct {
|
||||
gorm.Model
|
||||
@@ -376,329 +362,3 @@ type HotEvent struct {
|
||||
StatusCount int `json:"status_count"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type GDP struct {
|
||||
REPORTDATE string `json:"REPORT_DATE" md:"报告时间"`
|
||||
TIME string `json:"TIME" md:"报告期"`
|
||||
DOMESTICLPRODUCTBASE float64 `json:"DOMESTICL_PRODUCT_BASE" md:"国内生产总值(亿元)"`
|
||||
SUMSAME float64 `json:"SUM_SAME" md:"国内生产总值同比增长(%)"`
|
||||
FIRSTPRODUCTBASE float64 `json:"FIRST_PRODUCT_BASE" md:"第一产业(亿元)"`
|
||||
FIRSTSAME int `json:"FIRST_SAME" md:"第一产业同比增长(%)"`
|
||||
SECONDPRODUCTBASE float64 `json:"SECOND_PRODUCT_BASE" md:"第二产业(亿元)"`
|
||||
SECONDSAME float64 `json:"SECOND_SAME" md:"第二产业同比增长(%)"`
|
||||
THIRDPRODUCTBASE float64 `json:"THIRD_PRODUCT_BASE" md:"第三产业(亿元)"`
|
||||
THIRDSAME float64 `json:"THIRD_SAME" md:"第三产业同比增长(%)"`
|
||||
}
|
||||
type CPI struct {
|
||||
REPORTDATE string `json:"REPORT_DATE" md:"报告时间"`
|
||||
TIME string `json:"TIME" md:"报告期"`
|
||||
NATIONALBASE float64 `json:"NATIONAL_BASE" md:"全国当月"`
|
||||
NATIONALSAME float64 `json:"NATIONAL_SAME" md:"全国当月同比增长(%)"`
|
||||
NATIONALSEQUENTIAL float64 `json:"NATIONAL_SEQUENTIAL" md:"全国当月环比增长(%)"`
|
||||
NATIONALACCUMULATE float64 `json:"NATIONAL_ACCUMULATE" md:"全国当月累计"`
|
||||
CITYBASE float64 `json:"CITY_BASE" md:"城市当月"`
|
||||
CITYSAME float64 `json:"CITY_SAME" md:"城市当月同比增长(%)"`
|
||||
CITYSEQUENTIAL float64 `json:"CITY_SEQUENTIAL" md:"城市当月环比增长(%)"`
|
||||
CITYACCUMULATE int `json:"CITY_ACCUMULATE" md:"城市当月累计"`
|
||||
RURALBASE float64 `json:"RURAL_BASE" md:"农村当月"`
|
||||
RURALSAME float64 `json:"RURAL_SAME" md:"农村当月同比增长(%)"`
|
||||
RURALSEQUENTIAL int `json:"RURAL_SEQUENTIAL" md:"农村当月环比增长(%)"`
|
||||
RURALACCUMULATE float64 `json:"RURAL_ACCUMULATE" md:"农村当月累计"`
|
||||
}
|
||||
type PPI struct {
|
||||
REPORTDATE string `json:"REPORT_DATE" md:"报告时间"`
|
||||
TIME string `json:"TIME" md:"报告期"`
|
||||
BASE float64 `json:"BASE" md:"当月"`
|
||||
BASESAME float64 `json:"BASE_SAME" md:"当月同比增长(%)"`
|
||||
BASEACCUMULATE float64 `json:"BASE_ACCUMULATE" md:"累计"`
|
||||
}
|
||||
type PMI struct {
|
||||
REPORTDATE string `md:"报告时间" json:"REPORT_DATE"`
|
||||
TIME string `md:"报告期" json:"TIME"`
|
||||
MAKEINDEX float64 `md:"制造业指数" json:"MAKE_INDEX"`
|
||||
MAKESAME float64 `md:"制造业指数同比增长(%)" json:"MAKE_SAME"`
|
||||
NMAKEINDEX float64 `md:"非制造业" json:"NMAKE_INDEX"`
|
||||
NMAKESAME float64 `md:"非制造业同比增长(%)" json:"NMAKE_SAME"`
|
||||
}
|
||||
|
||||
type DCResp struct {
|
||||
Version string `json:"version"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
type GDPResult struct {
|
||||
Pages int `json:"pages"`
|
||||
Data []GDP `json:"data"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
type CPIResult struct {
|
||||
Pages int `json:"pages"`
|
||||
Data []CPI `json:"data"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type PPIResult struct {
|
||||
Pages int `json:"pages"`
|
||||
Data []PPI `json:"data"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
type PMIResult struct {
|
||||
Pages int `json:"pages"`
|
||||
Data []PMI `json:"data"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
type GDPResp struct {
|
||||
DCResp
|
||||
GDPResult GDPResult `json:"result"`
|
||||
}
|
||||
|
||||
type CPIResp struct {
|
||||
DCResp
|
||||
CPIResult CPIResult `json:"result"`
|
||||
}
|
||||
|
||||
type PPIResp struct {
|
||||
DCResp
|
||||
PPIResult PPIResult `json:"result"`
|
||||
}
|
||||
type PMIResp struct {
|
||||
DCResp
|
||||
PMIResult PMIResult `json:"result"`
|
||||
}
|
||||
|
||||
type OldSettings struct {
|
||||
gorm.Model
|
||||
TushareToken string `json:"tushareToken"`
|
||||
LocalPushEnable bool `json:"localPushEnable"`
|
||||
DingPushEnable bool `json:"dingPushEnable"`
|
||||
DingRobot string `json:"dingRobot"`
|
||||
UpdateBasicInfoOnStart bool `json:"updateBasicInfoOnStart"`
|
||||
RefreshInterval int64 `json:"refreshInterval"`
|
||||
|
||||
OpenAiEnable bool `json:"openAiEnable"`
|
||||
OpenAiBaseUrl string `json:"openAiBaseUrl"`
|
||||
OpenAiApiKey string `json:"openAiApiKey"`
|
||||
OpenAiModelName string `json:"openAiModelName"`
|
||||
OpenAiMaxTokens int `json:"openAiMaxTokens"`
|
||||
OpenAiTemperature float64 `json:"openAiTemperature"`
|
||||
OpenAiApiTimeOut int `json:"openAiApiTimeOut"`
|
||||
Prompt string `json:"prompt"`
|
||||
CheckUpdate bool `json:"checkUpdate"`
|
||||
QuestionTemplate string `json:"questionTemplate"`
|
||||
CrawlTimeOut int64 `json:"crawlTimeOut"`
|
||||
KDays int64 `json:"kDays"`
|
||||
EnableDanmu bool `json:"enableDanmu"`
|
||||
BrowserPath string `json:"browserPath"`
|
||||
EnableNews bool `json:"enableNews"`
|
||||
DarkTheme bool `json:"darkTheme"`
|
||||
BrowserPoolSize int `json:"browserPoolSize"`
|
||||
EnableFund bool `json:"enableFund"`
|
||||
EnablePushNews bool `json:"enablePushNews"`
|
||||
SponsorCode string `json:"sponsorCode"`
|
||||
}
|
||||
|
||||
func (receiver OldSettings) TableName() string {
|
||||
return "settings"
|
||||
}
|
||||
|
||||
type ReutersNews struct {
|
||||
StatusCode int `json:"statusCode"`
|
||||
Message string `json:"message"`
|
||||
Result struct {
|
||||
ParentSectionName string `json:"parent_section_name"`
|
||||
Pagination struct {
|
||||
Size int `json:"size"`
|
||||
ExpectedSize int `json:"expected_size"`
|
||||
TotalSize int `json:"total_size"`
|
||||
Orderby string `json:"orderby"`
|
||||
} `json:"pagination"`
|
||||
DateModified time.Time `json:"date_modified"`
|
||||
FetchType string `json:"fetch_type"`
|
||||
Articles []struct {
|
||||
Id string `json:"id"`
|
||||
CanonicalUrl string `json:"canonical_url"`
|
||||
Website string `json:"website"`
|
||||
Web string `json:"web"`
|
||||
Native string `json:"native"`
|
||||
UpdatedTime time.Time `json:"updated_time"`
|
||||
PublishedTime time.Time `json:"published_time"`
|
||||
ArticleType string `json:"article_type"`
|
||||
DisplayMyNews bool `json:"display_my_news"`
|
||||
DisplayNewsletterSignup bool `json:"display_newsletter_signup"`
|
||||
DisplayNotifications bool `json:"display_notifications"`
|
||||
DisplayRelatedMedia bool `json:"display_related_media"`
|
||||
DisplayRelatedOrganizations bool `json:"display_related_organizations"`
|
||||
ContentCode string `json:"content_code"`
|
||||
Source struct {
|
||||
Name string `json:"name"`
|
||||
OriginalName string `json:"original_name"`
|
||||
} `json:"source"`
|
||||
Title string `json:"title"`
|
||||
BasicHeadline string `json:"basic_headline"`
|
||||
Distributor string `json:"distributor"`
|
||||
Description string `json:"description"`
|
||||
PrimaryMediaType string `json:"primary_media_type,omitempty"`
|
||||
PrimaryTag struct {
|
||||
ShortBio string `json:"short_bio"`
|
||||
Description string `json:"description"`
|
||||
Slug string `json:"slug"`
|
||||
Text string `json:"text"`
|
||||
TopicUrl string `json:"topic_url"`
|
||||
CanFollow bool `json:"can_follow,omitempty"`
|
||||
IsTopic bool `json:"is_topic,omitempty"`
|
||||
} `json:"primary_tag"`
|
||||
WordCount int `json:"word_count"`
|
||||
ReadMinutes int `json:"read_minutes"`
|
||||
Kicker struct {
|
||||
Path string `json:"path"`
|
||||
Names []string `json:"names"`
|
||||
Name string `json:"name,omitempty"`
|
||||
} `json:"kicker"`
|
||||
AdTopics []string `json:"ad_topics"`
|
||||
Thumbnail struct {
|
||||
Url string `json:"url"`
|
||||
Caption string `json:"caption,omitempty"`
|
||||
Type string `json:"type"`
|
||||
ResizerUrl string `json:"resizer_url"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Authors string `json:"authors,omitempty"`
|
||||
AltText string `json:"alt_text"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
Slug string `json:"slug,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Company string `json:"company,omitempty"`
|
||||
PurchaseLicensingPath string `json:"purchase_licensing_path,omitempty"`
|
||||
} `json:"thumbnail"`
|
||||
Authors []struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
FirstName string `json:"first_name,omitempty"`
|
||||
LastName string `json:"last_name,omitempty"`
|
||||
Company string `json:"company"`
|
||||
Thumbnail struct {
|
||||
Url string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
ResizerUrl string `json:"resizer_url"`
|
||||
} `json:"thumbnail"`
|
||||
SocialLinks []struct {
|
||||
Site string `json:"site"`
|
||||
Url string `json:"url"`
|
||||
} `json:"social_links,omitempty"`
|
||||
Byline string `json:"byline"`
|
||||
Description string `json:"description,omitempty"`
|
||||
TopicUrl string `json:"topic_url,omitempty"`
|
||||
Role string `json:"role,omitempty"`
|
||||
} `json:"authors"`
|
||||
DisplayTime time.Time `json:"display_time"`
|
||||
ThumbnailDark struct {
|
||||
Url string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
ResizerUrl string `json:"resizer_url"`
|
||||
Id string `json:"id"`
|
||||
AltText string `json:"alt_text"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
} `json:"thumbnail_dark,omitempty"`
|
||||
} `json:"articles"`
|
||||
Section struct {
|
||||
Id string `json:"id"`
|
||||
AdUnitCode string `json:"ad_unit_code"`
|
||||
Website string `json:"website"`
|
||||
Name string `json:"name"`
|
||||
PageTitle string `json:"page_title"`
|
||||
CanFollow bool `json:"can_follow"`
|
||||
Language string `json:"language"`
|
||||
Type string `json:"type"`
|
||||
Advertising struct {
|
||||
Sponsored string `json:"sponsored"`
|
||||
} `json:"advertising"`
|
||||
VideoPlaylistId string `json:"video_playlistId"`
|
||||
MobileAdUnitPath string `json:"mobile_ad_unit_path"`
|
||||
AdUnitPath string `json:"ad_unit_path"`
|
||||
CollectionAlias string `json:"collection_alias"`
|
||||
SectionAbout string `json:"section_about"`
|
||||
Title string `json:"title"`
|
||||
Personalization struct {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
ShowTags bool `json:"show_tags"`
|
||||
CanFollow bool `json:"can_follow"`
|
||||
} `json:"personalization"`
|
||||
} `json:"section"`
|
||||
AdUnitPath string `json:"ad_unit_path"`
|
||||
ResponseTime int64 `json:"response_time"`
|
||||
} `json:"result"`
|
||||
Id string `json:"_id"`
|
||||
}
|
||||
|
||||
type InteractiveAnswer struct {
|
||||
PageNo int `json:"pageNo"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalRecord int `json:"totalRecord"`
|
||||
TotalPage int `json:"totalPage"`
|
||||
Results []InteractiveAnswerResults `json:"results"`
|
||||
Count bool `json:"count"`
|
||||
}
|
||||
|
||||
type InteractiveAnswerResults struct {
|
||||
EsId string `json:"esId" md:"-"`
|
||||
IndexId string `json:"indexId" md:"-"`
|
||||
ContentType int `json:"contentType" md:"-"`
|
||||
Trade []string `json:"trade" md:"行业名称"`
|
||||
MainContent string `json:"mainContent" md:"投资者提问"`
|
||||
StockCode string `json:"stockCode" md:"股票代码"`
|
||||
Secid string `json:"secid" md:"-"`
|
||||
CompanyShortName string `json:"companyShortName" md:"股票名称"`
|
||||
CompanyLogo string `json:"companyLogo,omitempty" md:"-"`
|
||||
BoardType []string `json:"boardType" md:"-"`
|
||||
PubDate string `json:"pubDate" md:"发布时间"`
|
||||
UpdateDate string `json:"updateDate" md:"-"`
|
||||
Author string `json:"author" md:"-"`
|
||||
AuthorName string `json:"authorName" md:"-"`
|
||||
PubClient string `json:"pubClient" md:"-"`
|
||||
AttachedId string `json:"attachedId" md:"-"`
|
||||
AttachedContent string `json:"attachedContent" md:"上市公司回复"`
|
||||
AttachedAuthor string `json:"attachedAuthor" md:"-"`
|
||||
AttachedPubDate string `json:"attachedPubDate" md:"回复时间"`
|
||||
Score float64 `json:"score" md:"-"`
|
||||
TopStatus int `json:"topStatus" md:"-"`
|
||||
PraiseCount int `json:"praiseCount" md:"-"`
|
||||
PraiseStatus bool `json:"praiseStatus" md:"-"`
|
||||
FavoriteStatus bool `json:"favoriteStatus" md:"-"`
|
||||
AttentionCompany bool `json:"attentionCompany" md:"-"`
|
||||
IsCheck string `json:"isCheck" md:"-"`
|
||||
QaStatus int `json:"qaStatus" md:"-"`
|
||||
PackageDate string `json:"packageDate" md:"-"`
|
||||
RemindStatus bool `json:"remindStatus" md:"-"`
|
||||
InterviewLive bool `json:"interviewLive" md:"-"`
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
package util
|
||||
|
||||
// @Author spark
|
||||
// @Date 2025/7/15 14:08
|
||||
// @Desc
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"golang.org/x/net/html"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HTMLNode 表示HTML文档中的一个节点
|
||||
type HTMLNode struct {
|
||||
Type html.NodeType
|
||||
Data string
|
||||
Attr []html.Attribute
|
||||
Children []*HTMLNode
|
||||
}
|
||||
|
||||
// HTMLToMarkdown 将HTML转换为Markdown
|
||||
func HTMLToMarkdown(htmlContent string) (string, error) {
|
||||
doc, err := html.Parse(strings.NewReader(htmlContent))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
root := parseHTMLNode(doc)
|
||||
var buf bytes.Buffer
|
||||
convertNode(&buf, root, 0)
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// parseHTMLNode 递归解析HTML节点
|
||||
func parseHTMLNode(n *html.Node) *HTMLNode {
|
||||
node := &HTMLNode{
|
||||
Type: n.Type,
|
||||
Data: n.Data,
|
||||
Attr: n.Attr,
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
node.Children = append(node.Children, parseHTMLNode(c))
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
// convertNode 递归转换节点为Markdown
|
||||
func convertNode(buf *bytes.Buffer, node *HTMLNode, depth int) {
|
||||
switch node.Type {
|
||||
case html.ElementNode:
|
||||
convertElementNode(buf, node, depth)
|
||||
case html.TextNode:
|
||||
// 处理文本节点,去除多余的空白
|
||||
text := strings.TrimSpace(node.Data)
|
||||
if text != "" {
|
||||
buf.WriteString(text)
|
||||
}
|
||||
}
|
||||
|
||||
// 递归处理子节点
|
||||
for _, child := range node.Children {
|
||||
convertNode(buf, child, depth+1)
|
||||
}
|
||||
|
||||
// 处理需要在结束标签后添加内容的元素
|
||||
switch node.Data {
|
||||
case "p", "h1", "h2", "h3", "h4", "h5", "h6", "li":
|
||||
buf.WriteString("\n\n")
|
||||
case "blockquote":
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// convertElementNode 转换元素节点为Markdown
|
||||
func convertElementNode(buf *bytes.Buffer, node *HTMLNode, depth int) {
|
||||
switch node.Data {
|
||||
case "h1":
|
||||
buf.WriteString("# ")
|
||||
case "h2":
|
||||
buf.WriteString("## ")
|
||||
case "h3":
|
||||
buf.WriteString("### ")
|
||||
case "h4":
|
||||
buf.WriteString("#### ")
|
||||
case "h5":
|
||||
buf.WriteString("##### ")
|
||||
case "h6":
|
||||
buf.WriteString("###### ")
|
||||
case "p":
|
||||
// 段落标签不需要特殊标记,直接处理内容
|
||||
case "strong", "b":
|
||||
buf.WriteString("**")
|
||||
case "em", "i":
|
||||
buf.WriteString("*")
|
||||
case "u":
|
||||
buf.WriteString("<u>")
|
||||
case "s", "del":
|
||||
buf.WriteString("~~")
|
||||
case "a":
|
||||
//href := getAttrValue(node.Attr, "href")
|
||||
buf.WriteString("[")
|
||||
case "img":
|
||||
src := getAttrValue(node.Attr, "src")
|
||||
alt := getAttrValue(node.Attr, "alt")
|
||||
buf.WriteString(fmt.Sprintf("", alt, src))
|
||||
case "ul":
|
||||
// 无序列表不需要特殊标记,子项会处理
|
||||
case "ol":
|
||||
// 有序列表不需要特殊标记,子项会处理
|
||||
case "li":
|
||||
if isParentListType(node, "ul") {
|
||||
buf.WriteString("- ")
|
||||
} else {
|
||||
// 计算当前列表项的序号
|
||||
index := 1
|
||||
if parent := findParentList(node); parent != nil {
|
||||
for i, sibling := range parent.Children {
|
||||
if sibling == node {
|
||||
index = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("%d. ", index))
|
||||
}
|
||||
case "blockquote":
|
||||
buf.WriteString("> ")
|
||||
case "code":
|
||||
if isParentPre(node) {
|
||||
// 父节点是pre,使用代码块
|
||||
buf.WriteString("\n```\n")
|
||||
} else {
|
||||
// 行内代码
|
||||
buf.WriteString("`")
|
||||
}
|
||||
case "pre":
|
||||
// 前置代码块由子节点code处理
|
||||
case "br":
|
||||
buf.WriteString("\n")
|
||||
case "hr":
|
||||
buf.WriteString("\n---\n")
|
||||
}
|
||||
|
||||
// 处理闭合标签
|
||||
if needsClosingTag(node.Data) {
|
||||
defer func() {
|
||||
switch node.Data {
|
||||
case "strong", "b":
|
||||
buf.WriteString("**")
|
||||
case "em", "i":
|
||||
buf.WriteString("*")
|
||||
case "u":
|
||||
buf.WriteString("</u>")
|
||||
case "s", "del":
|
||||
buf.WriteString("~~")
|
||||
case "a":
|
||||
href := getAttrValue(node.Attr, "href")
|
||||
buf.WriteString(fmt.Sprintf("](%s)", href))
|
||||
case "code":
|
||||
if isParentPre(node) {
|
||||
buf.WriteString("\n```\n")
|
||||
} else {
|
||||
buf.WriteString("`")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// getAttrValue 获取属性值
|
||||
func getAttrValue(attrs []html.Attribute, key string) string {
|
||||
for _, attr := range attrs {
|
||||
if attr.Key == key {
|
||||
return attr.Val
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// isParentListType 检查父节点是否为指定类型的列表
|
||||
func isParentListType(node *HTMLNode, listType string) bool {
|
||||
parent := findParentList(node)
|
||||
return parent != nil && parent.Data == listType
|
||||
}
|
||||
|
||||
// findParentList 查找父列表节点
|
||||
func findParentList(node *HTMLNode) *HTMLNode {
|
||||
// 简化实现,实际应该递归查找父节点
|
||||
if node.Type == html.ElementNode && (node.Data == "ul" || node.Data == "ol") {
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isParentPre 检查父节点是否为pre
|
||||
func isParentPre(node *HTMLNode) bool {
|
||||
if len(node.Children) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, child := range node.Children {
|
||||
if child.Type == html.ElementNode && child.Data == "pre" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// needsClosingTag 判断元素是否需要闭合标签
|
||||
func needsClosingTag(tag string) bool {
|
||||
switch tag {
|
||||
case "img", "br", "hr", "input", "meta", "link":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package util
|
||||
|
||||
// @Author spark
|
||||
// @Date 2025/7/15 14:08
|
||||
// @Desc
|
||||
//-----------------------------------------------------------------------------------
|
||||
@@ -1,286 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MarkdownTable 生成结构体或结构体切片的Markdown表格表示
|
||||
func MarkdownTable(v interface{}) string {
|
||||
value := reflect.ValueOf(v)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
// 处理单个结构体
|
||||
if value.Kind() == reflect.Struct {
|
||||
return markdownSingleStruct(value)
|
||||
}
|
||||
|
||||
// 处理结构体切片/数组
|
||||
if value.Kind() == reflect.Slice || value.Kind() == reflect.Array {
|
||||
if value.Len() == 0 {
|
||||
return "切片/数组为空"
|
||||
}
|
||||
return markdownStructSlice(value)
|
||||
}
|
||||
|
||||
return "输入必须是结构体、结构体指针、结构体切片或数组"
|
||||
}
|
||||
|
||||
func MarkdownTableWithTitle(title string, v interface{}) string {
|
||||
value := reflect.ValueOf(v)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
// 处理单个结构体
|
||||
if value.Kind() == reflect.Struct {
|
||||
return markdownSingleStruct(value)
|
||||
}
|
||||
|
||||
// 处理结构体切片/数组
|
||||
if value.Kind() == reflect.Slice || value.Kind() == reflect.Array {
|
||||
if value.Len() == 0 {
|
||||
return "\n## " + title + "\n" + "无数据" + "\n"
|
||||
}
|
||||
return "\n## " + title + "\n" + markdownStructSlice(value) + "\n"
|
||||
}
|
||||
|
||||
return "\n## " + title + "\n" + "无数据" + "\n"
|
||||
}
|
||||
|
||||
// 处理单个结构体
|
||||
func markdownSingleStruct(value reflect.Value) string {
|
||||
t := value.Type()
|
||||
var b strings.Builder
|
||||
|
||||
// 表头
|
||||
b.WriteString("|")
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if shouldSkip(field) {
|
||||
continue
|
||||
}
|
||||
b.WriteString(fmt.Sprintf(" %s |", getFieldName(field)))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
// 分隔线
|
||||
b.WriteString("|")
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if shouldSkip(field) {
|
||||
continue
|
||||
}
|
||||
b.WriteString(" --- |")
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
// 数据行
|
||||
b.WriteString("|")
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if shouldSkip(field) {
|
||||
continue
|
||||
}
|
||||
fieldValue := value.Field(i)
|
||||
b.WriteString(fmt.Sprintf(" %s |", formatValue(fieldValue)))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// 处理结构体切片/数组
|
||||
func markdownStructSlice(value reflect.Value) string {
|
||||
if value.Len() == 0 {
|
||||
return "切片/数组为空"
|
||||
}
|
||||
|
||||
firstElem := value.Index(0)
|
||||
if firstElem.Kind() == reflect.Ptr {
|
||||
firstElem = firstElem.Elem()
|
||||
}
|
||||
if firstElem.Kind() != reflect.Struct {
|
||||
return "切片/数组元素必须是结构体或结构体指针"
|
||||
}
|
||||
|
||||
t := firstElem.Type()
|
||||
var b strings.Builder
|
||||
|
||||
// 表头
|
||||
b.WriteString("|")
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if shouldSkip(field) {
|
||||
continue
|
||||
}
|
||||
b.WriteString(fmt.Sprintf(" %s |", getFieldName(field)))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
// 分隔线
|
||||
b.WriteString("|")
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if shouldSkip(field) {
|
||||
continue
|
||||
}
|
||||
b.WriteString(" --- |")
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
// 多行数据
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
elem := value.Index(i)
|
||||
if elem.Kind() == reflect.Ptr {
|
||||
elem = elem.Elem()
|
||||
}
|
||||
|
||||
b.WriteString("|")
|
||||
for j := 0; j < t.NumField(); j++ {
|
||||
field := t.Field(j)
|
||||
if shouldSkip(field) {
|
||||
continue
|
||||
}
|
||||
fieldValue := elem.Field(j)
|
||||
b.WriteString(fmt.Sprintf(" %s |", formatValue(fieldValue)))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// 判断是否应该跳过该字段
|
||||
func shouldSkip(field reflect.StructField) bool {
|
||||
return field.Tag.Get("md") == "-"
|
||||
}
|
||||
|
||||
// 获取字段的Markdown表头名称
|
||||
func getFieldName(field reflect.StructField) string {
|
||||
name := field.Tag.Get("md")
|
||||
if name == "" || name == "-" {
|
||||
return field.Name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// 格式化字段值为字符串
|
||||
func formatValue(value reflect.Value) string {
|
||||
if !value.IsValid() {
|
||||
return "n/a"
|
||||
}
|
||||
|
||||
// 处理指针
|
||||
if value.Kind() == reflect.Ptr {
|
||||
if value.IsNil() {
|
||||
return "nil"
|
||||
}
|
||||
return formatValue(value.Elem())
|
||||
}
|
||||
|
||||
// 处理结构体
|
||||
if value.Kind() == reflect.Struct {
|
||||
var fields []string
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
field := value.Type().Field(i)
|
||||
if shouldSkip(field) {
|
||||
continue
|
||||
}
|
||||
fieldValue := value.Field(i)
|
||||
fields = append(fields, fmt.Sprintf("%s: %s", getFieldName(field), formatValue(fieldValue)))
|
||||
}
|
||||
return "{" + strings.Join(fields, ", ") + "}"
|
||||
}
|
||||
|
||||
// 处理切片/数组
|
||||
if value.Kind() == reflect.Slice || value.Kind() == reflect.Array {
|
||||
var items []string
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
items = append(items, formatValue(value.Index(i)))
|
||||
}
|
||||
return "[" + strings.Join(items, ", ") + "]"
|
||||
}
|
||||
|
||||
// 处理映射
|
||||
if value.Kind() == reflect.Map {
|
||||
var items []string
|
||||
for _, key := range value.MapKeys() {
|
||||
keyStr := formatValue(key)
|
||||
valueStr := formatValue(value.MapIndex(key))
|
||||
items = append(items, fmt.Sprintf("%s: %s", keyStr, valueStr))
|
||||
}
|
||||
return "{" + strings.Join(items, ", ") + "}"
|
||||
}
|
||||
|
||||
// 基本类型
|
||||
return fmt.Sprintf("%s", strutil.RemoveNonPrintable(convertor.ToString(value.Interface())))
|
||||
}
|
||||
|
||||
// 示例结构体
|
||||
type Address struct {
|
||||
City string `md:"城市"`
|
||||
Country string `md:"国家"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `md:"姓名"`
|
||||
Age int `md:"年龄"`
|
||||
Email string `md:"邮箱"`
|
||||
Address Address `md:"地址"`
|
||||
Phones []string `md:"电话"`
|
||||
Active bool `md:"活跃状态"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 示例使用:单个结构体
|
||||
user := User{
|
||||
Name: "张三",
|
||||
Age: 30,
|
||||
Email: "zhangsan@example.com",
|
||||
Address: Address{
|
||||
City: "北京",
|
||||
Country: "中国",
|
||||
},
|
||||
Phones: []string{"13800138000", "13900139000"},
|
||||
Active: true,
|
||||
}
|
||||
|
||||
fmt.Println("单个结构体转换:")
|
||||
fmt.Println(MarkdownTable(user))
|
||||
fmt.Println()
|
||||
|
||||
// 示例使用:结构体切片
|
||||
users := []User{
|
||||
{
|
||||
Name: "张三",
|
||||
Age: 30,
|
||||
Email: "zhangsan@example.com",
|
||||
Address: Address{
|
||||
City: "北京",
|
||||
Country: "中国",
|
||||
},
|
||||
Phones: []string{"13800138000"},
|
||||
Active: true,
|
||||
},
|
||||
{
|
||||
Name: "李四",
|
||||
Age: 25,
|
||||
Email: "lisi@example.com",
|
||||
Address: Address{
|
||||
City: "上海",
|
||||
Country: "中国",
|
||||
},
|
||||
Phones: []string{"13900139000", "13700137000"},
|
||||
Active: false,
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Println("结构体切片转换:")
|
||||
fmt.Println(MarkdownTable(users))
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMd(t *testing.T) {
|
||||
// 示例使用:单个结构体
|
||||
user := User{
|
||||
Name: "张三",
|
||||
Age: 30,
|
||||
Email: "zhangsan@example.com",
|
||||
Address: Address{
|
||||
City: "北京",
|
||||
Country: "中国",
|
||||
},
|
||||
Phones: []string{"13800138000", "13900139000"},
|
||||
Active: true,
|
||||
}
|
||||
|
||||
fmt.Println("单个结构体转换:")
|
||||
fmt.Println(MarkdownTable(user))
|
||||
fmt.Println()
|
||||
|
||||
// 示例使用:结构体切片
|
||||
users := []User{
|
||||
{
|
||||
Name: "张三",
|
||||
Age: 30,
|
||||
Email: "zhangsan@example.com",
|
||||
Address: Address{
|
||||
City: "北京",
|
||||
Country: "中国",
|
||||
},
|
||||
Phones: []string{"13800138000"},
|
||||
Active: true,
|
||||
},
|
||||
{
|
||||
Name: "李四",
|
||||
Age: 25,
|
||||
Email: "lisi@example.com",
|
||||
Address: Address{
|
||||
City: "上海",
|
||||
Country: "中国",
|
||||
},
|
||||
Phones: []string{"13900139000", "13700137000"},
|
||||
Active: false,
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Println("结构体切片转换:")
|
||||
fmt.Println(MarkdownTable(users))
|
||||
}
|
||||
10
frontend/auto-imports.d.ts
vendored
10
frontend/auto-imports.d.ts
vendored
@@ -1,10 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
||||
45
frontend/components.d.ts
vendored
45
frontend/components.d.ts
vendored
@@ -1,45 +0,0 @@
|
||||
/* 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']
|
||||
AnalyzeMartket: typeof import('./src/components/AnalyzeMartket.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']
|
||||
}
|
||||
}
|
||||
1170
frontend/package-lock.json
generated
1170
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,6 @@
|
||||
"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",
|
||||
@@ -19,13 +18,11 @@
|
||||
"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": "^3.2.25",
|
||||
"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",
|
||||
@@ -36,10 +33,7 @@
|
||||
"@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"
|
||||
},
|
||||
|
||||
@@ -1 +1 @@
|
||||
b0b9f944d9af9c00b6d48234793db58c
|
||||
2d63c3a999d797889c01d6c96451b197
|
||||
@@ -6,12 +6,11 @@ import {
|
||||
Quit,
|
||||
WindowFullscreen,
|
||||
WindowHide,
|
||||
WindowUnfullscreen,
|
||||
WindowSetTitle
|
||||
WindowUnfullscreen
|
||||
} from '../wailsjs/runtime'
|
||||
import {h, onBeforeMount, onBeforeUnmount, onMounted, ref} from "vue";
|
||||
import {RouterLink, useRouter} from 'vue-router'
|
||||
import {createDiscreteApi,darkTheme,lightTheme , NIcon, NText,NButton,dateZhCN,zhCN} from 'naive-ui'
|
||||
import {createDiscreteApi,darkTheme,lightTheme , NIcon, NText,dateZhCN,zhCN} from 'naive-ui'
|
||||
import {
|
||||
AlarmOutline,
|
||||
AnalyticsOutline,
|
||||
@@ -28,8 +27,8 @@ import {
|
||||
StarOutline,
|
||||
Wallet, WarningOutline,
|
||||
} from '@vicons/ionicons5'
|
||||
import {AnalyzeSentiment, GetConfig, GetGroupList,GetVersionInfo} from "../wailsjs/go/main/App";
|
||||
import {Dragon, Fire, FirefoxBrowser, Gripfire, Robot} from "@vicons/fa";
|
||||
import {AnalyzeSentiment, GetConfig, GetGroupList} from "../wailsjs/go/main/App";
|
||||
import {Dragon, Fire, Gripfire} from "@vicons/fa";
|
||||
import {ReportSearch} from "@vicons/tabler";
|
||||
import {LocalFireDepartmentRound} from "@vicons/material";
|
||||
import {BoxSearch20Regular, CommentNote20Filled} from "@vicons/fluent";
|
||||
@@ -44,11 +43,10 @@ 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 content = ref('数据来源于网络,仅供参考;投资有风险,入市需谨慎\n\n未经授权,禁止商业目的!')
|
||||
const isFullscreen = ref(false)
|
||||
const activeKey = ref('stock')
|
||||
const activeKey = ref('')
|
||||
const containerRef = ref({})
|
||||
const realtimeProfit = ref(0)
|
||||
const telegraph = ref([])
|
||||
@@ -66,10 +64,7 @@ const menuOptions = ref([
|
||||
groupId: 0,
|
||||
},
|
||||
params: {},
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'stock'
|
||||
},
|
||||
}
|
||||
},
|
||||
{default: () => '股票自选',}
|
||||
),
|
||||
@@ -84,7 +79,6 @@ const menuOptions = ref([
|
||||
href: '#',
|
||||
type: 'info',
|
||||
onClick: () => {
|
||||
activeKey.value = 'stock'
|
||||
//console.log("push",item)
|
||||
router.push({
|
||||
name: 'stock',
|
||||
@@ -120,7 +114,6 @@ const menuOptions = ref([
|
||||
params: {}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '市场快讯'})
|
||||
},
|
||||
},
|
||||
@@ -142,7 +135,6 @@ const menuOptions = ref([
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '市场快讯'})
|
||||
},
|
||||
},
|
||||
@@ -164,7 +156,6 @@ const menuOptions = ref([
|
||||
},
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '全球股指'})
|
||||
},
|
||||
},
|
||||
@@ -182,15 +173,14 @@ const menuOptions = ref([
|
||||
to: {
|
||||
name: 'market',
|
||||
query: {
|
||||
name: "重大指数",
|
||||
name: "指标行情",
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '重大指数'})
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '指标行情'})
|
||||
},
|
||||
},
|
||||
{default: () => '重大指数',}
|
||||
{default: () => '指标行情',}
|
||||
),
|
||||
key: 'market3',
|
||||
icon: renderIcon(AnalyticsOutline),
|
||||
@@ -208,7 +198,6 @@ const menuOptions = ref([
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '行业排名'})
|
||||
},
|
||||
},
|
||||
@@ -230,7 +219,6 @@ const menuOptions = ref([
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '个股资金流向'})
|
||||
},
|
||||
},
|
||||
@@ -252,7 +240,6 @@ const menuOptions = ref([
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '龙虎榜'})
|
||||
},
|
||||
},
|
||||
@@ -274,7 +261,6 @@ const menuOptions = ref([
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '个股研报'})
|
||||
},
|
||||
},
|
||||
@@ -296,7 +282,6 @@ const menuOptions = ref([
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '公司公告'})
|
||||
},
|
||||
},
|
||||
@@ -318,7 +303,6 @@ const menuOptions = ref([
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '行业研究'})
|
||||
},
|
||||
},
|
||||
@@ -340,7 +324,6 @@ const menuOptions = ref([
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '当前热门'})
|
||||
},
|
||||
},
|
||||
@@ -362,7 +345,6 @@ const menuOptions = ref([
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'market'
|
||||
EventsEmit("changeMarketTab", {ID: 0, name: '指标选股'})
|
||||
},
|
||||
},
|
||||
@@ -371,28 +353,6 @@ 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),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -402,13 +362,8 @@ const menuOptions = ref([
|
||||
{
|
||||
to: {
|
||||
name: 'fund',
|
||||
query: {
|
||||
name: '基金自选',
|
||||
},
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'fund'
|
||||
},
|
||||
params: {},
|
||||
}
|
||||
},
|
||||
{default: () => '基金自选',}
|
||||
),
|
||||
@@ -424,27 +379,6 @@ 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(
|
||||
@@ -452,12 +386,7 @@ const menuOptions = ref([
|
||||
{
|
||||
to: {
|
||||
name: 'settings',
|
||||
query: {
|
||||
name:"设置",
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'settings'
|
||||
},
|
||||
params: {}
|
||||
}
|
||||
},
|
||||
{default: () => '设置'}
|
||||
@@ -472,13 +401,8 @@ const menuOptions = ref([
|
||||
{
|
||||
to: {
|
||||
name: 'about',
|
||||
query: {
|
||||
name:"关于",
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
activeKey.value = 'about'
|
||||
},
|
||||
params: {}
|
||||
}
|
||||
},
|
||||
{default: () => '关于'}
|
||||
),
|
||||
@@ -486,7 +410,6 @@ const menuOptions = ref([
|
||||
icon: renderIcon(LogoGithub),
|
||||
},
|
||||
{
|
||||
show:false,
|
||||
label: () => h("a", {
|
||||
href: '#',
|
||||
onClick: toggleFullscreen,
|
||||
@@ -499,7 +422,7 @@ const menuOptions = ref([
|
||||
label: () => h("a", {
|
||||
href: '#',
|
||||
onClick: WindowHide,
|
||||
title: '隐藏到托盘区 Ctrl+Z',
|
||||
title: '隐藏到托盘区 Ctrl+H',
|
||||
}, {default: () => '隐藏到托盘区'}),
|
||||
key: 'hide',
|
||||
icon: renderIcon(ReorderTwoOutline),
|
||||
@@ -528,7 +451,6 @@ function renderIcon(icon) {
|
||||
}
|
||||
|
||||
function toggleFullscreen(e) {
|
||||
activeKey.value = 'full'
|
||||
//console.log(e)
|
||||
if (isFullscreen.value) {
|
||||
WindowUnfullscreen()
|
||||
@@ -596,12 +518,6 @@ window.onerror = function (msg, source, lineno, colno, error) {
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
GetVersionInfo().then(result => {
|
||||
if(result.officialStatement){
|
||||
content.value = result.officialStatement+"\n\n"+content.value
|
||||
}
|
||||
})
|
||||
|
||||
GetGroupList().then(result => {
|
||||
groupList.value = result
|
||||
menuOptions.value.map((item) => {
|
||||
@@ -649,15 +565,11 @@ 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) {
|
||||
@@ -669,14 +581,12 @@ onBeforeMount(() => {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
WindowSetTitle("go-stock:AI赋能股票分析✨ 未经授权,禁止商业目的! [数据来源于网络,仅供参考;投资有风险,入市需谨慎]")
|
||||
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 ,
|
||||
@@ -690,24 +600,16 @@ onMounted(() => {
|
||||
//type:"error",
|
||||
// avatar: () => h(NIcon,{component:Notifications,color:"red"}),
|
||||
title: data.time,
|
||||
content: () => h('div',{type:"error",style:{
|
||||
"text-align":"left",
|
||||
"font-size":"14px",
|
||||
"color":"#f67979"
|
||||
}}, { default: () => data.content }),
|
||||
content: () => h(NText,{type:"error"}, { default: () => data.content }),
|
||||
meta: () => h(NText,{type:"warning"}, { default: () => data.source}),
|
||||
duration:1000*40,
|
||||
})
|
||||
}else{
|
||||
notification.create({
|
||||
notification.create({
|
||||
//type:"info",
|
||||
//avatar: () => h(NIcon,{component:Notifications}),
|
||||
title: data.time,
|
||||
content: () => h('div',{type:"info",style:{
|
||||
"text-align":"left",
|
||||
"font-size":"14px",
|
||||
"color": data.source==="go-stock"?"#F98C24":"#549EC8"
|
||||
}}, { default: () => data.content }),
|
||||
content: () => h(NText,{type:"info"}, { default: () => data.content }),
|
||||
meta: () => h(NText,{type:"warning"}, { default: () => data.source}),
|
||||
duration:1000*30 ,
|
||||
})
|
||||
@@ -723,7 +625,7 @@ onMounted(() => {
|
||||
<n-modal-provider>
|
||||
<n-dialog-provider>
|
||||
<n-watermark
|
||||
:content="''"
|
||||
:content="content"
|
||||
cross
|
||||
selectable
|
||||
:font-size="16"
|
||||
@@ -754,7 +656,7 @@ onMounted(() => {
|
||||
</n-spin>
|
||||
</n-gi>
|
||||
<n-gi style="position: fixed;bottom:0;z-index: 9;width: 100%;">
|
||||
<n-card size="small" style="--wails-draggable:no-drag">
|
||||
<n-card size="small" style="--wails-draggable:drag">
|
||||
<n-menu style="font-size: 18px;"
|
||||
v-model:value="activeKey"
|
||||
mode="horizontal"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,133 +0,0 @@
|
||||
<template>
|
||||
<div class="embed-container">
|
||||
<h3 v-if="title">{{ title }}</h3>
|
||||
<div class="iframe-wrapper">
|
||||
<iframe
|
||||
:src="url"
|
||||
:title="iframeTitle"
|
||||
frameborder="0"
|
||||
scrolling="auto"
|
||||
class="embedded-iframe"
|
||||
@load="onLoad"
|
||||
@error="onError"
|
||||
:style="iframeStyle"
|
||||
></iframe>
|
||||
</div>
|
||||
<div v-if="loading" class="loading-indicator">
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
<p v-if="error" class="error-message">{{ error }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
iframeTitle: {
|
||||
type: String,
|
||||
default: '外部内容'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(true)
|
||||
const error = ref(null)
|
||||
|
||||
const onLoad = () => {
|
||||
loading.value = false
|
||||
error.value = null
|
||||
}
|
||||
|
||||
const onError = (event) => {
|
||||
loading.value = false
|
||||
error.value = `加载失败: ${event.message || '无法加载该 URL'}`
|
||||
}
|
||||
|
||||
// 监听 URL 变化,重新加载
|
||||
watch(() => props.url, () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
})
|
||||
|
||||
// 设置 iframe 样式
|
||||
const iframeStyle = {
|
||||
width: props.width,
|
||||
height: props.height
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.embed-container {
|
||||
margin: 1rem 0;
|
||||
border: 0 solid #e5e7eb;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.iframe-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.embedded-iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.loading-indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border: 3px solid #f3f4f6;
|
||||
border-radius: 50%;
|
||||
border-top-color: #3b82f6;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ef4444;
|
||||
padding: 1rem;
|
||||
margin: 0;
|
||||
background-color: #fee2e2;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import {onBeforeMount, onUnmounted, ref} from 'vue'
|
||||
import {HotTopic, OpenURL} from "../../wailsjs/go/main/App";
|
||||
import {Environment} from "../../wailsjs/runtime";
|
||||
import {HotTopic} from "../../wailsjs/go/main/App";
|
||||
const list = ref([])
|
||||
const task =ref()
|
||||
|
||||
@@ -19,20 +18,11 @@ function openCenteredWindow(url, width, height) {
|
||||
const left = (window.screen.width - width) / 2;
|
||||
const top = (window.screen.height - height) / 2;
|
||||
|
||||
Environment().then(env => {
|
||||
switch (env.platform) {
|
||||
case 'windows':
|
||||
window.open(
|
||||
url,
|
||||
'centeredWindow',
|
||||
`width=${width},height=${height},left=${left},top=${top}`
|
||||
)
|
||||
break
|
||||
default:
|
||||
OpenURL(url)
|
||||
break
|
||||
}
|
||||
})
|
||||
return window.open(
|
||||
url,
|
||||
'centeredWindow',
|
||||
`width=${width},height=${height},left=${left},top=${top}`
|
||||
);
|
||||
}
|
||||
function showPage(htid) {
|
||||
openCenteredWindow(`https://gubatopic.eastmoney.com/topic_v3.html?htid=${htid}`, 1000, 600)
|
||||
|
||||
@@ -378,7 +378,7 @@ function calculateMA(dayCount,values) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="kLineChartRef" style="width: 100%;height: auto;--wails-draggable:no-drag" :style="{height:chartHeight+'px'}" ></div>
|
||||
<div ref="kLineChartRef" style="width: 100%;height: auto;" :style="{height:chartHeight+'px'}"></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,255 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import {h, onBeforeMount, onMounted, onUnmounted, ref} from 'vue'
|
||||
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";
|
||||
|
||||
import {SearchStock} from "../../wailsjs/go/main/App";
|
||||
import {useMessage, NText, NTag} from 'naive-ui'
|
||||
const message = useMessage()
|
||||
const search = ref('')
|
||||
const search = ref('科技股;换手率连续3日大于2')
|
||||
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) {
|
||||
message.warning('请输入选股指标或者要求')
|
||||
return
|
||||
}
|
||||
|
||||
const loading = message.loading("正在获取选股数据...", {duration: 0});
|
||||
SearchStock(search.value).then(res => {
|
||||
loading.destroy()
|
||||
// console.log(res)
|
||||
if (res.code == 100) {
|
||||
traceInfo.value = res.data.traceInfo.showText
|
||||
// message.success(res.msg)
|
||||
columns.value = res.data.result.columns.filter(item => !item.hiddenNeed && (item.title != "市场码" && item.title != "市场简称")).map(item => {
|
||||
if (item.children) {
|
||||
//console.log(res)
|
||||
if(res.code==100){
|
||||
message.success(res.msg)
|
||||
columns.value=res.data.result.columns.filter(item=>!item.hiddenNeed&&(item.title!="市场码"&&item.title!="市场简称")).map(item=>{
|
||||
|
||||
if(item.children){
|
||||
return {
|
||||
title: item.title + (item.unit ? '[' + item.unit + ']' : ''),
|
||||
key: item.key,
|
||||
title:item.title+(item.unit?'['+item.unit+']':''),
|
||||
key:item.key,
|
||||
resizable: true,
|
||||
minWidth: 200,
|
||||
minWidth:200,
|
||||
ellipsis: {
|
||||
tooltip: true
|
||||
},
|
||||
children: item.children.filter(item => !item.hiddenNeed).map(item => {
|
||||
children:item.children.filter(item=>!item.hiddenNeed).map(item=>{
|
||||
return {
|
||||
title: item.dateMsg,
|
||||
key: item.key,
|
||||
minWidth: 100,
|
||||
title:item.dateMsg,
|
||||
key:item.key,
|
||||
minWidth:100,
|
||||
resizable: true,
|
||||
ellipsis: {
|
||||
tooltip: true
|
||||
},
|
||||
sorter: (row1, row2) => {
|
||||
if (isNumeric(row1[item.key]) && isNumeric(row2[item.key])) {
|
||||
return row1[item.key] - row2[item.key];
|
||||
} else {
|
||||
return 'default'
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
}else{
|
||||
return {
|
||||
title: item.title + (item.unit ? '[' + item.unit + ']' : ''),
|
||||
key: item.key,
|
||||
title:item.title+(item.unit?'['+item.unit+']':''),
|
||||
key:item.key,
|
||||
resizable: true,
|
||||
minWidth: 120,
|
||||
minWidth:100,
|
||||
ellipsis: {
|
||||
tooltip: true
|
||||
},
|
||||
sorter: (row1, row2) => {
|
||||
if (isNumeric(row1[item.key]) && isNumeric(row2[item.key])) {
|
||||
return row1[item.key] - row2[item.key];
|
||||
} else {
|
||||
return 'default'
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
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)
|
||||
}
|
||||
dataList.value=res.data.result.dataList
|
||||
}else {
|
||||
message.error(res.msg)
|
||||
}
|
||||
}).catch(err => {
|
||||
message.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
// 修改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);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
GetHotStrategy().then(res => {
|
||||
console.log(res)
|
||||
if (res.code == 1) {
|
||||
hotStrategy.value = res.data
|
||||
search.value = hotStrategy.value[0].question
|
||||
Search()
|
||||
}
|
||||
}).catch(err => {
|
||||
message.error(err)
|
||||
})
|
||||
|
||||
Search()
|
||||
})
|
||||
|
||||
function DoSearch(question) {
|
||||
search.value = question
|
||||
Search()
|
||||
}
|
||||
|
||||
function openCenteredWindow(url, width, height) {
|
||||
const left = (window.screen.width - width) / 2;
|
||||
const top = (window.screen.height - height) / 2;
|
||||
|
||||
Environment().then(env => {
|
||||
switch (env.platform) {
|
||||
case 'windows':
|
||||
window.open(
|
||||
url,
|
||||
'centeredWindow',
|
||||
`width=${width},height=${height},left=${left},top=${top},location=no,menubar=no,toolbar=no,display=standalone`
|
||||
)
|
||||
break
|
||||
default:
|
||||
OpenURL(url)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-grid :cols="24" style="max-height: calc(100vh - 165px)">
|
||||
<n-gi :span="4">
|
||||
<n-list bordered style="text-align: left;" hoverable clickable>
|
||||
<n-scrollbar style="max-height: calc(100vh - 170px);">
|
||||
<n-list-item v-for="item in hotStrategy" :key="item.rank" @click="DoSearch(item.question)">
|
||||
<n-ellipsis line-clamp="1" :tooltip="true">
|
||||
<n-tag size="small" :bordered="false" type="info">#{{ item.rank }}</n-tag>
|
||||
<n-text type="warning">{{ item.question }}</n-text>
|
||||
<template #tooltip>
|
||||
<div style="text-align: center;max-width: 180px">
|
||||
<n-text type="warning">{{ item.question }}</n-text>
|
||||
</div>
|
||||
</template>
|
||||
</n-ellipsis>
|
||||
</n-list-item>
|
||||
</n-scrollbar>
|
||||
</n-list>
|
||||
|
||||
<!-- <n-virtual-list :items="hotStrategy" :item-size="hotStrategy.length">-->
|
||||
<!-- <template #default="{ item, index }">-->
|
||||
<!-- <n-card :title="''" size="small">-->
|
||||
<!-- <template #header-extra>-->
|
||||
<!-- {{item.rank}}-->
|
||||
<!-- </template>-->
|
||||
<!-- <n-ellipsis expand-trigger="click" line-clamp="3" :tooltip="false" >-->
|
||||
<!-- <n-text type="warning">{{item.question }}</n-text>-->
|
||||
<!-- </n-ellipsis>-->
|
||||
<!-- </n-card>-->
|
||||
|
||||
<!-- </template>-->
|
||||
<!-- </n-virtual-list>-->
|
||||
</n-gi>
|
||||
<n-gi :span="20">
|
||||
<n-flex style="--wails-draggable:no-drag">
|
||||
<n-input-group style="text-align: left">
|
||||
<n-input :rows="1" clearable v-model:value="search" placeholder="请输入选股指标或者要求"/>
|
||||
<n-button type="primary" @click="Search">搜索A股</n-button>
|
||||
</n-input-group>
|
||||
</n-flex>
|
||||
<n-flex justify="start" v-if="traceInfo" style="margin: 5px 0;--wails-draggable:no-drag">
|
||||
|
||||
<n-ellipsis line-clamp="1" :tooltip="true">
|
||||
<n-text type="info" :bordered="false">选股条件:</n-text>
|
||||
<n-text type="warning" :bordered="true">{{ traceInfo }}</n-text>
|
||||
<template #tooltip>
|
||||
<div style="text-align: center;max-width: 580px">
|
||||
<n-text type="warning">{{ traceInfo }}</n-text>
|
||||
</div>
|
||||
</template>
|
||||
</n-ellipsis>
|
||||
|
||||
<!-- <n-button type="primary" size="small">保存策略</n-button>-->
|
||||
</n-flex>
|
||||
<n-data-table
|
||||
:striped="true"
|
||||
:max-height="'calc(100vh - 150px)'"
|
||||
size="medium"
|
||||
:columns="columns"
|
||||
:data="dataList"
|
||||
:pagination="{pageSize: 10}"
|
||||
:scroll-x="tableScrollX"
|
||||
:render-cell="(value, rowData, column) => {
|
||||
<n-flex>
|
||||
<n-input-group>
|
||||
<n-input v-model:value="search" placeholder="请输入选股指标或者要求" />
|
||||
<n-button type="success" @click="Search">搜索A股</n-button>
|
||||
</n-input-group>
|
||||
</n-flex>
|
||||
<!-- <n-table striped size="small">-->
|
||||
<!-- <n-thead>-->
|
||||
<!-- <n-tr>-->
|
||||
<!-- <n-th v-for="item in columns">{{item.title}}</n-th>-->
|
||||
<!-- </n-tr>-->
|
||||
<!-- </n-thead>-->
|
||||
<!-- <n-tbody>-->
|
||||
<!-- <n-tr v-for="(item,index) in dataList">-->
|
||||
<!-- <n-td v-for="d in columns">{{item[d.key]}}</n-td>-->
|
||||
<!-- </n-tr>-->
|
||||
<!-- </n-tbody>-->
|
||||
<!-- </n-table>-->
|
||||
<n-data-table
|
||||
:max-height="'calc(100vh - 285px)'"
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:data="dataList"
|
||||
:pagination="false"
|
||||
:scroll-x="1800"
|
||||
:render-cell="(value, rowData, column) => {
|
||||
|
||||
if(column.key=='SECURITY_CODE'||column.key=='SERIAL'){
|
||||
return h(NText, { type: 'info',border: false }, { default: () => `${value}` })
|
||||
@@ -268,24 +112,13 @@ function openCenteredWindow(url, width, height) {
|
||||
return h(NText, { type: type }, { default: () => `${value}` })
|
||||
}else{
|
||||
if(column.key=='SECURITY_SHORT_NAME'){
|
||||
return h(NButton, { type: 'info',bordered: false ,size:'small',onClick:()=>{
|
||||
//https://quote.eastmoney.com/sz300558.html#fullScreenChart
|
||||
openCenteredWindow(`https://quote.eastmoney.com/${rowData.MARKET_SHORT_NAME}${rowData.SECURITY_CODE}.html#fullScreenChart`,1240,700)
|
||||
}}, { default: () => `${value}` })
|
||||
return h(NTag, { type: 'info',bordered: false }, { default: () => `${value}` })
|
||||
}else{
|
||||
return h(NText, { type: 'info' }, { default: () => `${value}` })
|
||||
}
|
||||
}
|
||||
}"
|
||||
/>
|
||||
<div style="margin-top: -25px">共找到
|
||||
<n-tag type="info" :bordered="false">{{ dataList.length }}</n-tag>
|
||||
只股
|
||||
</div>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -3,22 +3,15 @@
|
||||
// preview.css相比style.css少了编辑器那部分样式
|
||||
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,NText} from "naive-ui";
|
||||
import { addMonths, format ,parse} from 'date-fns';
|
||||
import { zhCN } from 'date-fns/locale';
|
||||
import {CheckUpdate, GetVersionInfo} from "../../wailsjs/go/main/App";
|
||||
import {EventsOff, EventsOn} from "../../wailsjs/runtime";
|
||||
import {NAvatar, NButton, useNotification} from "naive-ui";
|
||||
const updateLog = ref('');
|
||||
const versionInfo = ref('');
|
||||
const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png');
|
||||
const alipay =ref('https://github.com/ArvinLovegood/go-stock/raw/master/build/screenshot/alipay.jpg')
|
||||
const wxpay =ref('https://github.com/ArvinLovegood/go-stock/raw/master/build/screenshot/wxpay.jpg')
|
||||
const wxgzh =ref('https://github.com/ArvinLovegood/go-stock/raw/dev/build/screenshot/%E6%89%AB%E7%A0%81_%E6%90%9C%E7%B4%A2%E8%81%94%E5%90%88%E4%BC%A0%E6%92%AD%E6%A0%B7%E5%BC%8F-%E7%99%BD%E8%89%B2%E7%89%88.png')
|
||||
const notify = useNotification()
|
||||
const vipLevel=ref("");
|
||||
const vipStartTime=ref("");
|
||||
const vipEndTime=ref("");
|
||||
const expired=ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
document.title = '关于软件';
|
||||
@@ -28,25 +21,7 @@ onMounted(() => {
|
||||
icon.value = res.icon;
|
||||
alipay.value=res.alipay;
|
||||
wxpay.value=res.wxpay;
|
||||
wxgzh.value=res.wxgzh;
|
||||
|
||||
GetSponsorInfo().then((res) => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
notify.destroyAll()
|
||||
@@ -95,16 +70,7 @@ EventsOn("updateVersion",async (msg) => {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
Environment().then(env => {
|
||||
switch (env.platform) {
|
||||
case 'windows':
|
||||
window.open(msg.html_url)
|
||||
break
|
||||
default :
|
||||
OpenURL(msg.html_url)
|
||||
break
|
||||
}
|
||||
})
|
||||
window.open(msg.html_url)
|
||||
}
|
||||
}, { default: () => '查看' })
|
||||
}
|
||||
@@ -114,22 +80,21 @@ EventsOn("updateVersion",async (msg) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-space vertical size="large" style="--wails-draggable:no-drag">
|
||||
<n-space vertical size="large">
|
||||
<!-- 软件描述 -->
|
||||
<n-card size="large">
|
||||
<n-divider title-placement="center">关于软件</n-divider>
|
||||
<n-space vertical >
|
||||
<n-image width="100" :src="icon" />
|
||||
<h1>
|
||||
<n-badge v-if="!vipLevel" :value="versionInfo" :offset="[50,10]" type="success">
|
||||
<n-badge :value="versionInfo" :offset="[50,10]" type="success">
|
||||
<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="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="expired?'error':'warning'" v-if="vipLevel" >vip到期时间:{{vipEndTime}}</n-gradient-text>
|
||||
<n-button size="tiny" @click="CheckUpdate(1)" type="info" tertiary >检查更新</n-button>
|
||||
<n-button size="tiny" @click="CheckUpdate" type="info" tertiary >检查更新</n-button>
|
||||
|
||||
|
||||
|
||||
<div style="justify-self: center;text-align: left" >
|
||||
<p>自选股行情实时监控,基于Wails和NaiveUI构建的AI赋能股票分析工具</p>
|
||||
<p>目前已支持A股,港股,美股,未来计划加入基金,ETF等支持</p>
|
||||
@@ -148,39 +113,14 @@ EventsOn("updateVersion",async (msg) => {
|
||||
<p>QQ交流群:<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0YQ8qD3exahsD4YLNhzQTWe5ssstWC89&authKey=usOMMRFtIQDC%2FYcatHYapcxQbJ7PwXPHK9OypTXWzNjAq%2FRVvQu9bj2lRgb%2BSZ3p&noverify=0&group_code=491605333" target="_blank">491605333</a></p>
|
||||
</div>
|
||||
</n-space>
|
||||
<n-divider title-placement="center">支持💕开源</n-divider>
|
||||
<n-flex justify="center">
|
||||
<n-table size="small" style="width: 820px">
|
||||
<n-thead>
|
||||
<n-tr>
|
||||
<n-th>赞助计划</n-th>
|
||||
<n-th>赞助等级</n-th>
|
||||
<n-th>权益说明</n-th>
|
||||
</n-tr>
|
||||
</n-thead>
|
||||
<n-tbody>
|
||||
<n-tr>
|
||||
<n-td>每月 0 RMB</n-td><n-td>vip0</n-td><n-td>🌟 全部功能,软件自动更新(从GitHub下载),自行解决github平台网络问题。</n-td>
|
||||
</n-tr>
|
||||
<n-tr>
|
||||
<n-td>赞助 18.8 RMB/月<br>赞助 120 RMB/年</n-td><n-td>vip1</n-td><n-td>💕 全部功能,软件自动更新(从CDN下载),更新快速便捷。AI配置指导,提示词参考等</n-td>
|
||||
</n-tr>
|
||||
<n-tr>
|
||||
<n-td>赞助 28.8 RMB/月<br>赞助 240 RMB/年</n-td><n-td>vip2</n-td><n-td>💕 vip1全部功能,赠送硅基流动AI分析服务💕</n-td>
|
||||
</n-tr>
|
||||
<n-tr>
|
||||
<n-td>每月赞助 X RMB</n-td><n-td>vipX</n-td><n-td>🧩 更多计划,视go-stock开源项目发展情况而定...(承接GitHub项目README广告推广💖)</n-td>
|
||||
</n-tr>
|
||||
</n-tbody>
|
||||
</n-table>
|
||||
</n-flex>
|
||||
<n-divider title-placement="center">关于作者</n-divider>
|
||||
<n-space vertical>
|
||||
<!-- <h1>关于作者</h1>-->
|
||||
<n-avatar width="100" src="https://avatars.githubusercontent.com/u/7401917?v=4" />
|
||||
<h2><a href="https://github.com/ArvinLovegood" target="_blank">@ArvinLovegood</a></h2>
|
||||
<p>一个热爱编程的小白,欢迎关注我的Github/微信公众号</p>
|
||||
<n-image width="300" :src="wxgzh" />
|
||||
<p>一个热爱编程的小白,欢迎关注我的Github</p>
|
||||
<n-image width="300" src="https://go-stock.sparkmemory.top/assets/%E6%89%AB%E7%A0%81_%E6%90%9C%E7%B4%A2%E8%81%94%E5%90%88%E4%BC%A0%E6%92%AD%E6%A0%B7%E5%BC%8F-%E7%99%BD%E8%89%B2%E7%89%88-DEJtWc_y.png" />
|
||||
|
||||
<p>开源不易,如果觉得好用,可以请作者喝杯咖啡。</p>
|
||||
<n-flex justify="center">
|
||||
<n-image width="200" :src="alipay" />
|
||||
@@ -195,7 +135,6 @@ EventsOn("updateVersion",async (msg) => {
|
||||
</p>
|
||||
<p>
|
||||
感谢以下开发者:
|
||||
<a href="https://github.com/GiCo001" target="_blank">@Gico</a><n-divider vertical />
|
||||
<a href="https://github.com/CodeNoobLH" target="_blank">浓睡不消残酒</a><n-divider vertical />
|
||||
<a href="https://github.com/gnim2600" target="_blank">@gnim2600</a><n-divider vertical />
|
||||
<a href="https://github.com/XXXiaohuayanGGG" target="_blank">@XXXiaohuayanGGG</a><n-divider vertical />
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
<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>
|
||||
@@ -1,338 +0,0 @@
|
||||
<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>
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
GetConfig,
|
||||
GetFollowedFund,
|
||||
GetfundList,
|
||||
GetVersionInfo, OpenURL,
|
||||
GetVersionInfo,
|
||||
UnFollowFund
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import vueDanmaku from 'vue3-danmaku'
|
||||
@@ -147,19 +147,8 @@ function formatterTitle(title){
|
||||
|
||||
function search(code,name){
|
||||
setTimeout(() => {
|
||||
//window.open("https://fund.eastmoney.com/"+code+".html","_blank","noreferrer,width=1000,top=100,left=100,status=no,toolbar=no,location=no,scrollbars=no")
|
||||
window.open("https://fund.eastmoney.com/"+code+".html","_blank","noreferrer,width=1000,top=100,left=100,status=no,toolbar=no,location=no,scrollbars=no")
|
||||
//window.open("https://finance.sina.com.cn/fund/quotes/"+code+"/bc.shtml","_blank","width=1000,height=800,top=100,left=100,toolbar=no,location=no")
|
||||
|
||||
Environment().then(env => {
|
||||
switch (env.platform) {
|
||||
case 'windows':
|
||||
window.open("https://fund.eastmoney.com/"+code+".html","_blank","noreferrer,width=1000,top=100,left=100,status=no,toolbar=no,location=no,scrollbars=no")
|
||||
break
|
||||
default :
|
||||
OpenURL("https://fund.eastmoney.com/"+code+".html")
|
||||
}
|
||||
})
|
||||
|
||||
}, 500)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup>
|
||||
import * as echarts from "echarts";
|
||||
import {computed, h, onBeforeMount, onBeforeUnmount, onMounted,onUnmounted, ref} from 'vue'
|
||||
import {computed, h, onBeforeMount, onBeforeUnmount, ref} from 'vue'
|
||||
import {
|
||||
GetAIResponseResult,
|
||||
GetConfig,
|
||||
@@ -12,8 +11,7 @@ import {
|
||||
SaveAIResponseResult,
|
||||
SaveAsMarkdown,
|
||||
ShareAnalysis,
|
||||
SummaryStockNews,
|
||||
GetAiConfigs, AnalyzeSentimentWithFreqWeight
|
||||
SummaryStockNews
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import {EventsOff, EventsOn} from "../../wailsjs/runtime";
|
||||
import NewsList from "./newsList.vue";
|
||||
@@ -34,7 +32,6 @@ import HotTopics from "./HotTopics.vue";
|
||||
import InvestCalendarTimeLine from "./InvestCalendarTimeLine.vue";
|
||||
import ClsCalendarTimeLine from "./ClsCalendarTimeLine.vue";
|
||||
import SelectStock from "./SelectStock.vue";
|
||||
import Stockhotmap from "./stockhotmap.vue";
|
||||
|
||||
const route = useRoute()
|
||||
const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png');
|
||||
@@ -62,10 +59,8 @@ const aiSummaryTime = ref("")
|
||||
const modelName = ref("")
|
||||
const chatId = ref("")
|
||||
const question = ref(``)
|
||||
const aiConfigId = ref(null)
|
||||
const sysPromptId = ref(null)
|
||||
const sysPromptId = ref(0)
|
||||
const loading = ref(true)
|
||||
const aiConfigs = ref([])
|
||||
const sysPromptOptions = ref([])
|
||||
const userPromptOptions = ref([])
|
||||
const promptTemplates = ref([])
|
||||
@@ -75,9 +70,6 @@ const nowTab = ref("市场快讯")
|
||||
const indexInterval = ref(null)
|
||||
const indexIndustryRank = ref(null)
|
||||
const stockCode= ref('')
|
||||
const enableTools= ref(true)
|
||||
const treemapRef = ref(null);
|
||||
let treemapchart =null;
|
||||
|
||||
function getIndex() {
|
||||
GlobalStockIndexes().then((res) => {
|
||||
@@ -90,6 +82,8 @@ function getIndex() {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
onBeforeMount(() => {
|
||||
nowTab.value = route.query.name
|
||||
stockCode.value = route.query.stockCode
|
||||
@@ -103,11 +97,6 @@ onBeforeMount(() => {
|
||||
userPromptOptions.value = promptTemplates.value.filter(item => item.type === '模型用户Prompt')
|
||||
})
|
||||
|
||||
GetAiConfigs().then(res=>{
|
||||
aiConfigs.value = res
|
||||
aiConfigId.value = res[0].ID
|
||||
})
|
||||
|
||||
GetTelegraphList("财联社电报").then((res) => {
|
||||
telegraphList.value = res
|
||||
})
|
||||
@@ -123,13 +112,7 @@ onBeforeMount(() => {
|
||||
indexIndustryRank.value = setInterval(() => {
|
||||
industryRank()
|
||||
}, 1000 * 10)
|
||||
|
||||
|
||||
})
|
||||
onMounted(() => {
|
||||
Analyze() // 页面显示
|
||||
})
|
||||
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
EventsOff("changeMarketTab")
|
||||
@@ -140,32 +123,22 @@ onBeforeUnmount(() => {
|
||||
clearInterval(indexIndustryRank.value)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
});
|
||||
EventsOn("changeMarketTab", async (msg) => {
|
||||
//message.info(msg.name)
|
||||
console.log(msg.name)
|
||||
updateTab(msg.name)
|
||||
})
|
||||
|
||||
EventsOn("newTelegraph", (data) => {
|
||||
if (data!=null) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
telegraphList.value.pop()
|
||||
}
|
||||
telegraphList.value.unshift(...data)
|
||||
Analyze() // 页面显示
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
telegraphList.value.pop()
|
||||
}
|
||||
telegraphList.value.unshift(...data)
|
||||
})
|
||||
EventsOn("newSinaNews", (data) => {
|
||||
if (data!=null) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
sinaNewsList.value.pop()
|
||||
}
|
||||
sinaNewsList.value.unshift(...data)
|
||||
Analyze() // 页面显示
|
||||
}
|
||||
})
|
||||
|
||||
//获取页面高度
|
||||
@@ -173,32 +146,6 @@ window.onresize = () => {
|
||||
panelHeight.value = window.innerHeight - 240
|
||||
}
|
||||
|
||||
function Analyze(){
|
||||
console.log("treemapchart:",treemapchart)
|
||||
console.log("treemapRef:",treemapRef.value)
|
||||
treemapchart = echarts.init(treemapRef.value);
|
||||
treemapchart.showLoading()
|
||||
AnalyzeSentimentWithFreqWeight("").then((res) => {
|
||||
|
||||
let option = {
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'treemap',
|
||||
data: res['frequencies'].slice(0, 20).map(item => ({
|
||||
name: item.Word,
|
||||
value: item.Frequency,
|
||||
}))
|
||||
}
|
||||
]
|
||||
};
|
||||
treemapchart.setOption(option);
|
||||
treemapchart.hideLoading()
|
||||
})
|
||||
}
|
||||
|
||||
function getAreaName(code) {
|
||||
switch (code) {
|
||||
case "america":
|
||||
@@ -239,14 +186,13 @@ function reAiSummary() {
|
||||
aiSummary.value = ""
|
||||
summaryModal.value = true
|
||||
loading.value = true
|
||||
SummaryStockNews(question.value,aiConfigId.value, sysPromptId.value,enableTools.value)
|
||||
SummaryStockNews(question.value, sysPromptId.value)
|
||||
}
|
||||
|
||||
function getAiSummary() {
|
||||
summaryModal.value = true
|
||||
loading.value = true
|
||||
GetAIResponseResult("市场资讯").then(result => {
|
||||
loading.value = false
|
||||
if (result.content) {
|
||||
aiSummary.value = result.content
|
||||
question.value = result.question
|
||||
@@ -265,7 +211,7 @@ function getAiSummary() {
|
||||
aiSummaryTime.value = ""
|
||||
aiSummary.value = ""
|
||||
modelName.value = ""
|
||||
//SummaryStockNews(question.value, sysPromptId.value,enableTools.value)
|
||||
SummaryStockNews(question.value, sysPromptId.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -273,16 +219,13 @@ function getAiSummary() {
|
||||
function updateTab(name) {
|
||||
summaryBTN.value = (name === "市场快讯");
|
||||
nowTab.value = name
|
||||
if (name === "市场快讯") {
|
||||
Analyze()
|
||||
}
|
||||
}
|
||||
|
||||
EventsOn("summaryStockNews", async (msg) => {
|
||||
loading.value = false
|
||||
////console.log(msg)
|
||||
if (msg === "DONE") {
|
||||
await SaveAIResponseResult("市场资讯", "市场资讯", aiSummary.value, chatId.value, question.value,aiConfigId.value)
|
||||
SaveAIResponseResult("市场资讯", "市场资讯", aiSummary.value, chatId.value, question.value)
|
||||
message.info("AI分析完成!")
|
||||
message.destroyAll()
|
||||
|
||||
@@ -362,24 +305,16 @@ function ReFlesh(source) {
|
||||
|
||||
<template>
|
||||
<n-card>
|
||||
<n-tabs type="line" animated @update-value="updateTab" :value="nowTab" style="--wails-draggable:no-drag">
|
||||
<n-tabs type="line" animated @update-value="updateTab" :value="nowTab">
|
||||
<n-tab-pane name="市场快讯" tab="市场快讯">
|
||||
<n-grid :cols="1" :y-gap="0">
|
||||
<n-grid :cols="2" :y-gap="0">
|
||||
<n-gi>
|
||||
<div ref="treemapRef" style="width: 100%;height: 300px;" ></div>
|
||||
<news-list :newsList="telegraphList" :header-title="'财联社电报'" @update:message="ReFlesh"></news-list>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-grid :cols="2" :y-gap="0">
|
||||
<n-gi>
|
||||
<news-list :newsList="telegraphList" :header-title="'财联社电报'" @update:message="ReFlesh"></news-list>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<news-list :newsList="sinaNewsList" :header-title="'新浪财经'" @update:message="ReFlesh"></news-list>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<news-list :newsList="sinaNewsList" :header-title="'新浪财经'" @update:message="ReFlesh"></news-list>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="全球股指" tab="全球股指">
|
||||
<n-tabs type="segment" animated>
|
||||
@@ -448,34 +383,10 @@ function ReFlesh(source) {
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="重大指数" tab="重大指数">
|
||||
<n-tab-pane name="指标行情" tab="指标行情">
|
||||
<n-tabs type="segment" animated>
|
||||
<n-tab-pane name="恒生科技指数" tab="恒生科技指数">
|
||||
<k-line-chart code="hkHSTECH" :chart-height="panelHeight" name="恒生科技指数" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="科创50" tab="科创50" >
|
||||
<k-line-chart code="sh000688" :chart-height="panelHeight" name="科创50" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="科创芯片" tab="科创芯片" >
|
||||
<k-line-chart code="sh000685" :chart-height="panelHeight" name="科创芯片" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="证券龙头" tab="证券龙头" >
|
||||
<k-line-chart code="sz399437" :chart-height="panelHeight" name="证券龙头" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="高端装备" tab="高端装备" >
|
||||
<k-line-chart code="sz399437" :chart-height="panelHeight" name="高端装备" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="中证银行" tab="中证银行">
|
||||
<k-line-chart code="sz399986" :chart-height="panelHeight" name="中证银行" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="上证医药" tab="上证医药">
|
||||
<k-line-chart code="sh000037" :chart-height="panelHeight" name="上证医药" :k-days="20"
|
||||
<n-tab-pane name="科创50" tab="科创50">
|
||||
<k-line-chart code="sh000688" :chart-height="panelHeight" name="科创50" :k-days="20"
|
||||
:dark-theme="true"></k-line-chart>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="沪深300" tab="沪深300">
|
||||
@@ -687,9 +598,6 @@ function ReFlesh(source) {
|
||||
<n-tab-pane name="指标选股" tab="指标选股">
|
||||
<select-stock />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="名站优选" tab="名站优选">
|
||||
<Stockhotmap />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
<n-modal transform-origin="center" v-model:show="summaryModal" preset="card" style="width: 800px;"
|
||||
@@ -707,23 +615,10 @@ function ReFlesh(source) {
|
||||
</n-flex>
|
||||
</template>
|
||||
<template #action>
|
||||
<n-flex justify="left" style="margin-bottom: 10px">
|
||||
<n-switch v-model:value="enableTools" :round="false">
|
||||
<template #checked>
|
||||
启用AI函数工具调用
|
||||
</template>
|
||||
<template #unchecked>
|
||||
不启用AI函数工具调用
|
||||
</template>
|
||||
</n-switch>
|
||||
<n-gradient-text type="error" style="margin-left: 10px">*AI函数工具调用可以增强AI获取数据的能力,但会消耗更多tokens。</n-gradient-text>
|
||||
</n-flex>
|
||||
<n-flex justify="space-between" style="margin-bottom: 10px">
|
||||
<n-select style="width: 32%" v-model:value="aiConfigId" label-field="name" value-field="ID"
|
||||
:options="aiConfigs" placeholder="请选择AI模型服务配置"/>
|
||||
<n-select style="width: 32%" v-model:value="sysPromptId" label-field="name" value-field="ID"
|
||||
<n-select style="width: 49%" v-model:value="sysPromptId" label-field="name" value-field="ID"
|
||||
:options="sysPromptOptions" placeholder="请选择系统提示词"/>
|
||||
<n-select style="width: 32%" v-model:value="question" label-field="name" value-field="content"
|
||||
<n-select style="width: 49%" v-model:value="question" label-field="name" value-field="content"
|
||||
:options="userPromptOptions" placeholder="请选择用户提示词"/>
|
||||
</n-flex>
|
||||
<n-flex justify="right">
|
||||
@@ -756,4 +651,5 @@ function ReFlesh(source) {
|
||||
|
||||
</template>
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -49,9 +49,6 @@ const updateMessage = () => {
|
||||
<n-text type="warning">查看原文</n-text>
|
||||
</a>
|
||||
</n-tag>
|
||||
<n-tag v-if="item.sentimentResult" :bordered="false" :type="item.sentimentResult==='看涨'?'error':item.sentimentResult==='看跌'?'success':'info'" size="small">
|
||||
{{ item.sentimentResult }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
|
||||
@@ -1,201 +1,165 @@
|
||||
<script setup>
|
||||
import {h, onBeforeUnmount, onMounted, ref} from "vue";
|
||||
|
||||
import {computed, onBeforeUnmount, onMounted, ref} from "vue";
|
||||
import {
|
||||
AddPrompt, DelPrompt,
|
||||
ExportConfig,
|
||||
GetConfig,
|
||||
GetPromptTemplates,
|
||||
SendDingDingMessageByType,
|
||||
UpdateConfig, CheckSponsorCode
|
||||
UpdateConfig
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import {NTag, useMessage} from "naive-ui";
|
||||
import {useMessage} from "naive-ui";
|
||||
import {data, models} from "../../wailsjs/go/models";
|
||||
import {EventsEmit} from "../../wailsjs/runtime";
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const formRef = ref(null)
|
||||
const formValue = ref({
|
||||
ID: 1,
|
||||
tushareToken: '',
|
||||
dingPush: {
|
||||
enable: false,
|
||||
ID:1,
|
||||
tushareToken:'',
|
||||
dingPush:{
|
||||
enable:false,
|
||||
dingRobot: ''
|
||||
},
|
||||
localPush: {
|
||||
enable: true,
|
||||
localPush:{
|
||||
enable:true,
|
||||
},
|
||||
updateBasicInfoOnStart: false,
|
||||
refreshInterval: 1,
|
||||
openAI: {
|
||||
enable: false,
|
||||
aiConfigs: [], // AI配置列表
|
||||
prompt: "",
|
||||
questionTemplate: "{{stockName}}分析和总结",
|
||||
crawlTimeOut: 30,
|
||||
kDays: 30,
|
||||
},
|
||||
enableDanmu: false,
|
||||
browserPath: '',
|
||||
enableNews: false,
|
||||
darkTheme: true,
|
||||
enableFund: false,
|
||||
enablePushNews: false,
|
||||
enableOnlyPushRedNews: false,
|
||||
sponsorCode: "",
|
||||
httpProxy:"",
|
||||
httpProxyEnabled:false,
|
||||
enableAgent: false,
|
||||
})
|
||||
|
||||
// 添加一个新的AI配置到列表
|
||||
function addAiConfig() {
|
||||
formValue.value.openAI.aiConfigs.push(new data.AIConfig({
|
||||
name: '',
|
||||
updateBasicInfoOnStart:false,
|
||||
refreshInterval:1,
|
||||
openAI:{
|
||||
enable:false,
|
||||
baseUrl: 'https://api.deepseek.com',
|
||||
apiKey: '',
|
||||
modelName: 'deepseek-chat',
|
||||
model: 'deepseek-chat',
|
||||
temperature: 0.1,
|
||||
maxTokens: 1024,
|
||||
timeOut: 60,
|
||||
}));
|
||||
}
|
||||
|
||||
// 从列表中移除一个AI配置
|
||||
function removeAiConfig(index) {
|
||||
const originalCount = formValue.value.openAI.aiConfigs.length;
|
||||
// 使用filter创建新数组确保响应式更新
|
||||
formValue.value.openAI.aiConfigs = formValue.value.openAI.aiConfigs.filter((_, i) => i !== index);
|
||||
}
|
||||
|
||||
|
||||
const promptTemplates = ref([])
|
||||
onMounted(() => {
|
||||
GetConfig().then(res => {
|
||||
prompt:"",
|
||||
timeout: 5,
|
||||
questionTemplate: "{{stockName}}分析和总结",
|
||||
crawlTimeOut:30,
|
||||
kDays:30,
|
||||
},
|
||||
enableDanmu:false,
|
||||
browserPath: '',
|
||||
enableNews:false,
|
||||
darkTheme:true,
|
||||
enableFund:false,
|
||||
enablePushNews:false,
|
||||
})
|
||||
const promptTemplates=ref([])
|
||||
onMounted(()=>{
|
||||
GetConfig().then(res=>{
|
||||
formValue.value.ID = res.ID
|
||||
formValue.value.tushareToken = res.tushareToken
|
||||
formValue.value.dingPush = {
|
||||
enable: res.dingPushEnable,
|
||||
dingRobot: res.dingRobot
|
||||
enable:res.dingPushEnable,
|
||||
dingRobot:res.dingRobot
|
||||
}
|
||||
formValue.value.localPush = {
|
||||
enable: res.localPushEnable,
|
||||
enable:res.localPushEnable,
|
||||
}
|
||||
formValue.value.updateBasicInfoOnStart = res.updateBasicInfoOnStart
|
||||
formValue.value.refreshInterval = res.refreshInterval
|
||||
// 加载AI配置
|
||||
formValue.value.openAI = {
|
||||
enable: res.openAiEnable,
|
||||
aiConfigs: res.aiConfigs || [],
|
||||
prompt: res.prompt,
|
||||
questionTemplate: res.questionTemplate ? res.questionTemplate : '{{stockName}}分析和总结',
|
||||
crawlTimeOut: res.crawlTimeOut,
|
||||
kDays: res.kDays,
|
||||
enable:res.openAiEnable,
|
||||
baseUrl: res.openAiBaseUrl,
|
||||
apiKey:res.openAiApiKey,
|
||||
model:res.openAiModelName,
|
||||
temperature:res.openAiTemperature,
|
||||
maxTokens:res.openAiMaxTokens,
|
||||
prompt:res.prompt,
|
||||
timeout:res.openAiApiTimeOut,
|
||||
questionTemplate:res.questionTemplate?res.questionTemplate:'{{stockName}}分析和总结',
|
||||
crawlTimeOut:res.crawlTimeOut,
|
||||
kDays:res.kDays,
|
||||
}
|
||||
|
||||
|
||||
formValue.value.enableDanmu = res.enableDanmu
|
||||
formValue.value.browserPath = res.browserPath
|
||||
formValue.value.enableNews = res.enableNews
|
||||
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;
|
||||
|
||||
//console.log(res)
|
||||
})
|
||||
//message.info("加载完成")
|
||||
|
||||
GetPromptTemplates("", "").then(res => {
|
||||
promptTemplates.value = res
|
||||
GetPromptTemplates("","").then(res=>{
|
||||
//console.log(res)
|
||||
promptTemplates.value=res
|
||||
})
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
message.destroyAll()
|
||||
})
|
||||
|
||||
function saveConfig() {
|
||||
console.log('开始保存设置', formValue.value);
|
||||
// 构建配置时,包含aiConfigs列表
|
||||
let config = new data.SettingConfig({
|
||||
ID: formValue.value.ID,
|
||||
dingPushEnable: formValue.value.dingPush.enable,
|
||||
dingRobot: formValue.value.dingPush.dingRobot,
|
||||
localPushEnable: formValue.value.localPush.enable,
|
||||
updateBasicInfoOnStart: formValue.value.updateBasicInfoOnStart,
|
||||
refreshInterval: formValue.value.refreshInterval,
|
||||
openAiEnable: formValue.value.openAI.enable,
|
||||
aiConfigs: formValue.value.openAI.aiConfigs,
|
||||
// 序列化aiConfigs列表以传递给后端
|
||||
tushareToken: formValue.value.tushareToken,
|
||||
prompt: formValue.value.openAI.prompt,
|
||||
questionTemplate: formValue.value.openAI.questionTemplate,
|
||||
crawlTimeOut: formValue.value.openAI.crawlTimeOut,
|
||||
kDays: formValue.value.openAI.kDays,
|
||||
enableDanmu: formValue.value.enableDanmu,
|
||||
browserPath: formValue.value.browserPath,
|
||||
enableNews: formValue.value.enableNews,
|
||||
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,
|
||||
function saveConfig(){
|
||||
|
||||
let config= new data.Settings({
|
||||
ID:formValue.value.ID,
|
||||
dingPushEnable:formValue.value.dingPush.enable,
|
||||
dingRobot:formValue.value.dingPush.dingRobot,
|
||||
localPushEnable:formValue.value.localPush.enable,
|
||||
updateBasicInfoOnStart:formValue.value.updateBasicInfoOnStart,
|
||||
refreshInterval:formValue.value.refreshInterval,
|
||||
openAiEnable:formValue.value.openAI.enable,
|
||||
openAiBaseUrl:formValue.value.openAI.baseUrl,
|
||||
openAiApiKey:formValue.value.openAI.apiKey,
|
||||
openAiModelName:formValue.value.openAI.model,
|
||||
openAiMaxTokens:formValue.value.openAI.maxTokens,
|
||||
openAiTemperature:formValue.value.openAI.temperature,
|
||||
tushareToken:formValue.value.tushareToken,
|
||||
prompt:formValue.value.openAI.prompt,
|
||||
openAiApiTimeOut:formValue.value.openAI.timeout,
|
||||
questionTemplate:formValue.value.openAI.questionTemplate,
|
||||
crawlTimeOut:formValue.value.openAI.crawlTimeOut,
|
||||
kDays:formValue.value.openAI.kDays,
|
||||
enableDanmu:formValue.value.enableDanmu,
|
||||
browserPath:formValue.value.browserPath,
|
||||
enableNews:formValue.value.enableNews,
|
||||
darkTheme:formValue.value.darkTheme,
|
||||
enableFund:formValue.value.enableFund,
|
||||
enablePushNews:formValue.value.enablePushNews
|
||||
})
|
||||
|
||||
if (config.sponsorCode) {
|
||||
CheckSponsorCode(config.sponsorCode).then(res => {
|
||||
if (res.code) {
|
||||
UpdateConfig(config).then(res => {
|
||||
message.success(res)
|
||||
EventsEmit("updateSettings", config);
|
||||
})
|
||||
} else {
|
||||
message.error(res.msg)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
UpdateConfig(config).then(res => {
|
||||
message.success(res)
|
||||
EventsEmit("updateSettings", config);
|
||||
})
|
||||
}
|
||||
|
||||
//console.log("Settings",config)
|
||||
UpdateConfig(config).then(res=>{
|
||||
message.success(res)
|
||||
EventsEmit("updateSettings", config);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function getHeight() {
|
||||
return document.documentElement.clientHeight
|
||||
}
|
||||
|
||||
function sendTestNotice() {
|
||||
let markdown = "### go-stock test\n" + new Date()
|
||||
let msg = '{' +
|
||||
function sendTestNotice(){
|
||||
let markdown="### go-stock test\n"+new Date()
|
||||
let msg='{' +
|
||||
' "msgtype": "markdown",' +
|
||||
' "markdown": {' +
|
||||
' "title":"go-stock' + new Date() + '",' +
|
||||
' "text": "' + markdown + '"' +
|
||||
' "title":"go-stock'+new Date()+'",' +
|
||||
' "text": "'+markdown+'"' +
|
||||
' },' +
|
||||
' "at": {' +
|
||||
' "isAtAll": true' +
|
||||
' }' +
|
||||
' }'
|
||||
|
||||
SendDingDingMessageByType(msg, "test-" + new Date().getTime(), 1).then(res => {
|
||||
SendDingDingMessageByType(msg, "test-"+new Date().getTime(),1).then(res=>{
|
||||
message.info(res)
|
||||
})
|
||||
}
|
||||
|
||||
function exportConfig() {
|
||||
ExportConfig().then(res => {
|
||||
function exportConfig(){
|
||||
ExportConfig().then(res=>{
|
||||
message.info(res)
|
||||
})
|
||||
}
|
||||
|
||||
function importConfig() {
|
||||
function importConfig(){
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
@@ -204,25 +168,30 @@ function importConfig() {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
let config = JSON.parse(e.target.result);
|
||||
//console.log(config)
|
||||
formValue.value.ID = config.ID
|
||||
formValue.value.tushareToken = config.tushareToken
|
||||
formValue.value.dingPush = {
|
||||
enable: config.dingPushEnable,
|
||||
dingRobot: config.dingRobot
|
||||
enable:config.dingPushEnable,
|
||||
dingRobot:config.dingRobot
|
||||
}
|
||||
formValue.value.localPush = {
|
||||
enable: config.localPushEnable,
|
||||
enable:config.localPushEnable,
|
||||
}
|
||||
formValue.value.updateBasicInfoOnStart = config.updateBasicInfoOnStart
|
||||
formValue.value.refreshInterval = config.refreshInterval
|
||||
// 导入AI配置
|
||||
formValue.value.openAI = {
|
||||
enable: config.openAiEnable,
|
||||
aiConfigs: config.aiConfigs || [],
|
||||
prompt: config.prompt,
|
||||
questionTemplate: config.questionTemplate,
|
||||
crawlTimeOut: config.crawlTimeOut,
|
||||
kDays: config.kDays
|
||||
enable:config.openAiEnable,
|
||||
baseUrl: config.openAiBaseUrl,
|
||||
apiKey:config.openAiApiKey,
|
||||
model:config.openAiModelName,
|
||||
temperature:config.openAiTemperature,
|
||||
maxTokens:config.openAiMaxTokens,
|
||||
prompt:config.prompt,
|
||||
timeout:config.openAiApiTimeOut,
|
||||
questionTemplate:config.questionTemplate,
|
||||
crawlTimeOut:config.crawlTimeOut,
|
||||
kDays:config.kDays
|
||||
}
|
||||
formValue.value.enableDanmu = config.enableDanmu
|
||||
formValue.value.browserPath = config.browserPath
|
||||
@@ -230,11 +199,7 @@ 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
|
||||
// formRef.value.resetFields()
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
@@ -243,6 +208,8 @@ function importConfig() {
|
||||
|
||||
|
||||
window.onerror = function (event, source, lineno, colno, error) {
|
||||
//console.log(event, source, lineno, colno, error)
|
||||
// 将错误信息发送给后端
|
||||
EventsEmit("frontendError", {
|
||||
page: "settings.vue",
|
||||
message: event,
|
||||
@@ -251,256 +218,223 @@ window.onerror = function (event, source, lineno, colno, error) {
|
||||
colno: colno,
|
||||
error: error ? error.stack : null
|
||||
});
|
||||
//message.error("发生错误:"+event)
|
||||
return true;
|
||||
};
|
||||
|
||||
const showManagePromptsModal = ref(false)
|
||||
const promptTypeOptions = [
|
||||
{label: "模型系统Prompt", value: '模型系统Prompt'},
|
||||
{label: "模型用户Prompt", value: '模型用户Prompt'},]
|
||||
const formPromptRef = ref(null)
|
||||
const formPrompt = ref({
|
||||
ID: 0,
|
||||
Name: '',
|
||||
Content: '',
|
||||
Type: '',
|
||||
const showManagePromptsModal=ref(false)
|
||||
const promptTypeOptions=[
|
||||
{label:"模型系统Prompt",value:'模型系统Prompt'},
|
||||
{label:"模型用户Prompt",value:'模型用户Prompt'},]
|
||||
const formPromptRef=ref(null)
|
||||
const formPrompt=ref({
|
||||
ID:0,
|
||||
Name:'',
|
||||
Content:'',
|
||||
Type:'',
|
||||
})
|
||||
|
||||
function managePrompts() {
|
||||
formPrompt.value.ID = 0
|
||||
showManagePromptsModal.value = true
|
||||
function managePrompts(){
|
||||
formPrompt.value.ID=0
|
||||
showManagePromptsModal.value=true
|
||||
}
|
||||
|
||||
function savePrompt() {
|
||||
AddPrompt(formPrompt.value).then(res => {
|
||||
function savePrompt(){
|
||||
AddPrompt(formPrompt.value).then(res=>{
|
||||
message.success(res)
|
||||
GetPromptTemplates("", "").then(res => {
|
||||
promptTemplates.value = res
|
||||
GetPromptTemplates("","").then(res=>{
|
||||
//console.log(res)
|
||||
promptTemplates.value=res
|
||||
})
|
||||
showManagePromptsModal.value = false
|
||||
showManagePromptsModal.value=false
|
||||
})
|
||||
}
|
||||
|
||||
function editPrompt(prompt) {
|
||||
formPrompt.value.ID = prompt.ID
|
||||
formPrompt.value.Name = prompt.name
|
||||
formPrompt.value.Content = prompt.content
|
||||
formPrompt.value.Type = prompt.type
|
||||
showManagePromptsModal.value = true
|
||||
function editPrompt(prompt){
|
||||
//console.log(prompt)
|
||||
formPrompt.value.ID=prompt.ID
|
||||
formPrompt.value.Name=prompt.name
|
||||
formPrompt.value.Content=prompt.content
|
||||
formPrompt.value.Type=prompt.type
|
||||
showManagePromptsModal.value=true
|
||||
}
|
||||
|
||||
function deletePrompt(ID) {
|
||||
DelPrompt(ID).then(res => {
|
||||
function deletePrompt(ID){
|
||||
DelPrompt(ID).then(res=>{
|
||||
message.success(res)
|
||||
GetPromptTemplates("", "").then(res => {
|
||||
promptTemplates.value = res
|
||||
GetPromptTemplates("","").then(res=>{
|
||||
//console.log(res)
|
||||
promptTemplates.value=res
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-flex justify="left" style="text-align: left; --wails-draggable:no-drag">
|
||||
<n-form ref="formRef" :label-placement="'left'" :label-align="'left'">
|
||||
<n-space vertical size="large">
|
||||
<n-card :title="() => h(NTag, { type: 'primary', bordered: false }, () => '基础设置')" size="small">
|
||||
<n-grid :cols="24" :x-gap="24" style="text-align: left">
|
||||
<n-form-item-gi :span="10" label="Tushare Token:" path="tushareToken">
|
||||
<n-input type="text" placeholder="Tushare api token" v-model:value="formValue.tushareToken" clearable/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="4" label="启动时更新基础信息:" path="updateBasicInfoOnStart">
|
||||
<n-switch v-model:value="formValue.updateBasicInfoOnStart"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="4" label="数据刷新间隔:" path="refreshInterval">
|
||||
<n-input-number v-model:value="formValue.refreshInterval" placeholder="请输入数据刷新间隔(秒)">
|
||||
<template #suffix>秒</template>
|
||||
</n-input-number>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="6" label="暗黑主题:" path="darkTheme">
|
||||
<n-switch v-model:value="formValue.darkTheme"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="10" label="浏览器安装路径:" path="browserPath">
|
||||
<n-input type="text" placeholder="浏览器安装路径" v-model:value="formValue.browserPath" clearable/>
|
||||
</n-form-item-gi>
|
||||
<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-flex justify="left" style="margin-top: 12px;padding-left: 12px;">
|
||||
<n-form ref="formRef" :label-placement="'left'" :label-align="'left'" >
|
||||
<n-grid :cols="24" :x-gap="24" style="text-align: left" :layout-shift-disabled="true">
|
||||
<n-gi :span="24">
|
||||
<n-text type="success" style="font-size: 25px;font-weight: bold">基础设置</n-text>
|
||||
</n-gi>
|
||||
<n-form-item-gi :span="10" label="Tushare Token:" path="tushareToken" >
|
||||
<n-input type="text" placeholder="Tushare api token" v-model:value="formValue.tushareToken" clearable />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="4" label="启动时更新A股/指数信息:" path="updateBasicInfoOnStart" >
|
||||
<n-switch v-model:value="formValue.updateBasicInfoOnStart" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="4" label="数据刷新间隔:" path="refreshInterval" >
|
||||
<n-input-number v-model:value="formValue.refreshInterval" placeholder="请输入数据刷新间隔(秒)">
|
||||
<template #suffix>
|
||||
秒
|
||||
</template>
|
||||
</n-input-number>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="6" label="暗黑主题:" path="darkTheme" >
|
||||
<n-switch v-model:value="formValue.darkTheme" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="10" label="浏览器安装路径:" path="browserPath" >
|
||||
<n-input type="text" placeholder="浏览器安装路径" v-model:value="formValue.browserPath" clearable />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="6" label="指数基金:" path="enableFund" >
|
||||
<n-switch v-model:value="formValue.enableFund" />
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-form-item-gi :span="11" label="赞助码:" path="sponsorCode">
|
||||
<n-input-group>
|
||||
<n-input :show-count="true" placeholder="赞助码" v-model:value="formValue.sponsorCode"/>
|
||||
<n-button type="success" secondary strong
|
||||
@click="CheckSponsorCode(formValue.sponsorCode).then((res) => {message.warning(res.msg)})">验证
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
</n-card>
|
||||
<n-grid :cols="24" :x-gap="24" style="text-align: left">
|
||||
<n-gi :span="24">
|
||||
<n-text type="success" style="font-size: 25px;font-weight: bold">通知设置</n-text>
|
||||
</n-gi>
|
||||
<n-form-item-gi :span="4" 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-switch v-model:value="formValue.localPush.enable" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="4" label="弹幕功能:" path="enableDanmu" >
|
||||
<n-switch v-model:value="formValue.enableDanmu" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="4" label="显示滚动快讯:" path="enableNews" >
|
||||
<n-switch v-model:value="formValue.enableNews" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="4" label="市场资讯提醒:" path="enablePushNews" >
|
||||
<n-switch v-model:value="formValue.enablePushNews" />
|
||||
</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"/>
|
||||
<n-button type="primary" @click="sendTestNotice">发送测试通知</n-button>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
|
||||
<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="3" label="钉钉推送:" path="dingPush.enable">
|
||||
<n-switch v-model:value="formValue.dingPush.enable"/>
|
||||
</n-form-item-gi>
|
||||
<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="3" label="弹幕功能:" path="enableDanmu">
|
||||
<n-switch v-model:value="formValue.enableDanmu"/>
|
||||
</n-form-item-gi>
|
||||
<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="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"/>
|
||||
<n-button type="primary" @click="sendTestNotice">发送测试通知</n-button>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
</n-card>
|
||||
|
||||
<n-card :title="() => h(NTag, { type: 'primary', bordered: false }, () => 'AI设置')" size="small">
|
||||
<n-grid :cols="24" :x-gap="24" style="text-align: left;">
|
||||
<n-form-item-gi :span="24" label="AI诊股:" path="openAI.enable">
|
||||
<n-switch v-model:value="formValue.openAI.enable"/>
|
||||
</n-form-item-gi>
|
||||
|
||||
<n-form-item-gi :span="6" v-if="formValue.openAI.enable" label="Crawler Timeout(秒)"
|
||||
title="资讯采集超时时间(秒)" path="openAI.crawlTimeOut">
|
||||
<n-input-number min="30" step="1" v-model:value="formValue.openAI.crawlTimeOut"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="4" v-if="formValue.openAI.enable" title="天数越多消耗tokens越多"
|
||||
label="日K线数据(天)" path="openAI.kDays">
|
||||
<n-input-number min="30" step="1" max="365" v-model:value="formValue.openAI.kDays"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="2" label="http代理" path="httpProxyEnabled">
|
||||
<n-switch v-model:value="formValue.httpProxyEnabled"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="10" v-if="formValue.httpProxyEnabled" title="http代理地址"
|
||||
label="http代理地址" path="httpProxy">
|
||||
<n-input type="text" placeholder="http代理地址" v-model:value="formValue.httpProxy" clearable/>
|
||||
</n-form-item-gi>
|
||||
|
||||
|
||||
<n-gi :span="24" v-if="formValue.openAI.enable">
|
||||
<n-divider title-placement="left">Prompt 内容设置</n-divider>
|
||||
</n-gi>
|
||||
<n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="模型系统 Prompt" path="openAI.prompt">
|
||||
<n-input v-model:value="formValue.openAI.prompt" type="textarea" :show-count="true"
|
||||
placeholder="请输入系统prompt" :autosize="{ minRows: 4, maxRows: 8 }"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="模型用户 Prompt"
|
||||
path="openAI.questionTemplate">
|
||||
<n-input v-model:value="formValue.openAI.questionTemplate" type="textarea" :show-count="true"
|
||||
placeholder="请输入用户prompt:例如{{stockName}}[{{stockCode}}]分析和总结"
|
||||
:autosize="{ minRows: 4, maxRows: 8 }"/>
|
||||
</n-form-item-gi>
|
||||
|
||||
<n-gi :span="24" v-if="formValue.openAI.enable">
|
||||
<n-divider title-placement="left">AI模型服务配置</n-divider>
|
||||
</n-gi>
|
||||
<n-gi :span="24" v-if="formValue.openAI.enable">
|
||||
<n-space vertical>
|
||||
<n-card v-for="(aiConfig, index) in formValue.openAI.aiConfigs" :key="index" :bordered="true"
|
||||
size="small">
|
||||
<template #header>
|
||||
<n-flex justify="space-between" align="center">
|
||||
<n-text depth="3">AI 配置 #{{ index + 1 }}</n-text>
|
||||
<n-button type="error" size="tiny" ghost @click="removeAiConfig(index)">删除</n-button>
|
||||
</n-flex>
|
||||
</template>
|
||||
<n-grid :cols="24" :x-gap="24">
|
||||
<n-form-item-gi :span="24" hidden label="配置ID" :path="`openAI.aiConfigs[${index}].ID`">
|
||||
<n-input type="text" placeholder="配置ID" v-model:value="aiConfig.ID" clearable/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" label="配置名称" :path="`openAI.aiConfigs[${index}].name`">
|
||||
<n-input type="text" placeholder="配置名称" v-model:value="aiConfig.name" clearable/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" label="接口地址" :path="`openAI.aiConfigs[${index}].baseUrl`">
|
||||
<n-input type="text" placeholder="AI接口地址" v-model:value="aiConfig.baseUrl" clearable/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" label="令牌(apiKey)" :path="`openAI.aiConfigs[${index}].apiKey`">
|
||||
<n-input type="password" placeholder="apiKey" v-model:value="aiConfig.apiKey" clearable
|
||||
show-password-on="click"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="8" label="模型名称" :path="`openAI.aiConfigs[${index}].modelName`">
|
||||
<n-input type="text" placeholder="AI模型名称" v-model:value="aiConfig.modelName" clearable/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="5" label="Temperature" :path="`openAI.aiConfigs[${index}].temperature`">
|
||||
<n-input-number placeholder="temperature" v-model:value="aiConfig.temperature" :step="0.1"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="5" label="MaxTokens" :path="`openAI.aiConfigs[${index}].maxTokens`">
|
||||
<n-input-number placeholder="maxTokens" v-model:value="aiConfig.maxTokens"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="5" label="Timeout(秒)" :path="`openAI.aiConfigs[${index}].timeOut`">
|
||||
<n-input-number min="60" step="1" placeholder="超时(秒)" v-model:value="aiConfig.timeOut"/>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
</n-card>
|
||||
<n-button type="primary" dashed @click="addAiConfig" style="width: 100%;">+ 添加AI配置</n-button>
|
||||
</n-space>
|
||||
</n-gi>
|
||||
|
||||
<n-gi :span="24">
|
||||
<n-divider/>
|
||||
</n-gi>
|
||||
|
||||
<n-gi :span="24">
|
||||
<n-space vertical>
|
||||
<n-space justify="center">
|
||||
<n-button type="warning" @click="managePrompts">管理提示词模板</n-button>
|
||||
<n-button type="primary" strong @click="saveConfig">保存设置</n-button>
|
||||
<n-button type="info" @click="exportConfig">导出配置</n-button>
|
||||
<n-button type="error" @click="importConfig">导入配置</n-button>
|
||||
</n-space>
|
||||
|
||||
<n-flex justify="start" style="margin-top: 10px" v-if="promptTemplates.length > 0">
|
||||
<n-tag :bordered="false" type="warning">提示词模板:</n-tag>
|
||||
<n-tag size="medium" secondary v-for="prompt in promptTemplates" closable
|
||||
@close="deletePrompt(prompt.ID)" @click="editPrompt(prompt)" :title="prompt.content"
|
||||
:type="prompt.type === '模型系统Prompt' ? 'success' : 'info'" :bordered="false">{{
|
||||
prompt.name
|
||||
}}
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
</n-space>
|
||||
</n-gi>
|
||||
|
||||
</n-grid>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</n-form>
|
||||
<n-grid :cols="24" :x-gap="24" style="text-align: left;">
|
||||
<n-gi :span="24">
|
||||
<n-text type="success" style="font-size: 25px;font-weight: bold">OpenAI设置</n-text>
|
||||
</n-gi>
|
||||
<n-form-item-gi :span="3" label="AI诊股:" path="openAI.enable" >
|
||||
<n-switch v-model:value="formValue.openAI.enable" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="9" v-if="formValue.openAI.enable" label="openAI 接口地址:" path="openAI.baseUrl" >
|
||||
<n-input type="text" placeholder="AI接口地址" v-model:value="formValue.openAI.baseUrl" clearable />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="5" v-if="formValue.openAI.enable" label="AI Timeout(秒):" title="AI请求超时时间(秒)" path="openAI.timeout" >
|
||||
<n-input-number min="60" step="1" placeholder="AI请求超时时间(秒)" v-model:value="formValue.openAI.timeout" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="5" v-if="formValue.openAI.enable" label="Crawler Timeout(秒):" title="资讯采集超时时间(秒)" path="openAI.crawlTimeOut" >
|
||||
<n-input-number min="30" step="1" placeholder="资讯采集超时时间(秒)" v-model:value="formValue.openAI.crawlTimeOut" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="openAI 令牌(apiKey):" path="openAI.apiKey" >
|
||||
<n-input type="text" placeholder="apiKey" v-model:value="formValue.openAI.apiKey" clearable />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="10" v-if="formValue.openAI.enable" label="AI模型名称:" path="openAI.model" >
|
||||
<n-input type="text" placeholder="AI模型名称" v-model:value="formValue.openAI.model" clearable />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="openAI temperature:" path="openAI.temperature" >
|
||||
<n-input-number placeholder="temperature" v-model:value="formValue.openAI.temperature"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="5" v-if="formValue.openAI.enable" label="openAI maxTokens:" path="openAI.maxTokens" >
|
||||
<n-input-number placeholder="maxTokens" v-model:value="formValue.openAI.maxTokens"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="5" v-if="formValue.openAI.enable" title="天数越多消耗tokens越多" label="日K线数据(天):" path="openAI.maxTokens" >
|
||||
<n-input-number min="30" step="1" max="365" placeholder="日K线数据(天)" title="天数越多消耗tokens越多" v-model:value="formValue.openAI.kDays"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="11" v-if="formValue.openAI.enable" label="模型系统 Prompt:" path="openAI.prompt" >
|
||||
<n-input v-model:value="formValue.openAI.prompt"
|
||||
type="textarea"
|
||||
:show-count="true"
|
||||
placeholder="请输入系统prompt"
|
||||
:autosize="{
|
||||
minRows: 5,
|
||||
maxRows: 8
|
||||
}"
|
||||
/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="11" v-if="formValue.openAI.enable" label="模型用户 Prompt:" path="openAI.questionTemplate" >
|
||||
<n-input v-model:value="formValue.openAI.questionTemplate"
|
||||
type="textarea"
|
||||
:show-count="true"
|
||||
placeholder="请输入用户prompt:例如{{stockName}}[{{stockCode}}]分析和总结"
|
||||
:autosize="{
|
||||
minRows: 5,
|
||||
maxRows: 8
|
||||
}"
|
||||
/>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
<n-gi :span="24">
|
||||
<n-space justify="center">
|
||||
<n-button type="warning" @click="managePrompts">
|
||||
添加提示词模板
|
||||
</n-button>
|
||||
<n-button type="primary" @click="saveConfig">
|
||||
保存
|
||||
</n-button>
|
||||
<n-button type="info" @click="exportConfig">
|
||||
导出
|
||||
</n-button>
|
||||
<n-button type="error" @click="importConfig">
|
||||
导入
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-gi>
|
||||
</n-form>
|
||||
<n-gi :span="24" v-if="promptTemplates.length>0" v-for="prompt in promptTemplates" >
|
||||
<n-flex justify="start">
|
||||
<n-tag closable @close="deletePrompt(prompt.ID)" @click="editPrompt(prompt)" :title="prompt.content" :type="prompt.type==='模型系统Prompt'?'success':'info'" :bordered="false"> {{prompt.name}} </n-tag>
|
||||
</n-flex>
|
||||
</n-gi>
|
||||
</n-flex>
|
||||
|
||||
<n-modal v-model:show="showManagePromptsModal" closable :mask-closable="false">
|
||||
<n-card style="width: 800px; height: 600px; text-align: left" :bordered="false"
|
||||
:title="(formPrompt.ID > 0 ? '修改' : '添加') + '提示词'" size="huge" role="dialog" aria-modal="true">
|
||||
<n-form ref="formPromptRef" :label-placement="'left'" :label-align="'left'">
|
||||
<n-form-item label="名称">
|
||||
<n-input v-model:value="formPrompt.Name" placeholder="请输入提示词名称"/>
|
||||
<n-modal v-model:show="showManagePromptsModal" closable :mask-closable="false">
|
||||
<n-card
|
||||
style="width: 800px;height: 600px;text-align: left"
|
||||
:bordered="false"
|
||||
:title="(formPrompt.ID>0?'修改':'添加')+'提示词'"
|
||||
size="huge"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<n-form ref="formPromptRef" :label-placement="'left'" :label-align="'left'" >
|
||||
<n-form-item label="名称">
|
||||
<n-input v-model:value="formPrompt.Name" placeholder="请输入提示词名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="类型">
|
||||
<n-select v-model:value="formPrompt.Type" :options="promptTypeOptions" placeholder="请选择提示词类型"/>
|
||||
<n-form-item label="类型">
|
||||
<n-select v-model:value="formPrompt.Type" :options="promptTypeOptions" placeholder="请选择提示词类型" />
|
||||
</n-form-item>
|
||||
<n-form-item label="内容">
|
||||
<n-input v-model:value="formPrompt.Content" type="textarea" :show-count="true" placeholder="请输入prompt"
|
||||
:autosize="{ minRows: 12, maxRows: 12, }"/>
|
||||
<n-form-item label="内容">
|
||||
<n-input v-model:value="formPrompt.Content"
|
||||
type="textarea"
|
||||
:show-count="true"
|
||||
placeholder="请输入prompt"
|
||||
:autosize="{
|
||||
minRows: 12,
|
||||
maxRows: 12,
|
||||
}"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<n-flex justify="end">
|
||||
<n-button type="primary" @click="savePrompt">保存</n-button>
|
||||
<n-button type="warning" @click="showManagePromptsModal = false">取消</n-button>
|
||||
<n-button type="primary" @click="savePrompt">
|
||||
保存
|
||||
</n-button>
|
||||
<n-button type="warning" @click="showManagePromptsModal=false">
|
||||
取消
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-card>
|
||||
@@ -508,9 +442,5 @@ function deletePrompt(ID) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cardHeaderClass {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,137 +0,0 @@
|
||||
<script setup>
|
||||
import {onMounted, onBeforeMount, ref, watchEffect} from "vue";
|
||||
import * as echarts from 'echarts';
|
||||
import {GetStockMinutePriceLineData} from "../../wailsjs/go/main/App"; // 如果您使用多个组件,请将此样式导入放在您的主文件中
|
||||
const {stockCode,stockName,lastPrice,openPrice,darkTheme} = defineProps({
|
||||
stockCode: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
stockName: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
lastPrice: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
openPrice: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
darkTheme: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
})
|
||||
|
||||
const chartRef=ref();
|
||||
|
||||
function setChartData(chart) {
|
||||
//console.log("setChartData")
|
||||
GetStockMinutePriceLineData(stockCode, stockName).then(result => {
|
||||
//console.log("GetStockMinutePriceLineData",result)
|
||||
const priceData = result.priceData
|
||||
let category = []
|
||||
let price = []
|
||||
let min = 0
|
||||
let max = 0
|
||||
for (let i = 0; i < priceData.length; i++) {
|
||||
category.push(priceData[i].time)
|
||||
price.push(priceData[i].price)
|
||||
if (min === 0 || min > priceData[i].price) {
|
||||
min = priceData[i].price
|
||||
}
|
||||
if (max < priceData[i].price) {
|
||||
max = priceData[i].price
|
||||
}
|
||||
}
|
||||
let option = {
|
||||
padding: [0, 0, 0, 0],
|
||||
grid: {
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
show: false,
|
||||
type: 'category',
|
||||
data: category
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
type: 'value',
|
||||
min: (min).toFixed(2),
|
||||
max: (max).toFixed(2),
|
||||
minInterval: 0.01,
|
||||
},
|
||||
// visualMap: {
|
||||
// show: false,
|
||||
// type: 'piecewise',
|
||||
// pieces: [
|
||||
// {
|
||||
// min: Number(min),
|
||||
// max: Number(openPrice),
|
||||
// color: 'green'
|
||||
// },
|
||||
// {
|
||||
// min: Number(openPrice),
|
||||
// max: Number(max),
|
||||
// color: 'red'
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
series: [
|
||||
{
|
||||
data: price,
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
stack: '总量',
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
color: lastPrice > openPrice ? 'rgba(245, 0, 0, 1)' : 'rgb(6,251,10)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: lastPrice > openPrice ? 'rgba(245, 0, 0, 1)' : 'rgba(6,251,10, 1)'
|
||||
}, {
|
||||
offset: 1,
|
||||
color: lastPrice > openPrice ? 'rgba(245, 0, 0, 0.25)' : 'rgba(6,251,10, 0.25)'
|
||||
}])
|
||||
},
|
||||
}
|
||||
]
|
||||
};
|
||||
chart.setOption(option);
|
||||
})
|
||||
}
|
||||
const chart =ref( null)
|
||||
|
||||
onMounted(() => {
|
||||
chart.value = echarts.init( document.getElementById('sparkLine'+stockCode));
|
||||
setChartData(chart.value);
|
||||
})
|
||||
|
||||
|
||||
watchEffect(() => {
|
||||
console.log(stockName,'lastPrice变化为:', lastPrice,lastPrice > openPrice)
|
||||
setChartData(chart.value);
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div style="height: 20px;width: 100%" :id="'sparkLine'+stockCode">
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,39 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {h} from 'vue'
|
||||
import {NTag,NImage} from 'naive-ui'
|
||||
import EmbeddedUrl from "./EmbeddedUrl.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-tabs type="line" animated>
|
||||
<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://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://www.cls.cn/quotation" :height="'calc(100vh - 252px)'"/>
|
||||
</n-tab-pane>
|
||||
|
||||
|
||||
<n-tab-pane name="欢迎推荐更多有趣的财经网页" tab="欢迎推荐更多有趣的财经网页">
|
||||
</n-tab-pane>
|
||||
<!-- <n-tab-pane name="自在量化" tab="自在量化">-->
|
||||
<!-- <embedded-url url="https://quant.zizizaizai.com/home"/>-->
|
||||
<!-- </n-tab-pane>-->
|
||||
</n-tabs>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -2,8 +2,7 @@ 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)
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
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值作为延迟时间
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,21 @@ import {createMemoryHistory, createRouter, createWebHashHistory, createWebHistor
|
||||
|
||||
import stockView from '../components/stock.vue'
|
||||
import settingsView from '../components/settings.vue'
|
||||
import aboutView from "../components/about.vue";
|
||||
import about from "../components/about.vue";
|
||||
import fundView from "../components/fund.vue";
|
||||
import marketView from "../components/market.vue";
|
||||
import agentChat from "../components/agent-chat.vue"
|
||||
import market from "../components/market.vue";
|
||||
|
||||
const routes = [
|
||||
{ path: '/', component: stockView,name: 'stock'},
|
||||
{ path: '/fund', component: fundView,name: 'fund' },
|
||||
{ path: '/settings', component: settingsView,name: 'settings' },
|
||||
{ path: '/about', component: aboutView,name: 'about' },
|
||||
{ path: '/market', component: marketView,name: 'market' },
|
||||
{ path: '/agent', component: agentChat,name: 'agent' },
|
||||
{ path: '/about', component: about,name: 'about' },
|
||||
{ path: '/market', component: market,name: 'market' },
|
||||
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
//history: createWebHistory(),
|
||||
history: createWebHashHistory(),
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
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(),
|
||||
AutoImport({
|
||||
resolvers: [TDesignResolver({
|
||||
library: 'chat'
|
||||
})],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [TDesignResolver({
|
||||
library: 'chat'
|
||||
})],
|
||||
}),
|
||||
]
|
||||
plugins: [vue()]
|
||||
})
|
||||
|
||||
37
frontend/wailsjs/go/main/App.d.ts
vendored
Executable file → Normal file
37
frontend/wailsjs/go/main/App.d.ts
vendored
Executable file → Normal file
@@ -2,7 +2,6 @@
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {data} from '../models';
|
||||
import {models} from '../models';
|
||||
import {context} from '../models';
|
||||
|
||||
export function AddCronTask(arg1:data.FollowedStock):Promise<any>;
|
||||
|
||||
@@ -14,15 +13,7 @@ export function AddStockGroup(arg1:number,arg2:string):Promise<string>;
|
||||
|
||||
export function AnalyzeSentiment(arg1:string):Promise<data.SentimentResult>;
|
||||
|
||||
export function AnalyzeSentimentWithFreqWeight(arg1:string):Promise<Record<string, any>>;
|
||||
|
||||
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>;
|
||||
|
||||
export function CheckUpdate(arg1:number):Promise<void>;
|
||||
export function CheckUpdate():Promise<void>;
|
||||
|
||||
export function ClsCalendar():Promise<Array<any>>;
|
||||
|
||||
@@ -38,9 +29,7 @@ export function FollowFund(arg1:string):Promise<string>;
|
||||
|
||||
export function GetAIResponseResult(arg1:string):Promise<models.AIResponseResult>;
|
||||
|
||||
export function GetAiConfigs():Promise<Array<data.AIConfig>>;
|
||||
|
||||
export function GetConfig():Promise<data.SettingConfig>;
|
||||
export function GetConfig():Promise<data.Settings>;
|
||||
|
||||
export function GetFollowList(arg1:number):Promise<any>;
|
||||
|
||||
@@ -50,8 +39,6 @@ export function GetGroupList():Promise<Array<data.Group>>;
|
||||
|
||||
export function GetGroupStockList(arg1:number):Promise<Array<data.GroupStock>>;
|
||||
|
||||
export function GetHotStrategy():Promise<Record<string, any>>;
|
||||
|
||||
export function GetIndustryMoneyRankSina(arg1:string,arg2:string):Promise<Array<Record<string, any>>>;
|
||||
|
||||
export function GetIndustryRank(arg1:string,arg2:number):Promise<Array<any>>;
|
||||
@@ -60,8 +47,6 @@ export function GetMoneyRankSina(arg1:string):Promise<Array<Record<string, any>>
|
||||
|
||||
export function GetPromptTemplates(arg1:string,arg2:string):Promise<any>;
|
||||
|
||||
export function GetSponsorInfo():Promise<Record<string, any>>;
|
||||
|
||||
export function GetStockCommonKLine(arg1:string,arg2:string,arg3:number):Promise<any>;
|
||||
|
||||
export function GetStockKLine(arg1:string,arg2:string,arg3:number):Promise<any>;
|
||||
@@ -90,32 +75,24 @@ 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>;
|
||||
|
||||
export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:number,arg5:any,arg6:boolean):Promise<void>;
|
||||
export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:any):Promise<void>;
|
||||
|
||||
export function NewsPush(arg1:any):Promise<void>;
|
||||
|
||||
export function OpenURL(arg1:string):Promise<void>;
|
||||
|
||||
export function ReFleshTelegraphList(arg1:string):Promise<any>;
|
||||
|
||||
export function RemoveGroup(arg1:number):Promise<string>;
|
||||
|
||||
export function RemoveStockGroup(arg1:string,arg2:string,arg3:number):Promise<string>;
|
||||
|
||||
export function SaveAIResponseResult(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string,arg6:number):Promise<void>;
|
||||
export function SaveAIResponseResult(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string):Promise<void>;
|
||||
|
||||
export function SaveAsMarkdown(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SaveImage(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SaveWordFile(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SearchStock(arg1:string):Promise<Record<string, any>>;
|
||||
|
||||
export function SendDingDingMessage(arg1:string,arg2:string):Promise<string>;
|
||||
@@ -136,12 +113,10 @@ export function StockNotice(arg1:string):Promise<Array<any>>;
|
||||
|
||||
export function StockResearchReport(arg1:string):Promise<Array<any>>;
|
||||
|
||||
export function SummaryStockNews(arg1:string,arg2:number,arg3:any,arg4:boolean):Promise<void>;
|
||||
export function SummaryStockNews(arg1:string,arg2:any):Promise<void>;
|
||||
|
||||
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>;
|
||||
export function UpdateConfig(arg1:data.Settings):Promise<string>;
|
||||
|
||||
64
frontend/wailsjs/go/main/App.js
Executable file → Normal file
64
frontend/wailsjs/go/main/App.js
Executable file → Normal file
@@ -22,24 +22,8 @@ export function AnalyzeSentiment(arg1) {
|
||||
return window['go']['main']['App']['AnalyzeSentiment'](arg1);
|
||||
}
|
||||
|
||||
export function AnalyzeSentimentWithFreqWeight(arg1) {
|
||||
return window['go']['main']['App']['AnalyzeSentimentWithFreqWeight'](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);
|
||||
}
|
||||
|
||||
export function CheckStockBaseInfo(arg1) {
|
||||
return window['go']['main']['App']['CheckStockBaseInfo'](arg1);
|
||||
}
|
||||
|
||||
export function CheckUpdate(arg1) {
|
||||
return window['go']['main']['App']['CheckUpdate'](arg1);
|
||||
export function CheckUpdate() {
|
||||
return window['go']['main']['App']['CheckUpdate']();
|
||||
}
|
||||
|
||||
export function ClsCalendar() {
|
||||
@@ -70,10 +54,6 @@ export function GetAIResponseResult(arg1) {
|
||||
return window['go']['main']['App']['GetAIResponseResult'](arg1);
|
||||
}
|
||||
|
||||
export function GetAiConfigs() {
|
||||
return window['go']['main']['App']['GetAiConfigs']();
|
||||
}
|
||||
|
||||
export function GetConfig() {
|
||||
return window['go']['main']['App']['GetConfig']();
|
||||
}
|
||||
@@ -94,10 +74,6 @@ export function GetGroupStockList(arg1) {
|
||||
return window['go']['main']['App']['GetGroupStockList'](arg1);
|
||||
}
|
||||
|
||||
export function GetHotStrategy() {
|
||||
return window['go']['main']['App']['GetHotStrategy']();
|
||||
}
|
||||
|
||||
export function GetIndustryMoneyRankSina(arg1, arg2) {
|
||||
return window['go']['main']['App']['GetIndustryMoneyRankSina'](arg1, arg2);
|
||||
}
|
||||
@@ -114,10 +90,6 @@ export function GetPromptTemplates(arg1, arg2) {
|
||||
return window['go']['main']['App']['GetPromptTemplates'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function GetSponsorInfo() {
|
||||
return window['go']['main']['App']['GetSponsorInfo']();
|
||||
}
|
||||
|
||||
export function GetStockCommonKLine(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['GetStockCommonKLine'](arg1, arg2, arg3);
|
||||
}
|
||||
@@ -174,10 +146,6 @@ 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);
|
||||
}
|
||||
@@ -186,18 +154,14 @@ export function LongTigerRank(arg1) {
|
||||
return window['go']['main']['App']['LongTigerRank'](arg1);
|
||||
}
|
||||
|
||||
export function NewChatStream(arg1, arg2, arg3, arg4, arg5, arg6) {
|
||||
return window['go']['main']['App']['NewChatStream'](arg1, arg2, arg3, arg4, arg5, arg6);
|
||||
export function NewChatStream(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['main']['App']['NewChatStream'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function NewsPush(arg1) {
|
||||
return window['go']['main']['App']['NewsPush'](arg1);
|
||||
}
|
||||
|
||||
export function OpenURL(arg1) {
|
||||
return window['go']['main']['App']['OpenURL'](arg1);
|
||||
}
|
||||
|
||||
export function ReFleshTelegraphList(arg1) {
|
||||
return window['go']['main']['App']['ReFleshTelegraphList'](arg1);
|
||||
}
|
||||
@@ -210,22 +174,14 @@ export function RemoveStockGroup(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['RemoveStockGroup'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function SaveAIResponseResult(arg1, arg2, arg3, arg4, arg5, arg6) {
|
||||
return window['go']['main']['App']['SaveAIResponseResult'](arg1, arg2, arg3, arg4, arg5, arg6);
|
||||
export function SaveAIResponseResult(arg1, arg2, arg3, arg4, arg5) {
|
||||
return window['go']['main']['App']['SaveAIResponseResult'](arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
|
||||
export function SaveAsMarkdown(arg1, arg2) {
|
||||
return window['go']['main']['App']['SaveAsMarkdown'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SaveImage(arg1, arg2) {
|
||||
return window['go']['main']['App']['SaveImage'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SaveWordFile(arg1, arg2) {
|
||||
return window['go']['main']['App']['SaveWordFile'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SearchStock(arg1) {
|
||||
return window['go']['main']['App']['SearchStock'](arg1);
|
||||
}
|
||||
@@ -266,8 +222,8 @@ export function StockResearchReport(arg1) {
|
||||
return window['go']['main']['App']['StockResearchReport'](arg1);
|
||||
}
|
||||
|
||||
export function SummaryStockNews(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['main']['App']['SummaryStockNews'](arg1, arg2, arg3, arg4);
|
||||
export function SummaryStockNews(arg1, arg2) {
|
||||
return window['go']['main']['App']['SummaryStockNews'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function UnFollow(arg1) {
|
||||
@@ -281,7 +237,3 @@ 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);
|
||||
}
|
||||
|
||||
88
frontend/wailsjs/go/models.ts
Executable file → Normal file
88
frontend/wailsjs/go/models.ts
Executable file → Normal file
@@ -1,55 +1,5 @@
|
||||
export namespace data {
|
||||
|
||||
export class AIConfig {
|
||||
ID: number;
|
||||
// Go type: time
|
||||
CreatedAt: any;
|
||||
// Go type: time
|
||||
UpdatedAt: any;
|
||||
name: string;
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
modelName: string;
|
||||
maxTokens: number;
|
||||
temperature: number;
|
||||
timeOut: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AIConfig(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.ID = source["ID"];
|
||||
this.CreatedAt = this.convertValues(source["CreatedAt"], null);
|
||||
this.UpdatedAt = this.convertValues(source["UpdatedAt"], null);
|
||||
this.name = source["name"];
|
||||
this.baseUrl = source["baseUrl"];
|
||||
this.apiKey = source["apiKey"];
|
||||
this.modelName = source["modelName"];
|
||||
this.maxTokens = source["maxTokens"];
|
||||
this.temperature = source["temperature"];
|
||||
this.timeOut = source["timeOut"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class FundBasic {
|
||||
ID: number;
|
||||
// Go type: time
|
||||
@@ -296,7 +246,6 @@ export namespace data {
|
||||
Cron?: string;
|
||||
IsDel: number;
|
||||
Groups: GroupStock[];
|
||||
AiConfigId: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new FollowedStock(source);
|
||||
@@ -318,7 +267,6 @@ export namespace data {
|
||||
this.Cron = source["Cron"];
|
||||
this.IsDel = source["IsDel"];
|
||||
this.Groups = this.convertValues(source["Groups"], GroupStock);
|
||||
this.AiConfigId = source["AiConfigId"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
@@ -362,7 +310,7 @@ export namespace data {
|
||||
this.Description = source["Description"];
|
||||
}
|
||||
}
|
||||
export class SettingConfig {
|
||||
export class Settings {
|
||||
ID: number;
|
||||
// Go type: time
|
||||
CreatedAt: any;
|
||||
@@ -377,6 +325,12 @@ export namespace data {
|
||||
updateBasicInfoOnStart: boolean;
|
||||
refreshInterval: number;
|
||||
openAiEnable: boolean;
|
||||
openAiBaseUrl: string;
|
||||
openAiApiKey: string;
|
||||
openAiModelName: string;
|
||||
openAiMaxTokens: number;
|
||||
openAiTemperature: number;
|
||||
openAiApiTimeOut: number;
|
||||
prompt: string;
|
||||
checkUpdate: boolean;
|
||||
questionTemplate: string;
|
||||
@@ -389,15 +343,9 @@ 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 = {}) {
|
||||
return new SettingConfig(source);
|
||||
return new Settings(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
@@ -413,6 +361,12 @@ export namespace data {
|
||||
this.updateBasicInfoOnStart = source["updateBasicInfoOnStart"];
|
||||
this.refreshInterval = source["refreshInterval"];
|
||||
this.openAiEnable = source["openAiEnable"];
|
||||
this.openAiBaseUrl = source["openAiBaseUrl"];
|
||||
this.openAiApiKey = source["openAiApiKey"];
|
||||
this.openAiModelName = source["openAiModelName"];
|
||||
this.openAiMaxTokens = source["openAiMaxTokens"];
|
||||
this.openAiTemperature = source["openAiTemperature"];
|
||||
this.openAiApiTimeOut = source["openAiApiTimeOut"];
|
||||
this.prompt = source["prompt"];
|
||||
this.checkUpdate = source["checkUpdate"];
|
||||
this.questionTemplate = source["questionTemplate"];
|
||||
@@ -425,12 +379,6 @@ 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);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
@@ -476,8 +424,6 @@ export namespace data {
|
||||
is_hs: string;
|
||||
act_name: string;
|
||||
act_ent_type: string;
|
||||
bk_name: string;
|
||||
bk_code: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new StockBasic(source);
|
||||
@@ -506,8 +452,6 @@ export namespace data {
|
||||
this.is_hs = source["is_hs"];
|
||||
this.act_name = source["act_name"];
|
||||
this.act_ent_type = source["act_ent_type"];
|
||||
this.bk_name = source["bk_name"];
|
||||
this.bk_code = source["bk_code"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
@@ -756,9 +700,7 @@ export namespace models {
|
||||
icon: string;
|
||||
alipay: string;
|
||||
wxpay: string;
|
||||
wxgzh: string;
|
||||
buildTimeStamp: number;
|
||||
officialStatement: string;
|
||||
IsDel: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
@@ -776,9 +718,7 @@ export namespace models {
|
||||
this.icon = source["icon"];
|
||||
this.alipay = source["alipay"];
|
||||
this.wxpay = source["wxpay"];
|
||||
this.wxgzh = source["wxgzh"];
|
||||
this.buildTimeStamp = source["buildTimeStamp"];
|
||||
this.officialStatement = source["officialStatement"];
|
||||
this.IsDel = source["IsDel"];
|
||||
}
|
||||
|
||||
|
||||
68
go.mod
68
go.mod
@@ -1,33 +1,26 @@
|
||||
module go-stock
|
||||
|
||||
go 1.25.0
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.10.1
|
||||
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/chromedp/chromedp v0.11.2
|
||||
github.com/coocood/freecache v1.2.4
|
||||
github.com/duke-git/lancet/v2 v2.3.4
|
||||
github.com/energye/systray v1.0.2
|
||||
github.com/gen2brain/beeep v0.11.1
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-ego/gse v0.80.3
|
||||
github.com/go-resty/resty/v2 v2.16.2
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
|
||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
|
||||
github.com/robertkrimen/otto v0.5.1
|
||||
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.4
|
||||
github.com/tidwall/gjson v1.14.2
|
||||
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.36.0
|
||||
golang.org/x/text v0.26.0
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/text v0.23.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/gorm v1.25.12
|
||||
gorm.io/plugin/dbresolver v1.5.3
|
||||
@@ -35,43 +28,24 @@ require (
|
||||
)
|
||||
|
||||
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-20250803210736-d308e07a266d // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb // 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
|
||||
@@ -81,48 +55,26 @@ 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.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // 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/arch v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/net v0.38.0 // 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
|
||||
|
||||
285
go.sum
285
go.sum
@@ -1,53 +1,19 @@
|
||||
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-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/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/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=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/duke-git/lancet/v2 v2.3.4 h1:8XGI7P9w+/GqmEBEXYaH/XuNiM0f4/90Ioti0IvYJls=
|
||||
@@ -56,42 +22,18 @@ 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=
|
||||
@@ -103,74 +45,21 @@ 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=
|
||||
@@ -189,8 +78,6 @@ 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=
|
||||
@@ -204,45 +91,18 @@ 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/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c=
|
||||
github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4=
|
||||
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/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=
|
||||
@@ -253,63 +113,20 @@ 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=
|
||||
@@ -318,56 +135,34 @@ 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.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/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/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=
|
||||
@@ -380,9 +175,6 @@ 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=
|
||||
@@ -390,9 +182,6 @@ 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=
|
||||
@@ -400,7 +189,6 @@ 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=
|
||||
@@ -411,8 +199,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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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=
|
||||
@@ -422,8 +210,6 @@ 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=
|
||||
@@ -434,57 +220,24 @@ 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.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
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/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-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/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/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=
|
||||
@@ -500,8 +253,6 @@ 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=
|
||||
|
||||
131
main.go
131
main.go
@@ -5,22 +5,24 @@ import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"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"
|
||||
goruntime "runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed frontend/dist
|
||||
@@ -38,9 +40,6 @@ var alipay []byte
|
||||
//go:embed build/screenshot/wxpay.jpg
|
||||
var wxpay []byte
|
||||
|
||||
//go:embed build/screenshot/扫码_搜索联合传播样式-白色版.png
|
||||
var wxgzh []byte
|
||||
|
||||
//go:embed build/stock_basic.json
|
||||
var stocksBin []byte
|
||||
|
||||
@@ -54,13 +53,10 @@ var stocksBinUS []byte
|
||||
|
||||
var Version string
|
||||
var VersionCommit string
|
||||
var OFFICIAL_STATEMENT string
|
||||
var BuildKey string
|
||||
|
||||
func main() {
|
||||
checkDir("data")
|
||||
db.Init("")
|
||||
data.InitAnalyzeSentiment()
|
||||
go AutoMigrate()
|
||||
|
||||
//db.Dao.Model(&data.Group{}).Where("id = ?", 0).FirstOrCreate(&data.Group{
|
||||
@@ -71,33 +67,34 @@ func main() {
|
||||
// Create an instance of the app structure
|
||||
app := NewApp()
|
||||
AppMenu := menu.NewMenu()
|
||||
if IsMacOS() {
|
||||
AppMenu.Append(menu.EditMenu())
|
||||
}
|
||||
//FileMenu := AppMenu.AddSubmenu("设置")
|
||||
//FileMenu.AddText("窗口全屏", keys.CmdOrCtrl("f"), func(callback *menu.CallbackData) {
|
||||
// runtime.WindowFullscreen(app.ctx)
|
||||
//})
|
||||
//FileMenu.AddText("窗口还原", keys.Key("Esc"), func(callback *menu.CallbackData) {
|
||||
// runtime.WindowUnfullscreen(app.ctx)
|
||||
//})
|
||||
//FileMenu.AddText("显示搜索框", keys.CmdOrCtrl("s"), func(callbackData *menu.CallbackData) {
|
||||
// runtime.EventsEmit(app.ctx, "showSearch", 1)
|
||||
//})
|
||||
//FileMenu.AddText("隐藏搜索框", keys.CmdOrCtrl("d"), func(callbackData *menu.CallbackData) {
|
||||
// runtime.EventsEmit(app.ctx, "showSearch", 0)
|
||||
//})
|
||||
//FileMenu.AddText("刷新数据", keys.CmdOrCtrl("r"), func(callbackData *menu.CallbackData) {
|
||||
// //runtime.EventsEmit(app.ctx, "refresh", "setting-"+time.Now().Format("2006-01-02 15:04:05"))
|
||||
// runtime.EventsEmit(app.ctx, "refreshFollowList", "refresh-"+time.Now().Format("2006-01-02 15:04:05"))
|
||||
//})
|
||||
//FileMenu.AddSeparator()
|
||||
FileMenu := AppMenu.AddSubmenu("设置")
|
||||
FileMenu.AddText("显示搜索框", keys.CmdOrCtrl("s"), func(callbackData *menu.CallbackData) {
|
||||
runtime.EventsEmit(app.ctx, "showSearch", 1)
|
||||
})
|
||||
FileMenu.AddText("隐藏搜索框", keys.CmdOrCtrl("d"), func(callbackData *menu.CallbackData) {
|
||||
runtime.EventsEmit(app.ctx, "showSearch", 0)
|
||||
})
|
||||
FileMenu.AddText("刷新数据", keys.CmdOrCtrl("r"), func(callbackData *menu.CallbackData) {
|
||||
//runtime.EventsEmit(app.ctx, "refresh", "setting-"+time.Now().Format("2006-01-02 15:04:05"))
|
||||
runtime.EventsEmit(app.ctx, "refreshFollowList", "refresh-"+time.Now().Format("2006-01-02 15:04:05"))
|
||||
})
|
||||
FileMenu.AddSeparator()
|
||||
FileMenu.AddText("窗口全屏", keys.CmdOrCtrl("f"), func(callback *menu.CallbackData) {
|
||||
runtime.WindowFullscreen(app.ctx)
|
||||
})
|
||||
FileMenu.AddText("窗口还原", keys.Key("Esc"), func(callback *menu.CallbackData) {
|
||||
runtime.WindowUnfullscreen(app.ctx)
|
||||
})
|
||||
|
||||
//if goruntime.GOOS == "windows" {
|
||||
// FileMenu.AddText("隐藏到托盘区", keys.CmdOrCtrl("z"), func(_ *menu.CallbackData) {
|
||||
// runtime.WindowHide(app.ctx)
|
||||
// })
|
||||
//}
|
||||
if goruntime.GOOS == "windows" {
|
||||
FileMenu.AddText("隐藏到托盘区", keys.CmdOrCtrl("h"), func(_ *menu.CallbackData) {
|
||||
runtime.WindowHide(app.ctx)
|
||||
})
|
||||
|
||||
FileMenu.AddText("显示", keys.CmdOrCtrl("v"), func(_ *menu.CallbackData) {
|
||||
runtime.WindowShow(app.ctx)
|
||||
})
|
||||
}
|
||||
|
||||
//FileMenu.AddText("退出", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) {
|
||||
// runtime.Quit(app.ctx)
|
||||
@@ -108,33 +105,31 @@ func main() {
|
||||
//var width, height int
|
||||
//var err error
|
||||
//
|
||||
width, _, minWidth, minHeight, err := getScreenResolution()
|
||||
width, _, err := getScreenResolution()
|
||||
if err != nil {
|
||||
log.SugaredLogger.Error("get screen resolution error")
|
||||
width = 1456
|
||||
//height = 768
|
||||
}
|
||||
|
||||
darkTheme := data.GetSettingConfig().DarkTheme
|
||||
darkTheme := data.NewSettingsApi(&data.Settings{}).GetConfig().DarkTheme
|
||||
backgroundColour := &options.RGBA{R: 255, G: 255, B: 255, A: 1}
|
||||
if darkTheme {
|
||||
backgroundColour = &options.RGBA{R: 27, G: 38, B: 54, A: 1}
|
||||
}
|
||||
|
||||
//frameless := getFrameless()
|
||||
|
||||
// Create application with options
|
||||
err = wails.Run(&options.App{
|
||||
Title: "go-stock:AI赋能股票分析✨",
|
||||
Title: "go-stock",
|
||||
Width: width * 4 / 5,
|
||||
Height: 920,
|
||||
MinWidth: minWidth,
|
||||
MinHeight: minHeight,
|
||||
Height: 900,
|
||||
MinWidth: 1456,
|
||||
MinHeight: 768,
|
||||
//MaxWidth: width,
|
||||
//MaxHeight: height,
|
||||
DisableResize: false,
|
||||
Fullscreen: false,
|
||||
Frameless: false,
|
||||
Frameless: true,
|
||||
StartHidden: false,
|
||||
HideWindowOnClose: false,
|
||||
EnableDefaultContextMenu: true,
|
||||
@@ -150,7 +145,7 @@ func main() {
|
||||
OnShutdown: app.shutdown,
|
||||
WindowStartState: options.Normal,
|
||||
SingleInstanceLock: &options.SingleInstanceLock{
|
||||
UniqueId: "go-stock-dev",
|
||||
UniqueId: "go-stock",
|
||||
OnSecondInstanceLaunch: OnSecondInstanceLaunch,
|
||||
},
|
||||
Bind: []interface{}{
|
||||
@@ -167,11 +162,12 @@ func main() {
|
||||
// Mac platform specific options
|
||||
Mac: &mac.Options{
|
||||
TitleBar: &mac.TitleBar{
|
||||
TitlebarAppearsTransparent: false,
|
||||
TitlebarAppearsTransparent: true,
|
||||
HideTitle: false,
|
||||
HideTitleBar: false,
|
||||
FullSizeContent: false,
|
||||
UseToolbar: true,
|
||||
UseToolbar: false,
|
||||
HideToolbarSeparator: true,
|
||||
},
|
||||
Appearance: mac.NSAppearanceNameDarkAqua,
|
||||
WebviewIsTransparent: true,
|
||||
@@ -190,26 +186,6 @@ func main() {
|
||||
|
||||
}
|
||||
|
||||
func updateMultipleModel() {
|
||||
oldSettings := &models.OldSettings{}
|
||||
db.Dao.Model(oldSettings).First(oldSettings)
|
||||
aiConfig := &data.AIConfig{}
|
||||
db.Dao.Model(aiConfig).First(aiConfig)
|
||||
if oldSettings.OpenAiEnable && oldSettings.OpenAiApiKey != "" && aiConfig.ID == 0 {
|
||||
aiConfig.Name = oldSettings.OpenAiModelName
|
||||
aiConfig.ApiKey = oldSettings.OpenAiApiKey
|
||||
aiConfig.BaseUrl = oldSettings.OpenAiBaseUrl
|
||||
aiConfig.ModelName = oldSettings.OpenAiModelName
|
||||
aiConfig.Temperature = oldSettings.OpenAiTemperature
|
||||
aiConfig.MaxTokens = oldSettings.OpenAiMaxTokens
|
||||
aiConfig.TimeOut = oldSettings.OpenAiApiTimeOut
|
||||
err := db.Dao.Model(aiConfig).Create(aiConfig).Error
|
||||
if err != nil {
|
||||
log.SugaredLogger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func AutoMigrate() {
|
||||
db.Dao.AutoMigrate(&data.StockInfo{})
|
||||
db.Dao.AutoMigrate(&data.StockBasic{})
|
||||
@@ -228,10 +204,6 @@ func AutoMigrate() {
|
||||
db.Dao.AutoMigrate(&models.Telegraph{})
|
||||
db.Dao.AutoMigrate(&models.TelegraphTags{})
|
||||
db.Dao.AutoMigrate(&models.LongTigerRankData{})
|
||||
db.Dao.AutoMigrate(&data.AIConfig{})
|
||||
db.Dao.AutoMigrate(&models.BKDict{})
|
||||
|
||||
updateMultipleModel()
|
||||
}
|
||||
|
||||
func initStockDataUS(ctx context.Context) {
|
||||
@@ -288,7 +260,7 @@ func initStockDataHK(ctx context.Context) {
|
||||
}
|
||||
|
||||
func updateBasicInfo() {
|
||||
config := data.GetSettingConfig()
|
||||
config := data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||||
if config.UpdateBasicInfoOnStart {
|
||||
//更新基本信息
|
||||
go data.NewStockDataApi().GetStockBaseInfo()
|
||||
@@ -373,9 +345,6 @@ func checkDir(dir string) {
|
||||
os.Mkdir(dir, os.ModePerm)
|
||||
log.SugaredLogger.Info("create dir: " + dir)
|
||||
}
|
||||
if BuildKey == "" {
|
||||
BuildKey = "cc1e0d684e32f176c56ff1fcf384dcd9"
|
||||
}
|
||||
}
|
||||
|
||||
// PanicHandler 捕获 panic 的包装函数
|
||||
|
||||
23
utils.go
23
utils.go
@@ -1,23 +0,0 @@
|
||||
package main
|
||||
|
||||
// @Author spark
|
||||
// @Date 2025/7/8 18:51
|
||||
// @Desc
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
import "runtime"
|
||||
|
||||
// IsWindows 判断是否为 Windows 系统
|
||||
func IsWindows() bool {
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
||||
|
||||
// IsMacOS 判断是否为 macOS 系统
|
||||
func IsMacOS() bool {
|
||||
return runtime.GOOS == "darwin"
|
||||
}
|
||||
|
||||
// IsLinux 判断是否为 Linux 系统
|
||||
func IsLinux() bool {
|
||||
return runtime.GOOS == "linux"
|
||||
}
|
||||
Reference in New Issue
Block a user