feat: 微服务架构拆分和前后端优化
后端: - 拆分出 agent_service, runtime_service, trading_service, news_service - Gateway 模块化拆分 (gateway_*.py) - 添加 domains/ 领域层 - 新增 control_client, runtime_client - 更新 start-dev.sh 支持 split 服务模式 前端: - 完善 API 服务层 (newsApi, tradingApi) - 更新 vite.config.js - Explain 组件优化 测试: - 添加多个服务 app 测试 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,7 @@ export default function StockExplainView({
|
||||
visibleNews,
|
||||
newsCategories,
|
||||
visibleNewsByCategory,
|
||||
selectedNewsFreshness,
|
||||
selectedRangeWindow,
|
||||
selectedRangeExplain,
|
||||
latestSignal,
|
||||
@@ -337,6 +338,7 @@ export default function StockExplainView({
|
||||
newsSnapshot={newsSnapshot}
|
||||
visibleNewsByCategory={visibleNewsByCategory}
|
||||
visibleNews={visibleNews}
|
||||
selectedNewsFreshness={selectedNewsFreshness}
|
||||
activeNewsCategory={activeNewsCategory}
|
||||
onSelectNewsCategory={setActiveNewsCategory}
|
||||
activeNewsSentiment={activeNewsSentiment}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import React from 'react';
|
||||
import { formatDateTime } from '../../utils/formatters';
|
||||
|
||||
function renderFreshness(freshness) {
|
||||
if (!freshness || typeof freshness !== 'object') return null;
|
||||
const lastFetch = freshness.last_news_fetch || '-';
|
||||
return `新闻更新到 ${lastFetch}${freshness.refreshed ? ' · 本次已刷新' : ''}`;
|
||||
}
|
||||
|
||||
function categoryLabel(value) {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
const labels = {
|
||||
@@ -47,6 +53,7 @@ export default function ExplainNewsSection({
|
||||
newsSnapshot,
|
||||
visibleNewsByCategory,
|
||||
visibleNews,
|
||||
selectedNewsFreshness,
|
||||
activeNewsCategory,
|
||||
onSelectNewsCategory,
|
||||
activeNewsSentiment,
|
||||
@@ -64,6 +71,11 @@ export default function ExplainNewsSection({
|
||||
<div style={{ fontSize: 11, color: '#666666' }}>
|
||||
{newsSnapshot?.source ? `最近 ${visibleNewsByCategory.length} 条 · ${newsSnapshot.source}` : `最近 ${visibleNewsByCategory.length} 条真实新闻`}
|
||||
</div>
|
||||
{renderFreshness(selectedNewsFreshness) ? (
|
||||
<div style={{ fontSize: 11, color: '#666666' }}>
|
||||
{renderFreshness(selectedNewsFreshness)}
|
||||
</div>
|
||||
) : null}
|
||||
<button
|
||||
onClick={onToggle}
|
||||
style={{
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import React from 'react';
|
||||
import { formatTickerPrice } from '../../utils/formatters';
|
||||
|
||||
function renderFreshness(freshness) {
|
||||
if (!freshness || typeof freshness !== 'object') return null;
|
||||
const lastFetch = freshness.last_news_fetch || '-';
|
||||
return `新闻更新到 ${lastFetch}${freshness.refreshed ? ' · 本次已刷新' : ''}`;
|
||||
}
|
||||
|
||||
function renderSentimentLabel(value) {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
if (normalized === 'positive') return '利多';
|
||||
@@ -94,6 +100,11 @@ export default function ExplainRangeSection({
|
||||
: `分析来源 · ${renderAnalysisSourceLabel(selectedRangeExplain.analysis.analysis_source)}`}
|
||||
</div>
|
||||
) : null}
|
||||
{renderFreshness(selectedRangeExplain?.freshness) ? (
|
||||
<div style={{ fontSize: 11, color: '#666666' }}>
|
||||
{renderFreshness(selectedRangeExplain?.freshness)}
|
||||
</div>
|
||||
) : null}
|
||||
<button
|
||||
onClick={onToggle}
|
||||
style={{
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function renderFreshness(freshness) {
|
||||
if (!freshness || typeof freshness !== 'object') return null;
|
||||
const lastFetch = freshness.last_news_fetch || '-';
|
||||
return `新闻更新到 ${lastFetch}${freshness.refreshed ? ' · 本次已刷新' : ''}`;
|
||||
}
|
||||
|
||||
export default function ExplainSimilarDaysSection({
|
||||
selectedSimilarDays,
|
||||
selectedEventDate,
|
||||
@@ -15,6 +21,11 @@ export default function ExplainSimilarDaysSection({
|
||||
<div style={{ fontSize: 11, color: '#666666' }}>
|
||||
{selectedEventDate || '先选择一个事件日期'}
|
||||
</div>
|
||||
{renderFreshness(selectedSimilarDays?.freshness) ? (
|
||||
<div style={{ fontSize: 11, color: '#666666' }}>
|
||||
{renderFreshness(selectedSimilarDays?.freshness)}
|
||||
</div>
|
||||
) : null}
|
||||
<button
|
||||
onClick={onToggle}
|
||||
style={{
|
||||
|
||||
@@ -2,6 +2,12 @@ import React from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
function renderFreshness(freshness) {
|
||||
if (!freshness || typeof freshness !== 'object') return null;
|
||||
const lastFetch = freshness.last_news_fetch || '-';
|
||||
return `新闻更新到 ${lastFetch}${freshness.refreshed ? ' · 本次已刷新' : ''}`;
|
||||
}
|
||||
|
||||
export default function ExplainStorySection({
|
||||
selectedStory,
|
||||
selectedSymbol,
|
||||
@@ -17,6 +23,11 @@ export default function ExplainStorySection({
|
||||
<div style={{ fontSize: 11, color: '#666666' }}>
|
||||
{selectedStory?.asOfDate || currentDate || '按当前解释窗口生成'}
|
||||
</div>
|
||||
{renderFreshness(selectedStory?.freshness) ? (
|
||||
<div style={{ fontSize: 11, color: '#666666' }}>
|
||||
{renderFreshness(selectedStory?.freshness)}
|
||||
</div>
|
||||
) : null}
|
||||
<button
|
||||
onClick={onToggle}
|
||||
style={{
|
||||
|
||||
@@ -226,6 +226,13 @@ export default function useExplainModel({
|
||||
return similarCache[selectedEventDate] || null;
|
||||
}, [newsSnapshot, selectedEventDate]);
|
||||
|
||||
const selectedNewsFreshness = useMemo(() => {
|
||||
if (selectedEventDate && newsSnapshot?.byDateFreshness?.[selectedEventDate]) {
|
||||
return newsSnapshot.byDateFreshness[selectedEventDate];
|
||||
}
|
||||
return newsSnapshot?.categoriesFreshness || newsSnapshot?.timelineFreshness || newsSnapshot?.freshness || null;
|
||||
}, [newsSnapshot, selectedEventDate]);
|
||||
|
||||
const latestSignal = tickerSignals[0] || null;
|
||||
const priceColor = selectedTicker?.change > 0 ? '#00C853' : selectedTicker?.change < 0 ? '#FF1744' : '#000000';
|
||||
const exposureWeight = holding && Number.isFinite(Number(holding.weight)) ? Number(holding.weight) * 100 : null;
|
||||
@@ -644,6 +651,7 @@ export default function useExplainModel({
|
||||
visibleNews,
|
||||
newsCategories,
|
||||
visibleNewsByCategory,
|
||||
selectedNewsFreshness,
|
||||
selectedRangeWindow,
|
||||
selectedRangeExplain,
|
||||
selectedStory,
|
||||
|
||||
Reference in New Issue
Block a user