对接到deepseek分析
This commit is contained in:
parent
0f04b1370a
commit
398dfdf487
102
src/api/deepseek/analyze.ts
Normal file
102
src/api/deepseek/analyze.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import type { AiAdvice, AdviceDirection } from '../../types'
|
||||||
|
import { deepseekHttp } from '../http'
|
||||||
|
import type { AiAnalysisPayload } from './types'
|
||||||
|
import { buildAnalysisMessages } from './prompt'
|
||||||
|
import type {
|
||||||
|
DeepSeekAdviceJson,
|
||||||
|
DeepSeekChatRequest,
|
||||||
|
DeepSeekChatResponse,
|
||||||
|
} from './types'
|
||||||
|
|
||||||
|
/** 当前推荐模型(deepseek-chat 将于 2026-07-24 弃用) */
|
||||||
|
export const DEEPSEEK_MODEL = 'deepseek-v4-flash'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用 DeepSeek,用关键词 + 格式化行情/新闻/持仓数据生成交易建议。
|
||||||
|
*/
|
||||||
|
export async function fetchDeepSeekAdvice(
|
||||||
|
apiKey: string,
|
||||||
|
payload: AiAnalysisPayload,
|
||||||
|
): Promise<AiAdvice> {
|
||||||
|
const body: DeepSeekChatRequest = {
|
||||||
|
model: DEEPSEEK_MODEL,
|
||||||
|
messages: buildAnalysisMessages(payload),
|
||||||
|
temperature: 0.3,
|
||||||
|
response_format: { type: 'json_object' },
|
||||||
|
thinking: { type: 'disabled' },
|
||||||
|
stream: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await deepseekHttp.post<DeepSeekChatResponse>(
|
||||||
|
'/chat/completions',
|
||||||
|
body,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
},
|
||||||
|
timeout: 90000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const content = data.choices?.[0]?.message?.content
|
||||||
|
if (!content?.trim()) {
|
||||||
|
throw new Error('DeepSeek 返回空内容')
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseAdviceContent(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAdviceContent(content: string): AiAdvice {
|
||||||
|
const jsonText = extractJsonObject(content)
|
||||||
|
let raw: DeepSeekAdviceJson
|
||||||
|
try {
|
||||||
|
raw = JSON.parse(jsonText) as DeepSeekAdviceJson
|
||||||
|
} catch {
|
||||||
|
throw new Error(`DeepSeek 返回非 JSON:${content.slice(0, 200)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const direction = normalizeDirection(raw.direction)
|
||||||
|
const confidence = clampConfidence(raw.confidence)
|
||||||
|
const reasons = Array.isArray(raw.reasons)
|
||||||
|
? raw.reasons.map(String).filter(Boolean).slice(0, 8)
|
||||||
|
: []
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: String(raw.action || directionToAction(direction)).trim() || '观望',
|
||||||
|
direction,
|
||||||
|
confidence,
|
||||||
|
summary: String(raw.summary || '').trim() || '暂无摘要',
|
||||||
|
reasons: reasons.length ? reasons : ['模型未给出具体理由'],
|
||||||
|
updatedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractJsonObject(text: string): string {
|
||||||
|
const trimmed = text.trim()
|
||||||
|
const fence = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i)
|
||||||
|
if (fence?.[1]) return fence[1].trim()
|
||||||
|
|
||||||
|
const start = trimmed.indexOf('{')
|
||||||
|
const end = trimmed.lastIndexOf('}')
|
||||||
|
if (start >= 0 && end > start) return trimmed.slice(start, end + 1)
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDirection(value: unknown): AdviceDirection {
|
||||||
|
const v = String(value || '').toLowerCase()
|
||||||
|
if (v === 'long' || v === 'buy' || v === '多' || v === '买入') return 'long'
|
||||||
|
if (v === 'short' || v === 'sell' || v === '空' || v === '卖出') return 'short'
|
||||||
|
return 'neutral'
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampConfidence(value: unknown): number {
|
||||||
|
const n = Number(value)
|
||||||
|
if (!Number.isFinite(n)) return 50
|
||||||
|
return Math.max(0, Math.min(100, Math.round(n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function directionToAction(direction: AdviceDirection): string {
|
||||||
|
if (direction === 'long') return '买入'
|
||||||
|
if (direction === 'short') return '卖出'
|
||||||
|
return '观望'
|
||||||
|
}
|
||||||
61
src/api/deepseek/prompt.ts
Normal file
61
src/api/deepseek/prompt.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import type { AnalysisSnapshot } from '../../stores/quota'
|
||||||
|
import type { AiAnalysisPayload } from './types'
|
||||||
|
|
||||||
|
const OUTPUT_SCHEMA = `{
|
||||||
|
"action": "买入 | 卖出 | 观望(或更具体的操作建议,如「轻仓试多」)",
|
||||||
|
"direction": "long | short | neutral",
|
||||||
|
"confidence": 0-100 的整数,
|
||||||
|
"summary": "一句话结论,面向交易员",
|
||||||
|
"reasons": ["理由1", "理由2", "理由3", "理由4"]
|
||||||
|
}`
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将行情 / 新闻 / 持仓格式化规则数据拼成 DeepSeek messages。
|
||||||
|
* keywords 作为 system 角色设定;snapshot 作为 user 侧事实数据。
|
||||||
|
*/
|
||||||
|
export function buildAnalysisMessages(payload: AiAnalysisPayload): Array<{
|
||||||
|
role: 'system' | 'user'
|
||||||
|
content: string
|
||||||
|
}> {
|
||||||
|
const system = [
|
||||||
|
payload.keywords.trim() || '你是一名专业的国内期货交易员和分析员。',
|
||||||
|
'请仅基于用户提供的行情、盘口、新闻与机构持仓数据给出交易建议。',
|
||||||
|
'不要编造未提供的数据;信息不足时偏向观望并说明原因。',
|
||||||
|
'必须只输出一个 JSON 对象,不要 Markdown 代码块,不要其它说明文字。',
|
||||||
|
`JSON 格式:${OUTPUT_SCHEMA}`,
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
const user = [
|
||||||
|
'以下为当前合约的格式化分析数据(JSON),请据此给出建议:',
|
||||||
|
JSON.stringify(slimSnapshot(payload.snapshot), null, 2),
|
||||||
|
].join('\n\n')
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ role: 'system', content: system },
|
||||||
|
{ role: 'user', content: user },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩超长数组,保留分析关键字段,避免 prompt 过大。
|
||||||
|
* 盘口、统计、持仓、新闻完整保留;分时/成交/K 线做尾部截断。
|
||||||
|
*/
|
||||||
|
function slimSnapshot(snapshot: AnalysisSnapshot): AnalysisSnapshot {
|
||||||
|
const quote = snapshot.quote
|
||||||
|
if (!quote) return snapshot
|
||||||
|
|
||||||
|
return {
|
||||||
|
...snapshot,
|
||||||
|
quote: {
|
||||||
|
...quote,
|
||||||
|
intraday: quote.intraday.slice(-120),
|
||||||
|
trades: quote.trades.slice(-80),
|
||||||
|
candles: {
|
||||||
|
day: quote.candles.day.slice(-60),
|
||||||
|
week: quote.candles.week.slice(-40),
|
||||||
|
month: quote.candles.month.slice(-36),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
news: snapshot.news.slice(0, 30),
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/api/deepseek/types.ts
Normal file
52
src/api/deepseek/types.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import type { AnalysisSnapshot } from '../../stores/quota'
|
||||||
|
|
||||||
|
/** 提交给 AI 的完整载荷(格式化后的规则数据 + 关键词) */
|
||||||
|
export interface AiAnalysisPayload {
|
||||||
|
/** 设置中的角色/分析关键词 */
|
||||||
|
keywords: string
|
||||||
|
/** 行情 + 新闻 + 机构持仓等接口快照 */
|
||||||
|
snapshot: AnalysisSnapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DeepSeek Chat Completions(OpenAI 兼容)请求体 */
|
||||||
|
export interface DeepSeekChatRequest {
|
||||||
|
model: string
|
||||||
|
messages: Array<{
|
||||||
|
role: 'system' | 'user' | 'assistant'
|
||||||
|
content: string
|
||||||
|
}>
|
||||||
|
temperature?: number
|
||||||
|
response_format?: { type: 'json_object' | 'text' }
|
||||||
|
stream?: boolean
|
||||||
|
/** V4 默认开启 thinking;分析建议关闭以加快 JSON 输出 */
|
||||||
|
thinking?: { type: 'enabled' | 'disabled' }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeepSeekChatChoice {
|
||||||
|
index: number
|
||||||
|
message: {
|
||||||
|
role: string
|
||||||
|
content: string | null
|
||||||
|
}
|
||||||
|
finish_reason: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeepSeekChatResponse {
|
||||||
|
id: string
|
||||||
|
model: string
|
||||||
|
choices: DeepSeekChatChoice[]
|
||||||
|
usage?: {
|
||||||
|
prompt_tokens: number
|
||||||
|
completion_tokens: number
|
||||||
|
total_tokens: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 要求模型严格返回的 JSON 结构(与 AiAdvice 对齐) */
|
||||||
|
export interface DeepSeekAdviceJson {
|
||||||
|
action: string
|
||||||
|
direction: 'long' | 'short' | 'neutral'
|
||||||
|
confidence: number
|
||||||
|
summary: string
|
||||||
|
reasons: string[]
|
||||||
|
}
|
||||||
@ -11,3 +11,9 @@ export const jqkaHttp = axios.create({
|
|||||||
baseURL: '/jqka',
|
baseURL: '/jqka',
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 开发环境走 Vite 代理 /deepseek → https://api.deepseek.com */
|
||||||
|
export const deepseekHttp = axios.create({
|
||||||
|
baseURL: '/deepseek',
|
||||||
|
timeout: 90000,
|
||||||
|
})
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="expanded" class="body">
|
<div v-show="expanded" class="body">
|
||||||
<div class="hint">基于行情、盘口、新闻与持仓的综合分析(当前为 Mock)</div>
|
<div class="hint">基于行情、盘口、新闻与持仓,由 DeepSeek 综合分析</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(r, i) in data.reasons" :key="i">{{ r }}</li>
|
<li v-for="(r, i) in data.reasons" :key="i">{{ r }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -25,9 +25,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import type { AiAdvice } from '../../types'
|
import type { AiAdvice } from '../../types'
|
||||||
import { useSettingsStore } from '../../stores/settings'
|
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@ -40,13 +38,9 @@ withDefaults(
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
refresh: []
|
refresh: []
|
||||||
}>()
|
}>()
|
||||||
const settings = useSettingsStore()
|
|
||||||
const expanded = ref(false)
|
const expanded = ref(false)
|
||||||
|
|
||||||
function onRefresh() {
|
function onRefresh() {
|
||||||
if (!settings.apiKey) {
|
|
||||||
ElMessage.warning('请先在设置中配置 DeepSeek API Key(本阶段仍返回 Mock 建议)')
|
|
||||||
}
|
|
||||||
emit('refresh')
|
emit('refresh')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,17 +1,12 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
import type { AiAdvice } from '../types'
|
import type { AiAdvice } from '../types'
|
||||||
import { aiAdviceMock } from '../mocks/aiAdvice'
|
import { aiAdviceMock } from '../mocks/aiAdvice'
|
||||||
import { useQuotaStore, type AnalysisSnapshot, type KlinePeriod } from '../stores/quota'
|
import { fetchDeepSeekAdvice } from '../api/deepseek/analyze'
|
||||||
|
import type { AiAnalysisPayload } from '../api/deepseek/types'
|
||||||
|
import { useQuotaStore, type KlinePeriod } from '../stores/quota'
|
||||||
import { useSettingsStore } from '../stores/settings'
|
import { useSettingsStore } from '../stores/settings'
|
||||||
|
|
||||||
/** 提交给 AI 的完整载荷(格式化后的规则数据 + 关键词) */
|
|
||||||
export interface AiAnalysisPayload {
|
|
||||||
/** 设置中的角色/分析关键词 */
|
|
||||||
keywords: string
|
|
||||||
/** 行情 + 新闻 + 机构持仓等接口快照 */
|
|
||||||
snapshot: AnalysisSnapshot
|
|
||||||
}
|
|
||||||
|
|
||||||
const KLINE_PERIODS: KlinePeriod[] = ['day', 'week', 'month']
|
const KLINE_PERIODS: KlinePeriod[] = ['day', 'week', 'month']
|
||||||
|
|
||||||
export function useAiAdvice() {
|
export function useAiAdvice() {
|
||||||
@ -35,14 +30,19 @@ export function useAiAdvice() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一键获取建议:先收集行情 / 新闻 / 持仓(含 K 线),
|
* 一键获取建议:收集行情 / 新闻 / 持仓后提交 DeepSeek,返回真实 AI 建议。
|
||||||
* 再拼装提交给 AI 的关键词与格式化快照;当前阶段仅 console.log,仍返回 Mock 建议。
|
|
||||||
*/
|
*/
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
|
const apiKey = settings.apiKey.trim()
|
||||||
|
if (!apiKey) {
|
||||||
|
ElMessage.warning('请先在设置中配置 DeepSeek API Key')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
await quota.fetchAll()
|
await quota.fetchForAnalysis()
|
||||||
await ensureKlines()
|
await ensureKlines()
|
||||||
|
|
||||||
const payload: AiAnalysisPayload = {
|
const payload: AiAnalysisPayload = {
|
||||||
@ -50,18 +50,16 @@ export function useAiAdvice() {
|
|||||||
snapshot: quota.getAnalysisSnapshot(),
|
snapshot: quota.getAnalysisSnapshot(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 本阶段:打印将提交给 AI 的关键词与全部接口规则数据
|
console.log('[AI] 提交给 DeepSeek 的关键词', payload.keywords)
|
||||||
console.log('[AI] 提交给 AI 的关键词', payload.keywords)
|
console.log('[AI] 提交给 DeepSeek 的数据快照', payload.snapshot)
|
||||||
console.log('[AI] 提交给 AI 的数据(格式化规则快照)', payload.snapshot)
|
|
||||||
console.log('[AI] 完整载荷', payload)
|
|
||||||
|
|
||||||
data.value = {
|
data.value = await fetchDeepSeekAdvice(apiKey, payload)
|
||||||
...structuredClone(aiAdviceMock),
|
ElMessage.success('AI 建议已更新')
|
||||||
updatedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = e
|
error.value = e
|
||||||
|
const msg = formatError(e)
|
||||||
console.error('[AI] 一键获取建议失败', e)
|
console.error('[AI] 一键获取建议失败', e)
|
||||||
|
ElMessage.error(msg)
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@ -69,3 +67,15 @@ export function useAiAdvice() {
|
|||||||
|
|
||||||
return { data, loading, error, refresh }
|
return { data, loading, error, refresh }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatError(e: unknown): string {
|
||||||
|
if (typeof e === 'object' && e && 'response' in e) {
|
||||||
|
const resp = (e as { response?: { status?: number; data?: { error?: { message?: string } } } })
|
||||||
|
.response
|
||||||
|
const apiMsg = resp?.data?.error?.message
|
||||||
|
if (apiMsg) return `DeepSeek:${apiMsg}`
|
||||||
|
if (resp?.status) return `DeepSeek 请求失败(HTTP ${resp.status})`
|
||||||
|
}
|
||||||
|
if (e instanceof Error) return e.message
|
||||||
|
return '获取 AI 建议失败'
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref, toRaw } from 'vue'
|
||||||
import type { Candle, NewsItem, PositionsData, QuoteData } from '../types'
|
import type { Candle, NewsItem, PositionsData, QuoteData } from '../types'
|
||||||
import { quoteMock } from '../mocks/quote'
|
import { quoteMock } from '../mocks/quote'
|
||||||
import { newsMock } from '../mocks/news'
|
import { newsMock } from '../mocks/news'
|
||||||
@ -70,6 +70,15 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
/** 是否已有至少一次成功的行情拉取(可用于 AI 门禁) */
|
/** 是否已有至少一次成功的行情拉取(可用于 AI 门禁) */
|
||||||
const hasQuote = computed(() => quote.value.last > 0 && quote.value.intraday.length > 0)
|
const hasQuote = computed(() => quote.value.last > 0 && quote.value.intraday.length > 0)
|
||||||
const hasNews = computed(() => news.value.length > 0)
|
const hasNews = computed(() => news.value.length > 0)
|
||||||
|
/** 新闻 / 持仓是否已成功拉取过真实接口(有则 AI 刷新可跳过) */
|
||||||
|
const newsLoaded = ref(false)
|
||||||
|
const positionsLoaded = ref(false)
|
||||||
|
const hasPositions = computed(
|
||||||
|
() =>
|
||||||
|
positions.value.long.length > 0 ||
|
||||||
|
positions.value.short.length > 0 ||
|
||||||
|
positions.value.volume.length > 0,
|
||||||
|
)
|
||||||
|
|
||||||
async function fetchQuote() {
|
async function fetchQuote() {
|
||||||
quoteLoading.value = true
|
quoteLoading.value = true
|
||||||
@ -120,6 +129,7 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
try {
|
try {
|
||||||
const list = await getFuturesNews({ code: contractConfig.code })
|
const list = await getFuturesNews({ code: contractConfig.code })
|
||||||
news.value = mapBaiduNewsToItems(list)
|
news.value = mapBaiduNewsToItems(list)
|
||||||
|
newsLoaded.value = news.value.length > 0
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
newsError.value = e
|
newsError.value = e
|
||||||
console.error('[quota] 拉取新闻失败', e)
|
console.error('[quota] 拉取新闻失败', e)
|
||||||
@ -168,6 +178,7 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
positions.value = mapped
|
positions.value = mapped
|
||||||
|
positionsLoaded.value = true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
positionsError.value = e
|
positionsError.value = e
|
||||||
console.error('[quota] 拉取持仓失败', e)
|
console.error('[quota] 拉取持仓失败', e)
|
||||||
@ -181,6 +192,25 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
await Promise.all([fetchQuote(), fetchNews(), fetchPositions()])
|
await Promise.all([fetchQuote(), fetchNews(), fetchPositions()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI 分析前取数:行情每次重新拉取;
|
||||||
|
* 新闻 / 机构持仓若已有成功数据则跳过,避免重复请求。
|
||||||
|
*/
|
||||||
|
async function fetchForAnalysis() {
|
||||||
|
const tasks: Promise<void>[] = [fetchQuote()]
|
||||||
|
if (!newsLoaded.value) tasks.push(fetchNews())
|
||||||
|
if (!positionsLoaded.value) tasks.push(fetchPositions())
|
||||||
|
await Promise.all(tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深拷贝可序列化数据。Pinia/ref 内是 Proxy,structuredClone 直接克隆会 DataCloneError,
|
||||||
|
* 需先 toRaw 再 JSON 往返,得到脱离响应式的纯对象。
|
||||||
|
*/
|
||||||
|
function clonePlain<T>(value: T): T {
|
||||||
|
return JSON.parse(JSON.stringify(toRaw(value))) as T
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出当前全部接口数据快照,供下一步 AI 分析拼接 Prompt。
|
* 导出当前全部接口数据快照,供下一步 AI 分析拼接 Prompt。
|
||||||
* 返回深拷贝,避免分析过程中被行情刷新污染。
|
* 返回深拷贝,避免分析过程中被行情刷新污染。
|
||||||
@ -192,9 +222,9 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
name: quote.value.name || contractConfig.name,
|
name: quote.value.name || contractConfig.name,
|
||||||
exchange: quote.value.exchange || contractConfig.exchange,
|
exchange: quote.value.exchange || contractConfig.exchange,
|
||||||
},
|
},
|
||||||
quote: quote.value ? structuredClone(quote.value) : null,
|
quote: quote.value ? clonePlain(quote.value) : null,
|
||||||
news: structuredClone(news.value),
|
news: clonePlain(news.value),
|
||||||
positions: positions.value ? structuredClone(positions.value) : null,
|
positions: positions.value ? clonePlain(positions.value) : null,
|
||||||
fetchedAt: new Date().toISOString(),
|
fetchedAt: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,12 +245,16 @@ export const useQuotaStore = defineStore('quota', () => {
|
|||||||
// computed
|
// computed
|
||||||
hasQuote,
|
hasQuote,
|
||||||
hasNews,
|
hasNews,
|
||||||
|
hasPositions,
|
||||||
|
newsLoaded,
|
||||||
|
positionsLoaded,
|
||||||
// actions
|
// actions
|
||||||
fetchQuote,
|
fetchQuote,
|
||||||
loadKline,
|
loadKline,
|
||||||
fetchNews,
|
fetchNews,
|
||||||
fetchPositions,
|
fetchPositions,
|
||||||
fetchAll,
|
fetchAll,
|
||||||
|
fetchForAnalysis,
|
||||||
getAnalysisSnapshot,
|
getAnalysisSnapshot,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -26,6 +26,11 @@ export default defineConfig({
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/jqka/, ''),
|
rewrite: (path) => path.replace(/^\/jqka/, ''),
|
||||||
},
|
},
|
||||||
|
'/deepseek': {
|
||||||
|
target: 'https://api.deepseek.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/deepseek/, ''),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user