feat(data): 添加 AIResponseResult模型并实现相关功能

感谢 @gnim2600 的建议!

- 新增 AIResponseResult 模型用于保存 AI 分析结果
- 实现 SaveAIResponseResult 和 GetAIResponseResult 函数
- 在前端添加 AI 分析功能,包括保存和获取分析结果
-优化 AI 分析界面,增加分析时间显示和再次分析按钮
This commit is contained in:
spark 2025-02-08 11:13:17 +08:00
parent 3e13ef007b
commit 7b93d4d8ca
8 changed files with 167 additions and 13 deletions

7
app.go
View File

@ -424,6 +424,13 @@ func (a *App) NewChatStream(stock, stockCode string) {
runtime.EventsEmit(a.ctx, "newChatStream", "DONE")
}
func (a *App) SaveAIResponseResult(stockCode, stockName, result string) {
data.NewDeepSeekOpenAi().SaveAIResponseResult(stockCode, stockName, result)
}
func (a *App) GetAIResponseResult(stock string) *models.AIResponseResult {
return data.NewDeepSeekOpenAi().GetAIResponseResult(stock)
}
func GenNotificationMsg(stockInfo *data.StockInfo) string {
Price, err := convertor.ToFloat(stockInfo.Price)
if err != nil {

View File

@ -9,7 +9,9 @@ import (
"github.com/chromedp/chromedp"
"github.com/duke-git/lancet/v2/strutil"
"github.com/go-resty/resty/v2"
"go-stock/backend/db"
"go-stock/backend/logger"
"go-stock/backend/models"
"strings"
"sync"
"time"
@ -208,6 +210,9 @@ func (o OpenAi) NewChatStream(stock, stockCode string) <-chan string {
client.SetHeader("Authorization", "Bearer "+o.ApiKey)
client.SetHeader("Content-Type", "application/json")
client.SetRetryCount(3)
if o.TimeOut <= 0 {
o.TimeOut = 300
}
client.SetTimeout(time.Duration(o.TimeOut) * time.Second)
resp, err := client.R().
SetDoNotParseResponse(true).
@ -446,3 +451,17 @@ func GetTelegraphList() *[]string {
})
return &telegraph
}
func (o OpenAi) SaveAIResponseResult(stockCode, stockName, result string) {
db.Dao.Create(&models.AIResponseResult{
StockCode: stockCode,
StockName: stockName,
Content: result,
})
}
func (o OpenAi) GetAIResponseResult(stock string) *models.AIResponseResult {
var result models.AIResponseResult
db.Dao.Where("stock_code = ?", stock).Order("id desc").First(&result)
return &result
}

View File

@ -1,6 +1,10 @@
package models
import "time"
import (
"gorm.io/gorm"
"gorm.io/plugin/soft_delete"
"time"
)
// @Author spark
// @Date 2025/2/6 15:25
@ -128,3 +132,15 @@ type Commit struct {
VerifiedAt interface{} `json:"verified_at"`
} `json:"verification"`
}
type AIResponseResult struct {
gorm.Model
StockCode string `json:"stockCode"`
StockName string `json:"stockName"`
Content string `json:"content"`
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
}
func (receiver AIResponseResult) TableName() string {
return "ai_response_result"
}

View File

@ -4,11 +4,11 @@ import {
Follow, GetConfig,
GetFollowList,
GetStockList,
Greet, NewChat, NewChatStream,
Greet, SaveAIResponseResult, NewChatStream,
SendDingDingMessage, SendDingDingMessageByType,
SetAlarmChangePercent,
SetCostPriceAndVolume, SetStockSort,
UnFollow
UnFollow, GetAIResponseResult
} from '../../wailsjs/go/main/App'
import {
NAvatar,
@ -152,6 +152,7 @@ EventsOn("newChatStream",async (msg) => {
//console.log("newChatStream:->",data.airesult)
data.loading = false
if (msg === "DONE") {
SaveAIResponseResult(data.code, data.name, data.airesult)
message.info("AI分析完成")
message.destroyAll()
} else {
@ -448,9 +449,9 @@ function SendMessage(result,type){
// SendDingDingMessage(msg,result[""])
SendDingDingMessageByType(msg,result["股票代码"],type)
}
function aiCheckStock(stock,stockCode){
function aiReCheckStock(stock,stockCode) {
data.airesult=""
data.time=""
data.name=stock
data.code=stockCode
data.loading=true
@ -461,6 +462,38 @@ function aiCheckStock(stock,stockCode){
NewChatStream(stock,stockCode)
}
function aiCheckStock(stock,stockCode){
GetAIResponseResult(stockCode).then(result => {
if(result.content){
data.name=stock
data.code=stockCode
data.loading=false
modalShow4.value=true
data.airesult=result.content
const date = new Date(result.CreatedAt);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
data.time=formattedDate
}else{
data.airesult=""
data.time=""
data.name=stock
data.code=stockCode
data.loading=true
modalShow4.value=true
message.loading("ai检测中...",{
duration: 0,
})
NewChatStream(stock,stockCode)
}
})
}
function getTypeName(type){
switch (type)
{
@ -518,7 +551,9 @@ function getHeight() {
<n-button size="tiny" secondary type="primary" @click="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
取消关注
</n-button>&nbsp;
<n-button size="tiny" v-if="data.openAiEnable" secondary type="warning" @click="aiCheckStock(result['股票名称'],result['股票代码'])"> AI分析 </n-button>
<n-button size="tiny" v-if="data.openAiEnable" secondary type="warning" @click="aiCheckStock(result['股票名称'],result['股票代码'])">
AI分析
</n-button>
</template>
<template #footer>
@ -609,18 +644,28 @@ function getHeight() {
<n-image :src="data.kURL" />
</n-modal>
<n-modal transform-origin="center" v-model:show="modalShow4" preset="card" style="width: 800px;height: 480px" :title="'['+data.name+']AI分析结果'" >
<n-modal transform-origin="center" v-model:show="modalShow4" preset="card" style="width: 800px;height: 500px" :title="'['+data.name+']AI分析结果'" >
<n-spin size="small" :show="data.loading">
<MdPreview ref="mdPreviewRef" style="height: 380px;text-align: left" :modelValue="data.airesult" :theme="'dark'"/>
</n-spin>
<template #header-extra>
</template>
<template #footer>
<n-flex justify="space-between">
<n-text type="error" v-if="data.time" >分析时间:{{data.time}}</n-text>
<n-button size="tiny" type="warning" @click="aiReCheckStock(data.name,data.code)">再次分析</n-button>
</n-flex>
</template>
</n-modal>
</template>
<style scoped>
h3 {
text-align: center;
}
#总结 {
text-align: center;
}
.md-editor-preview h3{
text-align: center !important;
}
.md-editor-preview p{
text-align: left !important;
}
</style>

View File

@ -1,9 +1,12 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {models} from '../models';
import {data} from '../models';
export function Follow(arg1:string):Promise<string>;
export function GetAIResponseResult(arg1:string):Promise<models.AIResponseResult>;
export function GetConfig():Promise<data.Settings>;
export function GetFollowList():Promise<Array<data.FollowedStock>>;
@ -16,6 +19,8 @@ export function NewChat(arg1:string):Promise<string>;
export function NewChatStream(arg1:string,arg2:string):Promise<void>;
export function SaveAIResponseResult(arg1:string,arg2:string,arg3:string):Promise<void>;
export function SendDingDingMessage(arg1:string,arg2:string):Promise<string>;
export function SendDingDingMessageByType(arg1:string,arg2:string,arg3:number):Promise<string>;

View File

@ -6,6 +6,10 @@ export function Follow(arg1) {
return window['go']['main']['App']['Follow'](arg1);
}
export function GetAIResponseResult(arg1) {
return window['go']['main']['App']['GetAIResponseResult'](arg1);
}
export function GetConfig() {
return window['go']['main']['App']['GetConfig']();
}
@ -30,6 +34,10 @@ export function NewChatStream(arg1, arg2) {
return window['go']['main']['App']['NewChatStream'](arg1, arg2);
}
export function SaveAIResponseResult(arg1, arg2, arg3) {
return window['go']['main']['App']['SaveAIResponseResult'](arg1, arg2, arg3);
}
export function SendDingDingMessage(arg1, arg2) {
return window['go']['main']['App']['SendDingDingMessage'](arg1, arg2);
}

View File

@ -329,3 +329,55 @@ export namespace data {
}
export namespace models {
export class AIResponseResult {
ID: number;
// Go type: time
CreatedAt: any;
// Go type: time
UpdatedAt: any;
// Go type: gorm
DeletedAt: any;
stockCode: string;
stockName: string;
content: string;
IsDel: number;
static createFrom(source: any = {}) {
return new AIResponseResult(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.DeletedAt = this.convertValues(source["DeletedAt"], null);
this.stockCode = source["stockCode"];
this.stockName = source["stockName"];
this.content = source["content"];
this.IsDel = source["IsDel"];
}
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;
}
}
}

View File

@ -14,6 +14,7 @@ import (
"github.com/wailsapp/wails/v2/pkg/runtime"
"go-stock/backend/data"
"go-stock/backend/db"
"go-stock/backend/models"
"log"
"os"
goruntime "runtime"
@ -44,6 +45,7 @@ func main() {
db.Dao.AutoMigrate(&data.FollowedStock{})
db.Dao.AutoMigrate(&data.IndexBasic{})
db.Dao.AutoMigrate(&data.Settings{})
db.Dao.AutoMigrate(&models.AIResponseResult{})
if stocksBin != nil && len(stocksBin) > 0 {
go initStockData()