From bc480fb8060791afb898acbd776877644b9c5d11 Mon Sep 17 00:00:00 2001 From: eezd Date: Mon, 26 Oct 2026 11:39:52 +0800 Subject: [PATCH] Refactor frontend workspaces and centralize API contracts Organize domain APIs and types under common/apis, shared components under named folders, and private workspace logic alongside each page. Use Bearer sessions, explicit response envelopes, session-aware routing and streams, and configurable login captcha presentation for the deployed backend contract. Preserve news, economic calendar, crawler, market, chat, Skills, settings, and demo trading workflows with responsive layouts and explicit configuration saves. Validate with frontend lint, type checking, 22 test groups, production builds, and deployed desktop/mobile page checks. --- web/eslint.config.js | 4 +- web/package.json | 5 +- web/public/flags/de.svg | 1 + web/public/flags/ea.svg | 1 + web/public/flags/fr.svg | 1 + web/public/flags/gb.svg | 1 + web/public/flags/us.svg | 11 + web/src/App.vue | 5 +- web/src/apis/chat.ts | 52 --- web/src/apis/market.ts | 66 --- web/src/apis/settings.ts | 104 ----- web/src/apis/skills.ts | 59 --- web/src/common/apis/auth/index.ts | 46 +++ web/src/common/apis/auth/type.ts | 37 ++ web/src/common/apis/calendar/index.ts | 37 ++ web/src/common/apis/calendar/type.ts | 73 ++++ web/src/common/apis/chat/index.ts | 59 +++ web/src/common/apis/chat/type.ts | 65 +++ web/src/common/apis/crawler/index.ts | 45 +++ .../apis/crawler/type.ts} | 38 +- web/src/common/apis/market/index.ts | 59 +++ web/src/common/apis/market/type.ts | 86 ++++ web/src/common/apis/news/index.ts | 36 ++ .../news.ts => common/apis/news/type.ts} | 37 +- web/src/common/apis/settings/index.ts | 81 ++++ web/src/common/apis/settings/type.ts | 81 ++++ web/src/common/apis/skills/index.ts | 71 ++++ web/src/common/apis/skills/type.ts | 45 +++ web/src/common/apis/trading/index.ts | 152 +++++++ .../apis/trading/type.ts} | 124 ++---- web/src/{ => common/assets}/styles/index.css | 0 .../components/AppLayout}/index.vue | 23 +- .../components/SkillTree/index.vue} | 21 +- .../components/SkillVersionDrawer/index.vue} | 20 +- web/src/common/composables/useSessionScope.ts | 18 + web/src/common/composables/useSourceNames.ts | 8 + web/src/common/constants/ai.ts | 2 + web/src/{ => common}/utils/markdown.ts | 0 web/src/{ => common}/utils/storage.ts | 0 web/src/{ => common}/utils/text.ts | 0 web/src/composables/useChatStream.ts | 66 --- web/src/composables/useEventSource.ts | 10 - web/src/composables/useSourceNames.ts | 13 - web/src/http/axios.ts | 95 +++++ web/src/http/index.ts | 21 - web/src/http/sse.ts | 54 +++ web/src/http/stream.ts | 45 +++ web/src/http/useEventSource.ts | 55 +++ web/src/main.ts | 17 +- .../calendar/components/CalendarDetail.vue | 196 +++++++++ web/src/pages/calendar/index.vue | 376 ++++++++++++++++++ web/src/pages/chat/components/ContextTab.vue | 18 +- web/src/pages/chat/components/MessageList.vue | 28 +- .../pages/chat/composables/useChatStream.ts | 46 +++ web/src/pages/chat/index.vue | 132 +++--- web/src/pages/chat/message-items.ts | 22 + web/src/pages/chat/type.ts | 22 + .../components/CalendarSourcePanel.vue | 43 ++ .../pages/crawler/components/SourcePanel.vue | 26 +- .../crawler/composables/useCrawlerLogs.ts | 54 +++ web/src/pages/crawler/index.vue | 230 +++++++---- web/src/pages/error/index.vue | 27 ++ web/src/pages/login/index.vue | 101 ++++- .../market/components/AddSymbolDialog.vue | 8 +- .../pages/market/components/SymbolManager.vue | 13 +- .../market/composables/useMarketChart.ts | 196 +++++++++ .../market/composables/useMarketSocket.ts | 95 +++++ web/src/pages/market/index.vue | 194 +-------- web/src/pages/market/useMarketSocket.ts | 83 ---- web/src/pages/news/components/NewsDetail.vue | 23 +- web/src/pages/news/composables/useNewsFeed.ts | 78 ++++ web/src/pages/news/index.vue | 122 ++---- .../settings/components/ExchangeSettings.vue | 162 ++++++++ .../settings/components/ProviderSettings.vue | 256 ++++++++++++ web/src/pages/settings/index.vue | 326 +-------------- web/src/pages/skills/index.vue | 39 +- .../trading/components/AddSymbolDialog.vue | 8 +- .../trading/components/AnalysisDrawer.vue | 10 +- .../trading/components/NewsSignalDrawer.vue | 10 +- web/src/pages/trading/components/PlanCard.vue | 6 +- .../components/PositionHistoryDrawer.vue | 11 +- .../trading/components/TradingConfig.vue | 86 ++++ .../trading/composables/useTradingData.ts | 107 +++++ web/src/pages/trading/index.vue | 218 ++++------ web/src/pages/trading/plan-display.ts | 42 ++ web/src/pinia/stores/sources.ts | 31 ++ web/src/pinia/stores/user.ts | 53 +++ web/src/pinia/user.ts | 22 - web/src/router/guards.ts | 41 ++ web/src/router/index.ts | 33 +- web/src/router/routes.ts | 10 + web/tests/auth.test.ts | 207 ++++++++++ web/tests/login.test.ts | 140 +++++++ web/tests/module-loader.ts | 43 ++ web/tests/page-races.test.ts | 101 +++-- web/tests/streams.test.ts | 190 +++++++++ web/tests/workspaces.test.ts | 174 ++++++++ web/tsconfig.json | 3 +- web/types/{ => auto}/auto-imports.d.ts | 0 web/types/{ => auto}/components.d.ts | 3 +- web/types/router.d.ts | 3 + web/vite.config.ts | 5 +- 102 files changed, 4716 insertions(+), 1707 deletions(-) create mode 100644 web/public/flags/de.svg create mode 100644 web/public/flags/ea.svg create mode 100644 web/public/flags/fr.svg create mode 100644 web/public/flags/gb.svg create mode 100644 web/public/flags/us.svg delete mode 100644 web/src/apis/chat.ts delete mode 100644 web/src/apis/market.ts delete mode 100644 web/src/apis/settings.ts delete mode 100644 web/src/apis/skills.ts create mode 100644 web/src/common/apis/auth/index.ts create mode 100644 web/src/common/apis/auth/type.ts create mode 100644 web/src/common/apis/calendar/index.ts create mode 100644 web/src/common/apis/calendar/type.ts create mode 100644 web/src/common/apis/chat/index.ts create mode 100644 web/src/common/apis/chat/type.ts create mode 100644 web/src/common/apis/crawler/index.ts rename web/src/{apis/crawler.ts => common/apis/crawler/type.ts} (62%) create mode 100644 web/src/common/apis/market/index.ts create mode 100644 web/src/common/apis/market/type.ts create mode 100644 web/src/common/apis/news/index.ts rename web/src/{apis/news.ts => common/apis/news/type.ts} (63%) create mode 100644 web/src/common/apis/settings/index.ts create mode 100644 web/src/common/apis/settings/type.ts create mode 100644 web/src/common/apis/skills/index.ts create mode 100644 web/src/common/apis/skills/type.ts create mode 100644 web/src/common/apis/trading/index.ts rename web/src/{apis/trading.ts => common/apis/trading/type.ts} (64%) rename web/src/{ => common/assets}/styles/index.css (100%) rename web/src/{layouts => common/components/AppLayout}/index.vue (62%) rename web/src/{components/SkillTree.vue => common/components/SkillTree/index.vue} (90%) rename web/src/{components/SkillVersionDrawer.vue => common/components/SkillVersionDrawer/index.vue} (89%) create mode 100644 web/src/common/composables/useSessionScope.ts create mode 100644 web/src/common/composables/useSourceNames.ts create mode 100644 web/src/common/constants/ai.ts rename web/src/{ => common}/utils/markdown.ts (100%) rename web/src/{ => common}/utils/storage.ts (100%) rename web/src/{ => common}/utils/text.ts (100%) delete mode 100644 web/src/composables/useChatStream.ts delete mode 100644 web/src/composables/useEventSource.ts delete mode 100644 web/src/composables/useSourceNames.ts create mode 100644 web/src/http/axios.ts delete mode 100644 web/src/http/index.ts create mode 100644 web/src/http/sse.ts create mode 100644 web/src/http/stream.ts create mode 100644 web/src/http/useEventSource.ts create mode 100644 web/src/pages/calendar/components/CalendarDetail.vue create mode 100644 web/src/pages/calendar/index.vue create mode 100644 web/src/pages/chat/composables/useChatStream.ts create mode 100644 web/src/pages/chat/message-items.ts create mode 100644 web/src/pages/chat/type.ts create mode 100644 web/src/pages/crawler/components/CalendarSourcePanel.vue create mode 100644 web/src/pages/crawler/composables/useCrawlerLogs.ts create mode 100644 web/src/pages/error/index.vue create mode 100644 web/src/pages/market/composables/useMarketChart.ts create mode 100644 web/src/pages/market/composables/useMarketSocket.ts delete mode 100644 web/src/pages/market/useMarketSocket.ts create mode 100644 web/src/pages/news/composables/useNewsFeed.ts create mode 100644 web/src/pages/settings/components/ExchangeSettings.vue create mode 100644 web/src/pages/settings/components/ProviderSettings.vue create mode 100644 web/src/pages/trading/components/TradingConfig.vue create mode 100644 web/src/pages/trading/composables/useTradingData.ts create mode 100644 web/src/pages/trading/plan-display.ts create mode 100644 web/src/pinia/stores/sources.ts create mode 100644 web/src/pinia/stores/user.ts delete mode 100644 web/src/pinia/user.ts create mode 100644 web/src/router/guards.ts create mode 100644 web/src/router/routes.ts create mode 100644 web/tests/auth.test.ts create mode 100644 web/tests/login.test.ts create mode 100644 web/tests/module-loader.ts create mode 100644 web/tests/streams.test.ts create mode 100644 web/tests/workspaces.test.ts rename web/types/{ => auto}/auto-imports.d.ts (100%) rename web/types/{ => auto}/components.d.ts (95%) diff --git a/web/eslint.config.js b/web/eslint.config.js index 0769ba8..49d88b1 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -5,6 +5,8 @@ export default antfu( { // 使用外部格式化程序格式化 css、html、markdown 等文件 formatters: true, + // 使用 Node 内置测试,不引入 Vitest。 + test: { overrides: { "test/no-import-node-test": "off" } }, // 启用样式规则 stylistic: { // 缩进级别 @@ -15,7 +17,7 @@ export default antfu( semi: false }, // 忽略文件 - ignores: ["types/auto-imports.d.ts", "types/components.d.ts"] + ignores: ["types/auto/**"] }, { // 对所有文件都生效的规则 diff --git a/web/package.json b/web/package.json index 58bf2e4..c6d0c45 100644 --- a/web/package.json +++ b/web/package.json @@ -6,7 +6,10 @@ "scripts": { "dev": "vite", "build": "vue-tsc && vite build", - "lint": "eslint . --fix" + "lint": "eslint . --fix", + "lint:check": "eslint .", + "typecheck": "vue-tsc --noEmit", + "test": "node --experimental-strip-types --test tests/*.test.ts" }, "dependencies": { "@element-plus/icons-vue": "^2.3.2", diff --git a/web/public/flags/de.svg b/web/public/flags/de.svg new file mode 100644 index 0000000..3c1eba9 --- /dev/null +++ b/web/public/flags/de.svg @@ -0,0 +1 @@ + diff --git a/web/public/flags/ea.svg b/web/public/flags/ea.svg new file mode 100644 index 0000000..6d0d93e --- /dev/null +++ b/web/public/flags/ea.svg @@ -0,0 +1 @@ + diff --git a/web/public/flags/fr.svg b/web/public/flags/fr.svg new file mode 100644 index 0000000..0a23f80 --- /dev/null +++ b/web/public/flags/fr.svg @@ -0,0 +1 @@ + diff --git a/web/public/flags/gb.svg b/web/public/flags/gb.svg new file mode 100644 index 0000000..be99c96 --- /dev/null +++ b/web/public/flags/gb.svg @@ -0,0 +1 @@ + diff --git a/web/public/flags/us.svg b/web/public/flags/us.svg new file mode 100644 index 0000000..51479da --- /dev/null +++ b/web/public/flags/us.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/web/src/App.vue b/web/src/App.vue index 4d47c67..462719f 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,9 +1,12 @@ diff --git a/web/src/apis/chat.ts b/web/src/apis/chat.ts deleted file mode 100644 index 9576405..0000000 --- a/web/src/apis/chat.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { http } from "@/http" - -export interface ToolCall { - id: string - name: string - arguments: Record -} - -export interface ChatMessage { - id: number - role: "user" | "assistant" | "tool" - /** user: {text};assistant: {text, tool_calls, stopped?, error?};tool: {tool_call_id, name, result} */ - content: Record - provider_id: number | null - usage: { input_tokens?: number, output_tokens?: number } | null - created_at: string -} - -export interface ChatSession { - id: number - created_at: string - messages: ChatMessage[] - estimated_tokens: number - generating: boolean -} - -export interface ChatContext { - id: number | null - content: string - source: "user" | "chat" | "reset" | null - created_at: string | null -} - -export function getChatSession(): Promise { - return http.get("/chat/session") -} - -export function resetChat(clearContext: boolean): Promise { - return http.post("/chat/reset", { clear_context: clearContext }) -} - -export function getChatContext(): Promise { - return http.get("/chat/context") -} - -export function saveChatContext(content: string): Promise { - return http.put("/chat/context", { content }) -} - -export function listChatContextVersions(): Promise { - return http.get("/chat/context/versions") -} diff --git a/web/src/apis/market.ts b/web/src/apis/market.ts deleted file mode 100644 index 776ab45..0000000 --- a/web/src/apis/market.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { http } from "@/http" - -export type Market = "spot" | "um" - -export interface IntervalProgress { - done: boolean - percent: number -} - -export interface MarketSymbol { - id: number - market: Market - symbol: string - base: string - quote: string - price_precision: number - volume_precision: number - status: "pending" | "backfilling" | "ready" | "error" | "delisted" - is_default: boolean - added_at: string - last_error: string | null - progress: Record -} - -export interface TradableSymbol { - symbol: string - base: string - quote: string -} - -/** 币安 24hr ticker 原始字段(只列用到的) */ -export interface Ticker24h { - lastPrice: string - priceChangePercent: string - highPrice: string - lowPrice: string - volume: string - quoteVolume: string -} - -/** [open_time, open, high, low, close, volume, quote_volume] */ -export type KlineRow = [number, number, number, number, number, number, number] - -export function listSymbols(): Promise { - return http.get("/market/symbols") -} - -export function addSymbol(market: Market, symbol: string): Promise { - return http.post("/market/symbols", { market, symbol }) -} - -export function deleteSymbol(id: number): Promise { - return http.delete(`/market/symbols/${id}`) -} - -export function listTradable(market: Market): Promise { - return http.get("/market/exchange-info", { params: { market } }) -} - -export function listKlines(params: { market: Market, symbol: string, interval: string, end?: number, limit?: number }): Promise { - return http.get("/market/klines", { params }) -} - -export function getTicker(market: Market, symbol: string): Promise { - return http.get("/market/ticker", { params: { market, symbol } }) -} diff --git a/web/src/apis/settings.ts b/web/src/apis/settings.ts deleted file mode 100644 index 8e761fe..0000000 --- a/web/src/apis/settings.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { http } from "@/http" - -export type Protocol = "openai" | "anthropic" - -/** 思考强度,与后端 llm.Effort 一致 */ -export const EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const - -export type Effort = typeof EFFORTS[number] - -export interface Provider { - id: number - name: string - protocol: Protocol - base_url: string - api_key_hint: string - model: string - max_tokens: number - context_window: number - /** 为空表示不传,由模型决定 */ - reasoning_effort: Effort | null - is_default: boolean - updated_at: string -} - -export interface ProviderForm { - name: string - protocol: Protocol - base_url: string - /** 编辑时为空表示不修改 */ - api_key: string - model: string - max_tokens: number - context_window: number - /** 为空表示不传,由模型决定 */ - reasoning_effort: Effort | null - is_default: boolean -} - -export interface TestResult { - ok: boolean - elapsed_ms: number - reply: string | null - error: string | null -} - -export function listProviders(): Promise { - return http.get("/settings/providers") -} - -export function createProvider(body: ProviderForm): Promise { - return http.post("/settings/providers", body) -} - -export function patchProvider(id: number, body: Partial): Promise { - return http.patch(`/settings/providers/${id}`, body) -} - -export function deleteProvider(id: number): Promise { - return http.delete(`/settings/providers/${id}`) -} - -export function testProvider(id: number): Promise { - // 模型接口读超时 600s 且 SDK 会重试 2 次,前端放宽等待时间 - return http.post(`/settings/providers/${id}/test`, null, { timeout: 1900000 }) -} - -export type ExchangeName = "binance" | "bybit" - -export interface Exchange { - exchange: ExchangeName - configured_exchanges: ExchangeName[] - configured: boolean - api_key_hint: string | null - /** 是否有币种在交易:交易中不能修改 key */ - running: boolean - updated_at: string | null -} - -export interface ExchangeTestResult { - ok: boolean - elapsed_ms: number - available_usdt: string | null - wallet_usdt: string | null - /** 双向持仓模式;引擎使用单向持仓,启动时会切换 */ - dual_side_position: boolean | null - error: string | null -} - -export function getExchange(): Promise { - return http.get("/settings/exchange") -} - -export function putExchange(body: { exchange?: ExchangeName, api_key?: string, api_secret?: string }): Promise { - return http.put("/settings/exchange", body) -} - -export function deleteExchange(): Promise { - return http.delete("/settings/exchange") -} - -export function testExchange(): Promise { - // 后端串行请求两个接口,单次超时 10s - return http.post("/settings/exchange/test", null, { timeout: 30000 }) -} diff --git a/web/src/apis/skills.ts b/web/src/apis/skills.ts deleted file mode 100644 index 64e2bc1..0000000 --- a/web/src/apis/skills.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { http } from "@/http" - -export type NodeKind = "folder" | "skill" - -export interface SkillNode { - id: number - parent_id: number | null - kind: NodeKind - name: string - path: string - sort_order: number - priority: number - enabled: boolean - published_version_no: number | null - latest_version_no: number | null - has_draft: boolean - updated_at: string -} - -export interface SkillVersion { - id: number - version_no: number - content: string - source: "user" | "chat" - note: string - created_at: string -} - -export function listSkillTree(): Promise { - return http.get("/skills/tree") -} - -export function createSkillNode(body: { parent_id: number | null, kind: NodeKind, name: string, priority?: number, content?: string }): Promise { - return http.post("/skills/nodes", body) -} - -export function patchSkillNode(id: number, body: { name?: string, priority?: number, enabled?: boolean }): Promise { - return http.patch(`/skills/nodes/${id}`, body) -} - -export function moveSkillNode(id: number, parentId: number | null, index: number): Promise { - return http.post(`/skills/nodes/${id}/move`, { parent_id: parentId, index }) -} - -export function deleteSkillNode(id: number): Promise { - return http.delete(`/skills/nodes/${id}`) -} - -export function listSkillVersions(id: number): Promise { - return http.get(`/skills/${id}/versions`) -} - -export function saveSkillDraft(id: number, content: string, note = ""): Promise { - return http.post(`/skills/${id}/versions`, { content, note }) -} - -export function publishSkillVersion(id: number, versionId: number): Promise { - return http.post(`/skills/${id}/publish`, { version_id: versionId }) -} diff --git a/web/src/common/apis/auth/index.ts b/web/src/common/apis/auth/index.ts new file mode 100644 index 0000000..d9238c7 --- /dev/null +++ b/web/src/common/apis/auth/index.ts @@ -0,0 +1,46 @@ +import type * as Auth from "./type" +import { request } from "@/http/axios" + +/** 获取当前身份 */ +export function getIdentityApi() { + return request({ + url: "/auth/me", + method: "get" + }) +} + +/** 撤销当前会话 */ +export function revokeSessionApi() { + return request({ + url: "/auth/logout", + method: "post" + }) +} + +/** 获取登录验证码模式及挑战 */ +export function getCaptchaApi() { + return request({ + url: "/auth/captcha", + method: "get", + anonymous: true, + notify: false + }) +} + +/** 提交登录凭据 */ +export function loginApi(body: Auth.LoginForm) { + return request({ + url: "/auth/login", + method: "post", + data: body, + anonymous: true + }) +} + +/** 一次性短票据,只用于行情 WebSocket 握手。 */ +export function createWebSocketTicketApi() { + return request({ + url: "/auth/ws-ticket", + method: "post" + }) +} diff --git a/web/src/common/apis/auth/type.ts b/web/src/common/apis/auth/type.ts new file mode 100644 index 0000000..8b369b0 --- /dev/null +++ b/web/src/common/apis/auth/type.ts @@ -0,0 +1,37 @@ +export interface Identity { + username: string + is_admin: boolean +} + +export type GetIdentityResponseData = Identity + +export type RevokeSessionResponseData = null + +export type Captcha = { enabled: false } | { + enabled: true + captcha_id: string + image: string + expires_in: number +} + +export interface LoginForm { + username: string + password: string + captcha_id?: string + code?: string +} + +export interface LoginResult { + token: string + token_type: "bearer" + expires_at: string +} + +export type GetCaptchaResponseData = Captcha + +export type LoginResponseData = LoginResult + +export interface WebSocketTicket { + ticket: string + expires_in: number +} diff --git a/web/src/common/apis/calendar/index.ts b/web/src/common/apis/calendar/index.ts new file mode 100644 index 0000000..6124f09 --- /dev/null +++ b/web/src/common/apis/calendar/index.ts @@ -0,0 +1,37 @@ +import type * as Calendar from "./type" +import { request } from "@/http/axios" + +/** 按日期范围查询经济日历 */ +export function listCalendarApi(start: string, days: number) { + return request({ + url: "/calendar", + method: "get", + params: { start, days } + }) +} + +/** 读取本期事件修订记录 */ +export function calendarRevisionsApi(id: number) { + return request({ + url: `/calendar/${id}/revisions`, + method: "get" + }) +} + +/** 读取同指标历次公布值 */ +export function calendarHistoryApi(id: number, indicator: string) { + return request({ + url: `/calendar/${id}/history`, + method: "get", + params: { indicator } + }) +} + +/** 读取当前官方历史序列 */ +export function calendarSeriesApi(id: number, indicator: string) { + return request({ + url: `/calendar/${id}/series`, + method: "get", + params: { indicator } + }) +} diff --git a/web/src/common/apis/calendar/type.ts b/web/src/common/apis/calendar/type.ts new file mode 100644 index 0000000..a2df55a --- /dev/null +++ b/web/src/common/apis/calendar/type.ts @@ -0,0 +1,73 @@ +export interface CalendarItem { + release_id: number + indicator: string + name: string + country_code: string + country_name: string + event_name: string + title: string + period: string | null + stage: string | null + source: string + unit: string + importance: number + scheduled_at: string + status: "scheduled" | "waiting" | "delayed" | "released" | "withdrawn" | "error" | "historical" | "unavailable" + actual: string | null + previous: string | null + forecast: string | null + previous_basis: string | null + impact: "pending" | "bullish" | "bearish" | "neutral" | "uncertain" + impact_basis: "forecast" | "previous" | null + explanation: string + release_url: string | null + schedule_url: string + first_received_at: string | null + last_checked_at: string | null + error: string | null + revision_no: number +} + +export interface CalendarData { + items: CalendarItem[] + countries: { code: string, name: string }[] + worker: { at: string, sources: Record } | null + server_time: string +} + +export interface CalendarRevision { + revision_no: number + captured_at: string + payload: { title: string, scheduled_at: string, schedule_status: string, values: Record } +} + +export interface CalendarHistory { + release_id: number + scheduled_at: string + title: string + period: string | null + stage: string | null + actual: string + previous: string | null + forecast: string | null + first_recorded_actual: string | null + first_received_at: string + release_url: string | null + revised: boolean +} + +export type ListCalendarResponseData = CalendarData + +export type CalendarRevisionsResponseData = CalendarRevision[] + +export type CalendarHistoryResponseData = CalendarHistory[] + +export interface CalendarSeriesValue { + period: string + stage: string + value: string + captured_at: string + source_url: string +} + +export type CalendarSeriesResponseData = CalendarSeriesValue[] diff --git a/web/src/common/apis/chat/index.ts b/web/src/common/apis/chat/index.ts new file mode 100644 index 0000000..65451e8 --- /dev/null +++ b/web/src/common/apis/chat/index.ts @@ -0,0 +1,59 @@ +import type * as Chat from "./type" +import { request } from "@/http/axios" +import { consumeStream } from "@/http/stream" + +/** 读取当前对话及生成状态 */ +export function getChatSessionApi() { + return request({ + url: "/chat/session", + method: "get" + }) +} + +/** 重置当前对话,可按选择清空上下文 */ +export function resetChatApi(clearContext: boolean) { + return request({ + url: "/chat/reset", + method: "post", + data: { clear_context: clearContext } + }) +} + +/** 读取 Chat 上下文 */ +export function getChatContextApi() { + return request({ + url: "/chat/context", + method: "get" + }) +} + +/** 保存 Chat 上下文版本 */ +export function saveChatContextApi(content: string) { + return request({ + url: "/chat/context", + method: "put", + data: { content } + }) +} + +/** 读取 Chat 上下文历史 */ +export function listChatContextVersionsApi() { + return request({ + url: "/chat/context/versions", + method: "get" + }) +} + +/** POST 只发送一次;这里声明 SSE 事件协议,生命周期由 Chat 管理。 */ +export function sendChatApi(body: Chat.SendChatRequestData, signal: AbortSignal, version: number, onEvent: Chat.ChatStreamHandler) { + return consumeStream("/api/chat/messages", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + signal + }, version, () => {}, (name, data) => { + // SSE 将事件名与 JSON 分开发送;在协议边界恢复同一事件的关联类型。 + const event = { name, data } as Chat.ChatStreamEvent + onEvent(event) + }) +} diff --git a/web/src/common/apis/chat/type.ts b/web/src/common/apis/chat/type.ts new file mode 100644 index 0000000..7f15b82 --- /dev/null +++ b/web/src/common/apis/chat/type.ts @@ -0,0 +1,65 @@ +import type { Effort } from "../settings/type" + +export interface ToolCall { + id: string + name: string + arguments: Record +} + +interface MessageMetadata { + id: number + provider_id: number | null + usage: { input_tokens?: number, output_tokens?: number } | null + created_at: string +} + +export type ChatMessage = MessageMetadata & ( + | { role: "user", content: { text: string } } + | { role: "assistant", content: { text: string, tool_calls: ToolCall[], stopped?: boolean, error?: string, thinking_blocks?: unknown[] } } + | { role: "tool", content: { tool_call_id: string, name: string, result: string } } +) + +export interface ChatSession { + id: number + created_at: string + messages: ChatMessage[] + estimated_tokens: number + generating: boolean +} + +export interface ChatContext { + id: number | null + content: string + source: "user" | "chat" | "reset" | null + created_at: string | null +} + +export type GetChatSessionResponseData = ChatSession + +export type ResetChatResponseData = null + +export type GetChatContextResponseData = ChatContext + +export type SaveChatContextResponseData = ChatContext + +export type ListChatContextVersionsResponseData = ChatContext[] + +export interface SendChatRequestData { + text: string + provider_id: number + reasoning_effort: Effort | null +} + +export type ChatStreamEvent + = | { name: "prompt", data: { estimated_tokens: number, context_window: number, provider: string } } + | { name: "thinking", data: Record } + | { name: "done", data: Record } + | { name: "context_updated", data: { id: number } } + | { name: "delta", data: { text: string } } + | { name: "tool_call", data: ToolCall } + | { name: "tool_result", data: { id: string, name: string, preview: string } } + | { name: "skill_draft", data: { skill_id: number, path: string, version: number } } + | { name: "usage", data: { input_tokens: number, output_tokens: number } } + | { name: "error", data: { message: string } } + +export type ChatStreamHandler = (event: ChatStreamEvent) => void diff --git a/web/src/common/apis/crawler/index.ts b/web/src/common/apis/crawler/index.ts new file mode 100644 index 0000000..2deeefc --- /dev/null +++ b/web/src/common/apis/crawler/index.ts @@ -0,0 +1,45 @@ +import type * as Crawler from "./type" +import { request } from "@/http/axios" +import { consumeStream } from "@/http/stream" + +/** 读取新闻与日历来源状态 */ +export function listSourcesApi() { + return request({ + url: "/crawler/sources", + method: "get" + }) +} + +/** 修改新闻来源设置 */ +export function patchSourceApi(code: string, body: Crawler.SourcePatch) { + return request({ + url: `/crawler/sources/${code}`, + method: "patch", + data: body + }) +} + +/** 读取来源历史日志 */ +export function listLogsApi(source: string, limit = 200) { + return request({ + url: "/crawler/logs", + method: "get", + params: { source, limit } + }) +} + +/** 请求重启新闻采集 */ +export function restartCrawlerApi(source: string) { + return request({ + url: "/crawler/restart", + method: "post", + data: { source } + }) +} + +/** 只读订阅,重连由订阅生命周期负责。 */ +export function subscribeCrawlerApi(signal: AbortSignal, version: number, onOpen: () => void, onMessage: (data: Crawler.CrawlLog) => void) { + return consumeStream("/api/crawler/logs/stream", { signal }, version, onOpen, (name, data) => { + if (name === "log") onMessage(data) + }) +} diff --git a/web/src/apis/crawler.ts b/web/src/common/apis/crawler/type.ts similarity index 62% rename from web/src/apis/crawler.ts rename to web/src/common/apis/crawler/type.ts index 36a4bbd..095d772 100644 --- a/web/src/apis/crawler.ts +++ b/web/src/common/apis/crawler/type.ts @@ -1,5 +1,3 @@ -import { http } from "@/http" - export interface ProcessStatus { state: "running" | "stopped" | "restarting" pid: number | null @@ -10,6 +8,7 @@ export interface ProcessStatus { export interface SourceInfo { code: string name: string + region_code: string | null list_url: string enabled: boolean interval_seconds: number @@ -32,8 +31,27 @@ export interface SourcesInfo { manager_alive: boolean db_size_bytes: number sources: SourceInfo[] + calendar_sources: CalendarSourceInfo[] } +export interface CalendarSourceInfo { + code: string + name: string + region_code: string + worker_alive: boolean + heartbeat_at: string | null + schedule_checked_at: string | null + schedule_success_at: string | null + latest_result_at: string | null + next_event_at: string | null + overdue_count: number + history_start: string | null + history_end: string | null + errors: string[] +} + +export type ListSourcesResponseData = SourcesInfo + export interface CrawlLog { id: number source_code: string @@ -43,10 +61,6 @@ export interface CrawlLog { message: string } -export function listSources(): Promise { - return http.get("/crawler/sources") -} - export interface SourcePatch { enabled?: boolean interval_seconds?: number @@ -54,14 +68,8 @@ export interface SourcePatch { history_target?: string | null } -export function patchSource(code: string, body: SourcePatch): Promise { - return http.patch(`/crawler/sources/${code}`, body) -} +export type PatchSourceResponseData = null -export function listLogs(source: string, limit = 200): Promise { - return http.get("/crawler/logs", { params: { source, limit } }) -} +export type ListLogsResponseData = CrawlLog[] -export function restartCrawler(source: string): Promise { - return http.post("/crawler/restart", { source }) -} +export type RestartCrawlerResponseData = null diff --git a/web/src/common/apis/market/index.ts b/web/src/common/apis/market/index.ts new file mode 100644 index 0000000..6b6b713 --- /dev/null +++ b/web/src/common/apis/market/index.ts @@ -0,0 +1,59 @@ +import type * as Market from "./type" +import { request } from "@/http/axios" + +/** 读取研究行情交易对 */ +export function listSymbolsApi() { + return request({ + url: "/market/symbols", + method: "get" + }) +} + +/** 新增研究行情交易对 */ +export function addSymbolApi(market: Market.Market, symbol: string) { + return request({ + url: "/market/symbols", + method: "post", + data: { market, symbol } + }) +} + +/** 移出研究行情交易对 */ +export function deleteSymbolApi(id: number) { + return request({ + url: `/market/symbols/${id}`, + method: "delete" + }) +} + +/** 查询交易所可用交易对 */ +export function listTradableApi(market: Market.Market) { + return request({ + url: "/market/exchange-info", + method: "get", + params: { market } + }) +} + +/** 读取币安原生 K 线 */ +export function listKlinesApi(params: Market.ListKlinesParamsRequestData) { + return request({ + url: "/market/klines", + method: "get", + params + }) +} + +/** 读取币安 24 小时行情 */ +export function getTickerApi(market: Market.Market, symbol: string) { + return request({ + url: "/market/ticker", + method: "get", + params: { market, symbol } + }) +} + +/** 使用一次性票据连接行情 WebSocket */ +export function createMarketSocketApi(ticket: string) { + return new WebSocket(`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/api/market/ws`, ["alphapulse.market.v1", `ticket.${ticket}`]) +} diff --git a/web/src/common/apis/market/type.ts b/web/src/common/apis/market/type.ts new file mode 100644 index 0000000..65a1f1a --- /dev/null +++ b/web/src/common/apis/market/type.ts @@ -0,0 +1,86 @@ +export type Market = "spot" | "um" + +export interface IntervalProgress { + done: boolean + percent: number +} + +export interface MarketSymbol { + id: number + market: Market + symbol: string + base: string + quote: string + price_precision: number + volume_precision: number + status: "pending" | "backfilling" | "ready" | "error" | "delisted" + is_default: boolean + added_at: string + last_error: string | null + progress: Record +} + +export interface TradableSymbol { + symbol: string + base: string + quote: string +} + +/** 币安 24hr ticker 原始字段(只列用到的) */ +export interface Ticker24h { + lastPrice: string + priceChangePercent: string + highPrice: string + lowPrice: string + volume: string + quoteVolume: string +} + +/** [open_time, open, high, low, close, volume, quote_volume] */ +export type KlineRow = [number, number, number, number, number, number, number] + +export type ListSymbolsResponseData = MarketSymbol[] + +export type AddSymbolResponseData = null + +export type DeleteSymbolResponseData = null + +export type ListTradableResponseData = TradableSymbol[] + +export type ListKlinesResponseData = KlineRow[] + +export interface ListKlinesParamsRequestData { market: Market, symbol: string, interval: string, end?: number, limit?: number } + +export type GetTickerResponseData = Ticker24h + +export interface KlineMessage { + t: number + o: string + h: string + l: string + c: string + v: string + q: string + x: boolean +} + +export interface TickerMessage { + c: string + o: string + h: string + l: string + v: string + q: string + E: number +} + +export interface Subscription { + market: Market + symbol: string + interval: string +} + +export type MarketMessage + = | { type: "kline", market: Market, symbol: string, interval: string, data: KlineMessage } + | { type: "ticker", market: Market, symbol: string, interval: null, data: TickerMessage } + | { type: "error", message: string } diff --git a/web/src/common/apis/news/index.ts b/web/src/common/apis/news/index.ts new file mode 100644 index 0000000..ea73c40 --- /dev/null +++ b/web/src/common/apis/news/index.ts @@ -0,0 +1,36 @@ +import type * as News from "./type" +import { request } from "@/http/axios" +import { consumeStream } from "@/http/stream" + +/** 分页查询新闻 */ +export function listNewsApi(params: News.NewsQuery) { + return request({ + url: "/news", + method: "get", + params, + paramsSerializer: { indexes: null } + }) +} + +/** 读取新闻详情 */ +export function getNewsApi(id: number) { + return request({ + url: `/news/${id}`, + method: "get" + }) +} + +/** 读取新闻修订记录 */ +export function listRevisionsApi(id: number) { + return request({ + url: `/news/${id}/revisions`, + method: "get" + }) +} + +/** 只读订阅,重连由订阅生命周期负责。 */ +export function subscribeNewsApi(signal: AbortSignal, version: number, onOpen: () => void, onMessage: (data: News.NewsEvent) => void) { + return consumeStream("/api/news/stream", { signal }, version, onOpen, (name, data) => { + if (name === "news") onMessage(data) + }) +} diff --git a/web/src/apis/news.ts b/web/src/common/apis/news/type.ts similarity index 63% rename from web/src/apis/news.ts rename to web/src/common/apis/news/type.ts index ea82f02..22dfb93 100644 --- a/web/src/apis/news.ts +++ b/web/src/common/apis/news/type.ts @@ -1,5 +1,3 @@ -import { http } from "@/http" - export interface NewsBrief { id: number source_code: string @@ -15,6 +13,18 @@ export interface NewsBrief { revision_no: number } +export interface NewsQuery { + source?: string[] + q?: string + start?: string + end?: string + /** 从 1 开始 */ + page?: number + limit?: number +} + +export interface ListNewsResponseData { items: NewsBrief[], total: number } + export interface NewsDetail extends NewsBrief { content_html: string source_updated_at: string | null @@ -30,31 +40,12 @@ export interface Revision { captured_at: string } -export interface NewsQuery { - source?: string[] - q?: string - start?: string - end?: string - /** 从 1 开始 */ - page?: number - limit?: number -} - export interface NewsEvent { id: number source_code: string kind: "new" | "revised" } -export function listNews(params: NewsQuery): Promise<{ items: NewsBrief[], total: number }> { - // source 需要序列化为重复参数 source=a&source=b - return http.get("/news", { params, paramsSerializer: { indexes: null } }) -} +export type GetNewsResponseData = NewsDetail -export function getNews(id: number): Promise { - return http.get(`/news/${id}`) -} - -export function listRevisions(id: number): Promise { - return http.get(`/news/${id}/revisions`) -} +export type ListRevisionsResponseData = Revision[] diff --git a/web/src/common/apis/settings/index.ts b/web/src/common/apis/settings/index.ts new file mode 100644 index 0000000..dee76c2 --- /dev/null +++ b/web/src/common/apis/settings/index.ts @@ -0,0 +1,81 @@ +import type * as Settings from "./type" +import { request } from "@/http/axios" + +/** 读取模型配置(不返回密钥) */ +export function listProvidersApi() { + return request({ + url: "/settings/providers", + method: "get" + }) +} + +/** 新增模型配置 */ +export function createProviderApi(body: Settings.ProviderForm) { + return request({ + url: "/settings/providers", + method: "post", + data: body + }) +} + +/** 修改模型配置;空密钥保持原值 */ +export function patchProviderApi(id: number, body: Partial) { + return request({ + url: `/settings/providers/${id}`, + method: "patch", + data: body + }) +} + +/** 删除模型配置 */ +export function deleteProviderApi(id: number) { + return request({ + url: `/settings/providers/${id}`, + method: "delete" + }) +} + +/** 测试模型连接;按后端 SDK 重试上限等待 */ +export function testProviderApi(id: number) { + return request({ + url: `/settings/providers/${id}/test`, + method: "post", + data: null, + timeout: 1900000 + }) +} + +/** 读取当前模拟盘交易所设置 */ +export function getExchangeApi() { + return request({ + url: "/settings/exchange", + method: "get" + }) +} + +/** 切换交易所或保存其凭证 */ +export function putExchangeApi(body: Settings.PutExchangeBodyRequestData) { + return request({ + url: "/settings/exchange", + method: "put", + data: body + }) +} + +/** 删除当前交易所凭证 */ +export function deleteExchangeApi() { + return request({ + url: "/settings/exchange", + method: "delete" + }) +} + +/** 测试当前模拟盘连接 */ +export function testExchangeApi() { + return request({ + url: "/settings/exchange/test", + method: "post", + data: null, + timeout: 30000 + }) +} diff --git a/web/src/common/apis/settings/type.ts b/web/src/common/apis/settings/type.ts new file mode 100644 index 0000000..87a08f1 --- /dev/null +++ b/web/src/common/apis/settings/type.ts @@ -0,0 +1,81 @@ +export type Protocol = "openai" | "anthropic" + +export type Effort = "low" | "medium" | "high" | "xhigh" | "max" + +export interface Provider { + id: number + name: string + protocol: Protocol + base_url: string + api_key_hint: string + model: string + max_tokens: number + context_window: number + /** 为空表示不传,由模型决定 */ + reasoning_effort: Effort | null + is_default: boolean + updated_at: string +} + +export type ListProvidersResponseData = Provider[] + +export interface ProviderForm { + name: string + protocol: Protocol + base_url: string + /** 编辑时为空表示不修改 */ + api_key: string + model: string + max_tokens: number + context_window: number + /** 为空表示不传,由模型决定 */ + reasoning_effort: Effort | null + is_default: boolean +} + +export interface TestResult { + ok: boolean + elapsed_ms: number + reply: string | null + error: string | null +} + +export type CreateProviderResponseData = Provider + +export type PatchProviderResponseData = Provider + +export type DeleteProviderResponseData = null + +export type TestProviderResponseData = TestResult + +export type ExchangeName = "binance" | "bybit" + +export interface Exchange { + exchange: ExchangeName + configured_exchanges: ExchangeName[] + configured: boolean + api_key_hint: string | null + /** 是否有币种在交易:交易中不能修改 key */ + running: boolean + updated_at: string | null +} + +export interface ExchangeTestResult { + ok: boolean + elapsed_ms: number + available_usdt: string | null + wallet_usdt: string | null + /** 双向持仓模式;引擎使用单向持仓,启动时会切换 */ + dual_side_position: boolean | null + error: string | null +} + +export type GetExchangeResponseData = Exchange + +export type PutExchangeResponseData = Exchange + +export interface PutExchangeBodyRequestData { exchange?: ExchangeName, api_key?: string, api_secret?: string } + +export type DeleteExchangeResponseData = Exchange + +export type TestExchangeResponseData = ExchangeTestResult diff --git a/web/src/common/apis/skills/index.ts b/web/src/common/apis/skills/index.ts new file mode 100644 index 0000000..0142538 --- /dev/null +++ b/web/src/common/apis/skills/index.ts @@ -0,0 +1,71 @@ +import type * as Skills from "./type" +import { request } from "@/http/axios" + +/** 读取 Skills 树 */ +export function listSkillTreeApi() { + return request({ + url: "/skills/tree", + method: "get" + }) +} + +/** 新增文件夹或 Skill */ +export function createSkillNodeApi(body: Skills.CreateSkillNodeBodyRequestData) { + return request({ + url: "/skills/nodes", + method: "post", + data: body + }) +} + +/** 修改 Skill 元信息 */ +export function patchSkillNodeApi(id: number, body: Skills.PatchSkillNodeBodyRequestData) { + return request({ + url: `/skills/nodes/${id}`, + method: "patch", + data: body + }) +} + +/** 移动 Skill 节点 */ +export function moveSkillNodeApi(id: number, parentId: number | null, index: number) { + return request({ + url: `/skills/nodes/${id}/move`, + method: "post", + data: { parent_id: parentId, index } + }) +} + +/** 删除 Skill 节点 */ +export function deleteSkillNodeApi(id: number) { + return request({ + url: `/skills/nodes/${id}`, + method: "delete" + }) +} + +/** 读取 Skill 历史版本 */ +export function listSkillVersionsApi(id: number) { + return request({ + url: `/skills/${id}/versions`, + method: "get" + }) +} + +/** 发布指定 Skill 版本 */ +export function publishSkillVersionApi(id: number, versionId: number) { + return request({ + url: `/skills/${id}/publish`, + method: "post", + data: { version_id: versionId } + }) +} + +/** 保存 Skill 草稿,不自动发布 */ +export function saveSkillDraftApi(id: number, content: string, note = "") { + return request({ + url: `/skills/${id}/versions`, + method: "post", + data: { content, note } + }) +} diff --git a/web/src/common/apis/skills/type.ts b/web/src/common/apis/skills/type.ts new file mode 100644 index 0000000..2512c0f --- /dev/null +++ b/web/src/common/apis/skills/type.ts @@ -0,0 +1,45 @@ +export type NodeKind = "folder" | "skill" + +export interface SkillNode { + id: number + parent_id: number | null + kind: NodeKind + name: string + path: string + sort_order: number + priority: number + enabled: boolean + published_version_no: number | null + latest_version_no: number | null + has_draft: boolean + updated_at: string +} + +export interface SkillVersion { + id: number + version_no: number + content: string + source: "user" | "chat" + note: string + created_at: string +} + +export type ListSkillTreeResponseData = SkillNode[] + +export type CreateSkillNodeResponseData = SkillNode + +export interface CreateSkillNodeBodyRequestData { parent_id: number | null, kind: NodeKind, name: string, priority?: number, content?: string } + +export type PatchSkillNodeResponseData = SkillNode + +export interface PatchSkillNodeBodyRequestData { name?: string, priority?: number, enabled?: boolean } + +export type MoveSkillNodeResponseData = null + +export type DeleteSkillNodeResponseData = null + +export type ListSkillVersionsResponseData = SkillVersion[] + +export type PublishSkillVersionResponseData = SkillNode + +export type SaveSkillDraftResponseData = SkillVersion diff --git a/web/src/common/apis/trading/index.ts b/web/src/common/apis/trading/index.ts new file mode 100644 index 0000000..d700f8a --- /dev/null +++ b/web/src/common/apis/trading/index.ts @@ -0,0 +1,152 @@ +import type * as Trading from "./type" +import { request } from "@/http/axios" + +/** 读取模拟盘账户与全局分析状态 */ +export function getStatusApi() { + return request({ + url: "/trading/status", + method: "get" + }) +} + +/** 开始指定币种的自动交易 */ +export function startTradingApi(symbol: string) { + return request({ + url: `/trading/symbols/${symbol}/start`, + method: "post" + }) +} + +/** 停止指定币种的新分析并撤销入场单 */ +export function stopTradingApi(symbol: string) { + return request({ + url: `/trading/symbols/${symbol}/stop`, + method: "post" + }) +} + +/** 设置该币的交易资金:各币合计不超过账户权益 */ +export function patchSymbolApi(symbol: string, body: Trading.PatchSymbolBodyRequestData) { + return request({ + url: `/trading/symbols/${symbol}`, + method: "patch", + data: body + }) +} + +/** 更新指定的全局交易配置字段 */ +export function patchConfigApi(body: Trading.PatchConfigBodyRequestData) { + return request({ + url: "/trading/config", + method: "patch", + data: body + }) +} + +/** 查询模拟盘可交易币种 */ +export function listSymbolOptionsApi() { + return request({ + url: "/trading/symbol-options", + method: "get" + }) +} + +/** 读取交易币种列表 */ +export function listSymbolsApi() { + return request({ + url: "/trading/symbols", + method: "get" + }) +} + +/** 新增交易币种 */ +export function addSymbolApi(symbol: string) { + return request({ + url: "/trading/symbols", + method: "post", + data: { symbol } + }) +} + +/** 移出交易币种,保留历史 */ +export function deleteSymbolApi(symbol: string) { + return request({ + url: `/trading/symbols/${symbol}`, + method: "delete" + }) +} + +/** 分页读取交易时间线 */ +export function listEventsApi(params: Trading.ListEventsParamsRequestData) { + return request({ + url: "/trading/events", + method: "get", + params + }) +} + +/** 筛选已平仓历史及汇总 */ +export function listPositionHistoryApi(params: Trading.PositionHistoryQuery) { + return request({ + url: "/trading/positions/history", + method: "get", + params + }) +} + +/** 读取历史仓位币种 */ +export function listPositionHistorySymbolsApi() { + return request({ + url: "/trading/positions/history/symbols", + method: "get" + }) +} + +/** 读取新闻信号前向统计 */ +export function getNewsSignalsApi() { + return request({ + url: "/trading/news-signals", + method: "get" + }) +} + +/** 读取当前执行计划与订单流水 */ +export function getSymbolPlanApi(symbol: string) { + return request({ + url: `/trading/symbols/${symbol}/plan`, + method: "get" + }) +} + +/** 排队手动分析;202 仅表示已受理 */ +export function requestAnalysisApi(symbol: string) { + return request({ + url: `/trading/symbols/${symbol}/analyze`, + method: "post" + }) +} + +/** 一键平仓(同时停止该币的行情交易)、清除挂单:由执行器在几秒内执行,结果写入时间线 */ +export function manualActionApi(symbol: string, action: "close" | "cancel") { + return request({ + url: `/trading/symbols/${symbol}/manual/${action}`, + method: "post" + }) +} + +/** 读取分析记录列表 */ +export function listAnalysesApi(params: Trading.ListAnalysesParamsRequestData) { + return request({ + url: "/trading/analyses", + method: "get", + params + }) +} + +/** 读取分析详情与工具调用过程 */ +export function getAnalysisApi(id: number) { + return request({ + url: `/trading/analyses/${id}`, + method: "get" + }) +} diff --git a/web/src/apis/trading.ts b/web/src/common/apis/trading/type.ts similarity index 64% rename from web/src/apis/trading.ts rename to web/src/common/apis/trading/type.ts index f995e65..a846792 100644 --- a/web/src/apis/trading.ts +++ b/web/src/common/apis/trading/type.ts @@ -1,5 +1,4 @@ -import type { Effort } from "@/apis/settings" -import { http } from "@/http" +import type { Effort } from "../settings/type" /** 以下数值字段都是交易所返回的十进制字符串,避免浮点误差 */ export interface SnapshotPosition { @@ -97,51 +96,31 @@ export interface TradingEvent { data: Record | null } -/** 本程序下的单;其他是用户手动下的 */ -export function isOwnOrder(clientId: string) { - return /^ap\d+$/.test(clientId) -} +export type GetStatusResponseData = TradingStatus -export function getStatus(): Promise { - return http.get("/trading/status") -} +export type StartTradingResponseData = null -export function startTrading(symbol: string): Promise { - return http.post(`/trading/symbols/${symbol}/start`) -} +export type StopTradingResponseData = null -export function stopTrading(symbol: string): Promise { - return http.post(`/trading/symbols/${symbol}/stop`) -} +export type PatchSymbolResponseData = null -/** 设置该币的交易资金:各币合计不超过账户权益 */ -export function patchSymbol(symbol: string, body: { capital_usdt: string | null }): Promise { - return http.patch(`/trading/symbols/${symbol}`, body) -} +export interface PatchSymbolBodyRequestData { capital_usdt: string | null } -export function patchConfig(body: { provider_id?: number | null, reasoning_effort?: Effort | null, daily_analysis_limit?: number }): Promise { - return http.patch("/trading/config", body) -} +export type PatchConfigResponseData = TradingStatus -export function listSymbolOptions(): Promise { - return http.get("/trading/symbol-options") -} +export interface PatchConfigBodyRequestData { provider_id?: number | null, reasoning_effort?: Effort | null, daily_analysis_limit?: number } -export function listSymbols(): Promise { - return http.get("/trading/symbols") -} +export type ListSymbolOptionsResponseData = SymbolOption[] -export function addSymbol(symbol: string): Promise { - return http.post("/trading/symbols", { symbol }) -} +export type ListSymbolsResponseData = TradingSymbol[] -export function deleteSymbol(symbol: string): Promise { - return http.delete(`/trading/symbols/${symbol}`) -} +export type AddSymbolResponseData = null -export function listEvents(params: { symbol?: string, before?: number, limit?: number }): Promise { - return http.get("/trading/events", { params }) -} +export type DeleteSymbolResponseData = null + +export type ListEventsResponseData = TradingEvent[] + +export interface ListEventsParamsRequestData { symbol?: string, before?: number, limit?: number } export interface PlanCondition { direction: "above" | "below" @@ -224,18 +203,6 @@ export interface AnalysisDetail extends Analysis { export type PlanStatus = "proposed" | "pending" | "open" | "closing" | "closed" | "canceled" | "replaced" | "stale" | "applied" -export const PLAN_STATUS_LABELS: Record = { - proposed: "待采纳", - pending: "挂单中", - open: "持仓中", - closing: "平仓中", - closed: "已结束", - canceled: "已撤销", - replaced: "已被替换", - stale: "已作废", - applied: "已执行" -} - /** 执行器的订单流水 */ export interface OrderRow { id: number @@ -253,30 +220,6 @@ export interface OrderRow { created_at: string } -export const CLOSE_REASON_LABELS: Record = { - stop: "止损", - take_profit: "止盈", - time: "到达持仓时限", - exit: "模型决定结束", - external: "外部平仓", - stop_failed: "止损挂单失败", - close: "市价平仓", - manual_close: "一键平仓", - manual_cancel: "清除挂单", - // 计划 3.1 之前的旧原因,历史仓位里还会出现 - invalidation: "短线失效条件触发", - replaced: "被新计划替换", - expired: "限价单到期未成交", - stopped: "已停止交易", - entry_failed: "入场单被拒" -} - -/** 止盈阶梯(与后端 plan.TAKE_PROFIT_STEPS 一致):[距成交均价的涨跌幅, 平仓比例] */ -export const TAKE_PROFIT_STEPS: Record<"long" | "short", [number, number][]> = { - long: [[0.005, 0.6], [0.0075, 0.3], [0.01, 0.1]], - short: [[0.005, 0.8], [0.0075, 0.2]] -} - /** 由当前交易所成交记录还原的已平仓仓位(含程序外的手动仓位);数值为十进制字符串 */ export interface PositionHistory { symbol: string @@ -313,13 +256,9 @@ export interface PositionHistoryPage { items: PositionHistory[] } -export function listPositionHistory(params: PositionHistoryQuery): Promise { - return http.get("/trading/positions/history", { params }) -} +export type ListPositionHistoryResponseData = PositionHistoryPage -export function listPositionHistorySymbols(): Promise { - return http.get("/trading/positions/history/symbols") -} +export type ListPositionHistorySymbolsResponseData = string[] export type SignalHorizon = "1h" | "4h" @@ -356,27 +295,16 @@ export interface NewsSignals { recent: NewsSignalItem[] } -export function getNewsSignals(): Promise { - return http.get("/trading/news-signals") -} +export type GetNewsSignalsResponseData = NewsSignals -export function getSymbolPlan(symbol: string): Promise<{ plan: Analysis | null, orders: OrderRow[] }> { - return http.get(`/trading/symbols/${symbol}/plan`) -} +export interface GetSymbolPlanResponseData { plan: Analysis | null, orders: OrderRow[] } -export function requestAnalysis(symbol: string): Promise { - return http.post(`/trading/symbols/${symbol}/analyze`) -} +export type RequestAnalysisResponseData = null -/** 一键平仓(同时停止该币的行情交易)、清除挂单:由执行器在几秒内执行,结果写入时间线 */ -export function manualAction(symbol: string, action: "close" | "cancel"): Promise { - return http.post(`/trading/symbols/${symbol}/manual/${action}`) -} +export type ManualActionResponseData = null -export function listAnalyses(params: { symbol?: string, limit?: number }): Promise { - return http.get("/trading/analyses", { params }) -} +export type ListAnalysesResponseData = Analysis[] -export function getAnalysis(id: number): Promise { - return http.get(`/trading/analyses/${id}`) -} +export interface ListAnalysesParamsRequestData { symbol?: string, limit?: number } + +export type GetAnalysisResponseData = AnalysisDetail diff --git a/web/src/styles/index.css b/web/src/common/assets/styles/index.css similarity index 100% rename from web/src/styles/index.css rename to web/src/common/assets/styles/index.css diff --git a/web/src/layouts/index.vue b/web/src/common/components/AppLayout/index.vue similarity index 62% rename from web/src/layouts/index.vue rename to web/src/common/components/AppLayout/index.vue index a9f2e52..5d9ea2d 100644 --- a/web/src/layouts/index.vue +++ b/web/src/common/components/AppLayout/index.vue @@ -1,7 +1,7 @@ + + + + diff --git a/web/src/pages/calendar/index.vue b/web/src/pages/calendar/index.vue new file mode 100644 index 0000000..01cd6f3 --- /dev/null +++ b/web/src/pages/calendar/index.vue @@ -0,0 +1,376 @@ + + + + + diff --git a/web/src/pages/chat/components/ContextTab.vue b/web/src/pages/chat/components/ContextTab.vue index 77aedbd..52c2622 100644 --- a/web/src/pages/chat/components/ContextTab.vue +++ b/web/src/pages/chat/components/ContextTab.vue @@ -1,8 +1,8 @@