commit
57cfc61521
@ -0,0 +1,29 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
data/collector.log
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
cache/*.pkl
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,40 @@
|
|||||||
|
"""
|
||||||
|
期货智析数据库 - 独立存储
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# 数据目录
|
||||||
|
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
|
||||||
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
ANALYSIS_DB_PATH = DATA_DIR / "futures_analysis.db"
|
||||||
|
|
||||||
|
analysis_engine = create_engine(
|
||||||
|
f"sqlite:///{ANALYSIS_DB_PATH}",
|
||||||
|
connect_args={"check_same_thread": False},
|
||||||
|
pool_pre_ping=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
AnalysisSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=analysis_engine)
|
||||||
|
AnalysisBase = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
def get_analysis_db():
|
||||||
|
"""获取期货智析数据库会话"""
|
||||||
|
db = AnalysisSessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def init_analysis_db():
|
||||||
|
"""初始化期货智析数据库表"""
|
||||||
|
# 确保导入所有模型类,使其注册到 AnalysisBase
|
||||||
|
from app import analysis_models
|
||||||
|
# 直接导入 analysis_models 模块中的所有类
|
||||||
|
from app.analysis_models import FuturesAnalysis, WatchedSymbol, AIModelConfig, AnalysisSettings, AIAnalysisCache
|
||||||
|
AnalysisBase.metadata.create_all(bind=analysis_engine)
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
"""
|
||||||
|
期货智析数据模型
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy import Column, String, Integer, Float, Text, DateTime, Boolean, Index, UniqueConstraint, JSON
|
||||||
|
from app.analysis_db import AnalysisBase
|
||||||
|
|
||||||
|
|
||||||
|
class FuturesAnalysis(AnalysisBase):
|
||||||
|
"""期货分析报告表"""
|
||||||
|
__tablename__ = "futures_analysis"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
symbol = Column(String(32), nullable=False, index=True, comment="品种合约代码")
|
||||||
|
analysis_time = Column(DateTime, nullable=False, default=datetime.now, index=True, comment="分析时间")
|
||||||
|
period = Column(String(16), nullable=False, default="15min", comment="分析周期")
|
||||||
|
# 分析结果
|
||||||
|
suggestion = Column(String(32), nullable=True, comment="交易建议: 逢低做多/逢高做空/观望等待")
|
||||||
|
suggestion_type = Column(String(16), nullable=True, comment="建议类型: up/down/neutral")
|
||||||
|
entry_price = Column(Float, nullable=True, comment="建议入场价")
|
||||||
|
target_price = Column(Float, nullable=True, comment="目标价位")
|
||||||
|
stop_loss = Column(Float, nullable=True, comment="止损价位")
|
||||||
|
risk_level = Column(String(16), nullable=True, comment="风险等级: 低/中/高")
|
||||||
|
# 技术指标
|
||||||
|
macd_signal = Column(String(16), nullable=True, comment="MACD信号")
|
||||||
|
rsi_value = Column(Float, nullable=True, comment="RSI值")
|
||||||
|
boll_signal = Column(String(16), nullable=True, comment="布林带信号")
|
||||||
|
kdj_signal = Column(String(16), nullable=True, comment="KDJ信号")
|
||||||
|
# 趋势评分
|
||||||
|
trend_score = Column(Integer, nullable=True, comment="趋势评分 0-100")
|
||||||
|
success_rate = Column(Float, nullable=True, comment="交易成功率")
|
||||||
|
# 关键点位
|
||||||
|
resistance_levels = Column(JSON, nullable=True, comment="压力位列表")
|
||||||
|
support_levels = Column(JSON, nullable=True, comment="支撑位列表")
|
||||||
|
# 多周期趋势
|
||||||
|
period_trends = Column(JSON, nullable=True, comment="各周期趋势")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<FuturesAnalysis {self.symbol} {self.analysis_time}>"
|
||||||
|
|
||||||
|
|
||||||
|
class WatchedSymbol(AnalysisBase):
|
||||||
|
"""用户关注品种表"""
|
||||||
|
__tablename__ = "watched_symbols"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
symbol = Column(String(32), nullable=False, unique=True, comment="品种合约代码")
|
||||||
|
name = Column(String(64), nullable=True, comment="品种名称")
|
||||||
|
note = Column(Text, nullable=True, comment="备注")
|
||||||
|
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
||||||
|
updated_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<WatchedSymbol {self.symbol}>"
|
||||||
|
|
||||||
|
|
||||||
|
class AIModelConfig(AnalysisBase):
|
||||||
|
"""AI模型配置表"""
|
||||||
|
__tablename__ = "ai_model_configs"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
provider = Column(String(32), nullable=False, comment="AI提供商: openai/anthropic/google等")
|
||||||
|
model_name = Column(String(64), nullable=False, comment="模型名称")
|
||||||
|
api_key = Column(String(256), nullable=False, comment="API密钥")
|
||||||
|
api_base = Column(String(256), nullable=True, comment="API基础URL")
|
||||||
|
model_id = Column(String(64), nullable=True, comment="模型ID")
|
||||||
|
temperature = Column(Float, nullable=True, default=0.7, comment="温度参数")
|
||||||
|
max_tokens = Column(Integer, nullable=True, default=2000, comment="最大输出token")
|
||||||
|
enabled = Column(Boolean, nullable=False, default=True, comment="是否启用")
|
||||||
|
is_active = Column(Boolean, nullable=False, default=False, comment="是否为当前活跃模型")
|
||||||
|
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
||||||
|
updated_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<AIModelConfig {self.provider} {self.model_name}>"
|
||||||
|
|
||||||
|
|
||||||
|
class AnalysisSettings(AnalysisBase):
|
||||||
|
"""分析设置表(单例配置)"""
|
||||||
|
__tablename__ = "analysis_settings"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
key = Column(String(64), nullable=False, unique=True, comment="配置键")
|
||||||
|
value = Column(JSON, nullable=False, comment="配置值")
|
||||||
|
updated_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<AnalysisSettings {self.key}>"
|
||||||
|
|
||||||
|
|
||||||
|
class AIAnalysisCache(AnalysisBase):
|
||||||
|
"""AI分析缓存表"""
|
||||||
|
__tablename__ = "ai_analysis_cache"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
symbol = Column(String(32), nullable=False, index=True, comment="品种合约代码")
|
||||||
|
analysis_data = Column(JSON, nullable=False, comment="AI分析结果数据")
|
||||||
|
kline_timestamp = Column(DateTime, nullable=True, comment="分析时K线数据的时间戳")
|
||||||
|
created_at = Column(DateTime, nullable=False, default=datetime.now, index=True, comment="分析时间")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<AIAnalysisCache {self.symbol} {self.created_at}>"
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,189 @@
|
|||||||
|
"""
|
||||||
|
AI模型配置接口 - 管理AI分析模型的配置
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
router = APIRouter(prefix="/ai-config", tags=["AI模型配置"])
|
||||||
|
|
||||||
|
|
||||||
|
class AIModelConfig(BaseModel):
|
||||||
|
"""AI模型配置"""
|
||||||
|
model_name: str
|
||||||
|
api_key: str
|
||||||
|
api_base: str = "https://api.openai.com/v1"
|
||||||
|
model_id: str = "gpt-4"
|
||||||
|
temperature: float = 0.7
|
||||||
|
max_tokens: int = 2000
|
||||||
|
enabled: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
class AIConfigResponse(BaseModel):
|
||||||
|
"""AI配置响应"""
|
||||||
|
success: bool
|
||||||
|
data: Optional[dict] = None
|
||||||
|
message: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class SaveAIConfigRequest(BaseModel):
|
||||||
|
"""保存AI配置请求"""
|
||||||
|
models: list = []
|
||||||
|
active_model: Optional[str] = None
|
||||||
|
analysis_settings: Optional[dict] = None
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_DIR = Path(__file__).resolve().parent.parent.parent / "config"
|
||||||
|
AI_CONFIG_FILE = CONFIG_DIR / "ai_config.json"
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_config_dir():
|
||||||
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_ai_config() -> dict:
|
||||||
|
"""加载AI配置"""
|
||||||
|
_ensure_config_dir()
|
||||||
|
if not AI_CONFIG_FILE.exists():
|
||||||
|
return {
|
||||||
|
"models": [],
|
||||||
|
"active_model": None,
|
||||||
|
"analysis_settings": {
|
||||||
|
"enable_technical_analysis": True,
|
||||||
|
"enable_fundamental_analysis": False,
|
||||||
|
"enable_sentiment_analysis": False,
|
||||||
|
"risk_tolerance": "medium",
|
||||||
|
"max_position_pct": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with open(AI_CONFIG_FILE, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def _save_ai_config(config: dict):
|
||||||
|
"""保存AI配置"""
|
||||||
|
_ensure_config_dir()
|
||||||
|
with open(AI_CONFIG_FILE, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(config, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("", response_model=AIConfigResponse)
|
||||||
|
def get_ai_config():
|
||||||
|
"""获取当前AI模型配置"""
|
||||||
|
try:
|
||||||
|
config = _load_ai_config()
|
||||||
|
return {"success": True, "data": config}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"加载AI配置失败: {e}")
|
||||||
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("", response_model=AIConfigResponse)
|
||||||
|
def save_ai_config(config: SaveAIConfigRequest):
|
||||||
|
"""保存AI模型配置"""
|
||||||
|
try:
|
||||||
|
config_dict = {
|
||||||
|
"models": config.models,
|
||||||
|
"active_model": config.active_model,
|
||||||
|
"analysis_settings": config.analysis_settings or {}
|
||||||
|
}
|
||||||
|
_save_ai_config(config_dict)
|
||||||
|
return {"success": True, "message": "AI配置保存成功"}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"保存AI配置失败: {e}")
|
||||||
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/test", response_model=AIConfigResponse)
|
||||||
|
def test_ai_connection(model_config: AIModelConfig):
|
||||||
|
"""测试AI模型连接"""
|
||||||
|
try:
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {model_config.api_key}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"model": model_config.model_id,
|
||||||
|
"messages": [{"role": "user", "content": "Hello"}],
|
||||||
|
"max_tokens": 10
|
||||||
|
}
|
||||||
|
|
||||||
|
with httpx.Client(timeout=30) as client:
|
||||||
|
response = client.post(
|
||||||
|
f"{model_config.api_base}/chat/completions",
|
||||||
|
headers=headers,
|
||||||
|
json=data
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return {"success": True, "message": "连接测试成功"}
|
||||||
|
else:
|
||||||
|
return {"success": False, "message": f"连接失败: {response.status_code} - {response.text}"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"AI连接测试失败: {e}")
|
||||||
|
return {"success": False, "message": f"连接测试失败: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/providers")
|
||||||
|
def get_ai_providers():
|
||||||
|
"""获取支持的AI提供商列表"""
|
||||||
|
providers = [
|
||||||
|
{
|
||||||
|
"id": "openai",
|
||||||
|
"name": "OpenAI",
|
||||||
|
"api_base": "https://api.openai.com/v1",
|
||||||
|
"models": ["gpt-4o", "gpt-4-turbo", "gpt-3.5-turbo"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "anthropic",
|
||||||
|
"name": "Anthropic Claude",
|
||||||
|
"api_base": "https://api.anthropic.com/v1",
|
||||||
|
"models": ["claude-3-opus", "claude-3-sonnet", "claude-3-haiku"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "google",
|
||||||
|
"name": "Google Gemini",
|
||||||
|
"api_base": "https://generativelanguage.googleapis.com/v1beta",
|
||||||
|
"models": ["gemini-pro", "gemini-pro-vision"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aliyun",
|
||||||
|
"name": "阿里云通义千问",
|
||||||
|
"api_base": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||||
|
"models": ["qwen-max", "qwen-plus", "qwen-turbo"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aliyun_coding",
|
||||||
|
"name": "阿里云通义灵码",
|
||||||
|
"api_base": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||||
|
"models": ["qwen-coder-plus", "qwen-coder-turbo"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bailian",
|
||||||
|
"name": "阿里百炼",
|
||||||
|
"api_base": "https://coding.dashscope.aliyuncs.com/v1",
|
||||||
|
"models": ["qwen3.6-plus", "qwen3.5-plus", "qwen3-max", "qwen3-coder-plus", "MiniMax-M2.5", "glm-4.7", "kimi-k2.5"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "baidu",
|
||||||
|
"name": "百度文心一言",
|
||||||
|
"api_base": "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop",
|
||||||
|
"models": ["ernie-4.0", "ernie-3.5", "ernie-speed"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "zhipu",
|
||||||
|
"name": "智谱清言",
|
||||||
|
"api_base": "https://open.bigmodel.cn/api/paas/v4",
|
||||||
|
"models": ["glm-4", "glm-3-turbo"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return {"success": True, "data": providers}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,568 @@
|
|||||||
|
:root {
|
||||||
|
--bg-primary: #0d0f14;
|
||||||
|
--bg-secondary: #151820;
|
||||||
|
--bg-card: #1a1d28;
|
||||||
|
--bg-card-hover: #222633;
|
||||||
|
--border-color: #2a2d3a;
|
||||||
|
--text-primary: #e8eaed;
|
||||||
|
--text-secondary: #9aa0ab;
|
||||||
|
--text-muted: #6b7280;
|
||||||
|
--green: #22c55e;
|
||||||
|
--green-bg: rgba(34, 197, 94, 0.15);
|
||||||
|
--green-border: rgba(34, 197, 94, 0.3);
|
||||||
|
--red: #ef4444;
|
||||||
|
--red-bg: rgba(239, 68, 68, 0.15);
|
||||||
|
--blue: #3b82f6;
|
||||||
|
--purple: #8b5cf6;
|
||||||
|
--orange: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title i {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-icon-btn {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-icon-btn:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: var(--bg-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h3 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h3 i {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提供商网格 */
|
||||||
|
.provider-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-card {
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-card:hover {
|
||||||
|
border-color: var(--text-muted);
|
||||||
|
background: var(--bg-card-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-card.active {
|
||||||
|
border-color: var(--green);
|
||||||
|
background: var(--green-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-card i {
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-card.active i {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-card .provider-name {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单 */
|
||||||
|
.form-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-with-toggle {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-with-toggle .form-control {
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-visibility {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-range {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
outline: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-range::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--green);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-labels {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-result {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-result.success {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-result.error {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 当前启用的AI卡片 */
|
||||||
|
.active-ai-card {
|
||||||
|
border-color: var(--green-border);
|
||||||
|
background: var(--green-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-ai-card .card-header h3 {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-ai-card .card-header i {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-ai-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-ai-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(34, 197, 94, 0.15);
|
||||||
|
border: 1px solid var(--green-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
color: var(--green);
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-ai-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-ai-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-ai-model {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设置列表 */
|
||||||
|
.settings-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 开关 */
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
width: 48px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 26px;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
left: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
background: var(--text-muted);
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider {
|
||||||
|
background: var(--green-bg);
|
||||||
|
border-color: var(--green-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
transform: translateX(22px);
|
||||||
|
background: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模型列表 */
|
||||||
|
.models-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-status {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-status.active {
|
||||||
|
background: var(--green);
|
||||||
|
box-shadow: 0 0 8px var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-status.inactive {
|
||||||
|
background: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-provider {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-actions button {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-set-active {
|
||||||
|
background: var(--green-bg);
|
||||||
|
color: var(--green);
|
||||||
|
border: 1px solid var(--green-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-set-active:hover {
|
||||||
|
background: var(--green);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
background: var(--red-bg);
|
||||||
|
color: var(--red);
|
||||||
|
border: 1px solid var(--red-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete:hover {
|
||||||
|
background: var(--red);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮 */
|
||||||
|
.btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lg {
|
||||||
|
padding: 12px 28px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--green);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: var(--bg-card-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.form-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-grid {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-actions .btn {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,211 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AI模型配置 - 期货智析</title>
|
||||||
|
<link rel="stylesheet" href="/static/ai_config.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app-container">
|
||||||
|
<header class="top-nav">
|
||||||
|
<div class="nav-left">
|
||||||
|
<a href="/static/futures_analysis.html" class="back-link">
|
||||||
|
<i class="fas fa-arrow-left"></i> 返回期货智析
|
||||||
|
</a>
|
||||||
|
<div class="page-title">
|
||||||
|
<i class="fas fa-robot"></i>
|
||||||
|
<span>AI模型配置</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="nav-right">
|
||||||
|
<a href="/" class="nav-icon-btn" title="返回首页">
|
||||||
|
<i class="fas fa-home"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="config-container">
|
||||||
|
<!-- 当前启用的AI -->
|
||||||
|
<div class="config-card active-ai-card" id="active-ai-card" style="display:none;">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3><i class="fas fa-check-circle"></i> 当前启用</h3>
|
||||||
|
</div>
|
||||||
|
<div class="active-ai-display">
|
||||||
|
<div class="active-ai-icon" id="active-ai-icon">
|
||||||
|
<i class="fas fa-robot"></i>
|
||||||
|
</div>
|
||||||
|
<div class="active-ai-info">
|
||||||
|
<div class="active-ai-name" id="active-ai-name">--</div>
|
||||||
|
<div class="active-ai-model" id="active-ai-model">--</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- AI提供商选择 -->
|
||||||
|
<div class="config-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3><i class="fas fa-cloud"></i> AI提供商</h3>
|
||||||
|
</div>
|
||||||
|
<div class="provider-grid" id="provider-grid">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- API配置 -->
|
||||||
|
<div class="config-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3><i class="fas fa-key"></i> API配置</h3>
|
||||||
|
</div>
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>提供商</label>
|
||||||
|
<select id="api-provider" class="form-control">
|
||||||
|
<option value="openai">OpenAI</option>
|
||||||
|
<option value="anthropic">Anthropic Claude</option>
|
||||||
|
<option value="google">Google Gemini</option>
|
||||||
|
<option value="aliyun">阿里云通义千问</option>
|
||||||
|
<option value="aliyun_coding">阿里云通义灵码</option>
|
||||||
|
<option value="bailian">阿里百炼</option>
|
||||||
|
<option value="baidu">百度文心一言</option>
|
||||||
|
<option value="zhipu">智谱清言</option>
|
||||||
|
<option value="custom">自定义</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>API Key</label>
|
||||||
|
<div class="input-with-toggle">
|
||||||
|
<input type="password" id="api-key" class="form-control" placeholder="sk-...">
|
||||||
|
<button type="button" class="toggle-visibility" onclick="toggleApiKeyVisibility()">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>API Base URL</label>
|
||||||
|
<input type="text" id="api-base" class="form-control" placeholder="https://api.openai.com/v1">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>模型ID</label>
|
||||||
|
<select id="model-id" class="form-control">
|
||||||
|
<option value="gpt-4o">gpt-4o</option>
|
||||||
|
<option value="gpt-4-turbo">gpt-4-turbo</option>
|
||||||
|
<option value="gpt-3.5-turbo">gpt-3.5-turbo</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>自定义模型</label>
|
||||||
|
<input type="text" id="custom-model" class="form-control" placeholder="输入自定义模型名称(留空使用上方选择)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-secondary" onclick="testConnection()">
|
||||||
|
<i class="fas fa-plug"></i> 测试连接
|
||||||
|
</button>
|
||||||
|
<span id="test-result" class="test-result"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 模型参数 -->
|
||||||
|
<div class="config-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3><i class="fas fa-sliders-h"></i> 模型参数</h3>
|
||||||
|
</div>
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Temperature (创造力): <span id="temp-value">0.7</span></label>
|
||||||
|
<input type="range" id="temperature" class="form-range" min="0" max="2" step="0.1" value="0.7">
|
||||||
|
<div class="range-labels">
|
||||||
|
<span>精确</span>
|
||||||
|
<span>创造</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Max Tokens (最大输出)</label>
|
||||||
|
<input type="number" id="max-tokens" class="form-control" value="2000" min="100" max="8000">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分析设置 -->
|
||||||
|
<div class="config-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3><i class="fas fa-cogs"></i> 分析设置</h3>
|
||||||
|
</div>
|
||||||
|
<div class="settings-list">
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-name">技术分析</span>
|
||||||
|
<span class="setting-desc">基于K线和技术指标进行分析</span>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="enable-technical" checked>
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-name">基本面分析</span>
|
||||||
|
<span class="setting-desc">结合宏观经济和行业数据</span>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="enable-fundamental">
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-name">情绪分析</span>
|
||||||
|
<span class="setting-desc">分析市场情绪和新闻舆情</span>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="enable-sentiment">
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>风险偏好</label>
|
||||||
|
<select id="risk-tolerance" class="form-control">
|
||||||
|
<option value="conservative">保守型</option>
|
||||||
|
<option value="medium" selected>平衡型</option>
|
||||||
|
<option value="aggressive">激进型</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>最大仓位比例 (%)</label>
|
||||||
|
<input type="number" id="max-position" class="form-control" value="10" min="1" max="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 已保存的模型 -->
|
||||||
|
<div class="config-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3><i class="fas fa-database"></i> 已保存的模型</h3>
|
||||||
|
<button class="btn btn-primary btn-sm" onclick="addNewModel()">
|
||||||
|
<i class="fas fa-plus"></i> 添加模型
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="models-list" id="models-list">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 保存按钮 -->
|
||||||
|
<div class="save-actions">
|
||||||
|
<button class="btn btn-primary btn-lg" onclick="saveConfig()">
|
||||||
|
<i class="fas fa-save"></i> 保存配置
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary btn-lg" onclick="loadConfig()">
|
||||||
|
<i class="fas fa-sync"></i> 重新加载
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/ai_config.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,369 @@
|
|||||||
|
const API_BASE = '/api/v1/ai-config';
|
||||||
|
|
||||||
|
let currentConfig = null;
|
||||||
|
let selectedProvider = 'openai';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
loadProviders();
|
||||||
|
loadConfig();
|
||||||
|
initEventListeners();
|
||||||
|
});
|
||||||
|
|
||||||
|
function initEventListeners() {
|
||||||
|
document.getElementById('api-provider').addEventListener('change', function() {
|
||||||
|
selectedProvider = this.value;
|
||||||
|
updateProviderModels();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('temperature').addEventListener('input', function() {
|
||||||
|
document.getElementById('temp-value').textContent = this.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadProviders() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/providers`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.success) {
|
||||||
|
renderProviders(data.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载提供商失败:', error);
|
||||||
|
renderProviders(getDefaultProviders());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultProviders() {
|
||||||
|
return [
|
||||||
|
{ id: 'openai', name: 'OpenAI', icon: 'fas fa-brain' },
|
||||||
|
{ id: 'anthropic', name: 'Claude', icon: 'fas fa-robot' },
|
||||||
|
{ id: 'google', name: 'Gemini', icon: 'fas fa-gem' },
|
||||||
|
{ id: 'aliyun', name: '通义千问', icon: 'fas fa-cloud' },
|
||||||
|
{ id: 'aliyun_coding', name: '通义灵码', icon: 'fas fa-code' },
|
||||||
|
{ id: 'bailian', name: '阿里百炼', icon: 'fas fa-flask' },
|
||||||
|
{ id: 'baidu', name: '文心一言', icon: 'fas fa-comments' },
|
||||||
|
{ id: 'zhipu', name: '智谱清言', icon: 'fas fa-lightbulb' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderProviders(providers) {
|
||||||
|
const grid = document.getElementById('provider-grid');
|
||||||
|
const iconMap = {
|
||||||
|
'openai': 'fas fa-brain',
|
||||||
|
'anthropic': 'fas fa-robot',
|
||||||
|
'google': 'fas fa-gem',
|
||||||
|
'aliyun': 'fas fa-cloud',
|
||||||
|
'aliyun_coding': 'fas fa-code',
|
||||||
|
'bailian': 'fas fa-flask',
|
||||||
|
'baidu': 'fas fa-comments',
|
||||||
|
'zhipu': 'fas fa-lightbulb',
|
||||||
|
'custom': 'fas fa-cog'
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.innerHTML = providers.map(p => `
|
||||||
|
<div class="provider-card ${p.id === selectedProvider ? 'active' : ''}" data-provider="${p.id}">
|
||||||
|
<i class="${iconMap[p.id] || 'fas fa-cog'}"></i>
|
||||||
|
<div class="provider-name">${p.name}</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
grid.querySelectorAll('.provider-card').forEach(card => {
|
||||||
|
card.addEventListener('click', function() {
|
||||||
|
grid.querySelectorAll('.provider-card').forEach(c => c.classList.remove('active'));
|
||||||
|
this.classList.add('active');
|
||||||
|
selectedProvider = this.dataset.provider;
|
||||||
|
document.getElementById('api-provider').value = selectedProvider;
|
||||||
|
updateProviderModels();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProviderModels() {
|
||||||
|
const modelSelect = document.getElementById('model-id');
|
||||||
|
const modelMap = {
|
||||||
|
'openai': ['gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo'],
|
||||||
|
'anthropic': ['claude-3-opus', 'claude-3-sonnet', 'claude-3-haiku'],
|
||||||
|
'google': ['gemini-pro', 'gemini-pro-vision'],
|
||||||
|
'aliyun': ['qwen-max', 'qwen-plus', 'qwen-turbo'],
|
||||||
|
'aliyun_coding': ['qwen-coder-plus', 'qwen-coder-turbo'],
|
||||||
|
'bailian': ['qwen3.6-plus', 'qwen3.5-plus', 'qwen3-max', 'qwen3-coder-plus', 'MiniMax-M2.5', 'glm-4.7', 'kimi-k2.5', 'custom'],
|
||||||
|
'baidu': ['ernie-4.0', 'ernie-3.5', 'ernie-speed'],
|
||||||
|
'zhipu': ['glm-4', 'glm-3-turbo'],
|
||||||
|
'custom': ['custom-model']
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiBaseMap = {
|
||||||
|
'openai': 'https://api.openai.com/v1',
|
||||||
|
'anthropic': 'https://api.anthropic.com/v1',
|
||||||
|
'google': 'https://generativelanguage.googleapis.com/v1beta',
|
||||||
|
'aliyun': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||||
|
'aliyun_coding': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||||
|
'bailian': 'https://coding.dashscope.aliyuncs.com/v1',
|
||||||
|
'baidu': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop',
|
||||||
|
'zhipu': 'https://open.bigmodel.cn/api/paas/v4',
|
||||||
|
'custom': ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const models = modelMap[selectedProvider] || ['custom-model'];
|
||||||
|
modelSelect.innerHTML = models.map(m => `<option value="${m}">${m}</option>`).join('');
|
||||||
|
document.getElementById('api-base').value = apiBaseMap[selectedProvider] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadConfig() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(API_BASE);
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success && result.data) {
|
||||||
|
currentConfig = result.data;
|
||||||
|
populateForm(currentConfig);
|
||||||
|
renderModelsList(currentConfig.models || []);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载配置失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateForm(config) {
|
||||||
|
if (config.models && config.models.length > 0) {
|
||||||
|
const activeModel = config.models.find(m => m.enabled) || config.models[0];
|
||||||
|
document.getElementById('api-provider').value = activeModel.provider || 'openai';
|
||||||
|
document.getElementById('api-key').value = activeModel.api_key || '';
|
||||||
|
document.getElementById('api-base').value = activeModel.api_base || '';
|
||||||
|
document.getElementById('model-id').value = activeModel.model_id || 'gpt-4o';
|
||||||
|
document.getElementById('custom-model').value = '';
|
||||||
|
document.getElementById('temperature').value = activeModel.temperature || 0.7;
|
||||||
|
document.getElementById('temp-value').textContent = activeModel.temperature || 0.7;
|
||||||
|
document.getElementById('max-tokens').value = activeModel.max_tokens || 2000;
|
||||||
|
|
||||||
|
// 选中当前启用的提供商卡片
|
||||||
|
const providerCard = document.querySelector(`.provider-card[data-provider="${activeModel.provider}"]`);
|
||||||
|
if (providerCard) {
|
||||||
|
document.querySelectorAll('.provider-card').forEach(c => c.classList.remove('active'));
|
||||||
|
providerCard.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新模型下拉框
|
||||||
|
updateProviderModels();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.analysis_settings) {
|
||||||
|
document.getElementById('enable-technical').checked = config.analysis_settings.enable_technical_analysis !== false;
|
||||||
|
document.getElementById('enable-fundamental').checked = config.analysis_settings.enable_fundamental_analysis === true;
|
||||||
|
document.getElementById('enable-sentiment').checked = config.analysis_settings.enable_sentiment_analysis === true;
|
||||||
|
document.getElementById('risk-tolerance').value = config.analysis_settings.risk_tolerance || 'medium';
|
||||||
|
document.getElementById('max-position').value = config.analysis_settings.max_position_pct || 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderModelsList(models) {
|
||||||
|
const list = document.getElementById('models-list');
|
||||||
|
const activeCard = document.getElementById('active-ai-card');
|
||||||
|
|
||||||
|
if (!models || models.length === 0) {
|
||||||
|
list.innerHTML = '<div class="empty-state">暂无已保存的模型</div>';
|
||||||
|
activeCard.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeModel = models.find(m => m.enabled);
|
||||||
|
if (activeModel) {
|
||||||
|
activeCard.style.display = 'block';
|
||||||
|
document.getElementById('active-ai-name').textContent = getProviderName(activeModel.provider || activeModel.api_base);
|
||||||
|
document.getElementById('active-ai-model').textContent = activeModel.model_name || activeModel.model_id || '--';
|
||||||
|
|
||||||
|
const iconMap = {
|
||||||
|
'openai': 'fab fa-openai',
|
||||||
|
'anthropic': 'fas fa-robot',
|
||||||
|
'google': 'fab fa-google',
|
||||||
|
'aliyun': 'fas fa-cloud',
|
||||||
|
'aliyun_coding': 'fas fa-code',
|
||||||
|
'bailian': 'fas fa-flask',
|
||||||
|
'baidu': 'fas fa-comments',
|
||||||
|
'zhipu': 'fas fa-lightbulb'
|
||||||
|
};
|
||||||
|
const icon = document.getElementById('active-ai-icon').querySelector('i');
|
||||||
|
icon.className = iconMap[activeModel.provider] || 'fas fa-robot';
|
||||||
|
} else {
|
||||||
|
activeCard.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
list.innerHTML = models.map((model, index) => `
|
||||||
|
<div class="model-item">
|
||||||
|
<div class="model-info">
|
||||||
|
<div class="model-status ${model.enabled ? 'active' : 'inactive'}"></div>
|
||||||
|
<div>
|
||||||
|
<div class="model-name">${model.model_name || model.model_id}</div>
|
||||||
|
<div class="model-provider">${getProviderName(model.provider || model.api_base)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="model-actions">
|
||||||
|
${!model.enabled ? `<button class="btn-set-active" onclick="setActiveModel(${index})">设为默认</button>` : '<span class="active-badge">当前启用</span>'}
|
||||||
|
<button class="btn-delete" onclick="deleteModel(${index})"><i class="fas fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProviderName(apiBase) {
|
||||||
|
const map = {
|
||||||
|
'openai': 'OpenAI',
|
||||||
|
'anthropic': 'Anthropic',
|
||||||
|
'google': 'Google',
|
||||||
|
'aliyun': '阿里云',
|
||||||
|
'aliyun_coding': '阿里云通义灵码',
|
||||||
|
'bailian': '阿里百炼',
|
||||||
|
'baidu': '百度',
|
||||||
|
'zhipu': '智谱'
|
||||||
|
};
|
||||||
|
return map[apiBase] || apiBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleApiKeyVisibility() {
|
||||||
|
const input = document.getElementById('api-key');
|
||||||
|
const icon = document.querySelector('.toggle-visibility i');
|
||||||
|
if (input.type === 'password') {
|
||||||
|
input.type = 'text';
|
||||||
|
icon.className = 'fas fa-eye-slash';
|
||||||
|
} else {
|
||||||
|
input.type = 'password';
|
||||||
|
icon.className = 'fas fa-eye';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testConnection() {
|
||||||
|
const resultEl = document.getElementById('test-result');
|
||||||
|
resultEl.textContent = '测试中...';
|
||||||
|
resultEl.className = 'test-result';
|
||||||
|
|
||||||
|
const customModel = document.getElementById('custom-model').value.trim();
|
||||||
|
const selectedModel = document.getElementById('model-id').value;
|
||||||
|
const modelId = customModel || (selectedModel === 'custom' ? '' : selectedModel);
|
||||||
|
|
||||||
|
if (!modelId) {
|
||||||
|
resultEl.textContent = '✗ 请输入模型ID';
|
||||||
|
resultEl.className = 'test-result error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
model_name: modelId,
|
||||||
|
api_key: document.getElementById('api-key').value,
|
||||||
|
api_base: document.getElementById('api-base').value,
|
||||||
|
model_id: modelId,
|
||||||
|
temperature: parseFloat(document.getElementById('temperature').value),
|
||||||
|
max_tokens: parseInt(document.getElementById('max-tokens').value),
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/test`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(config)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
resultEl.textContent = `✗ 请求失败: ${response.status}`;
|
||||||
|
resultEl.className = 'test-result error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
resultEl.textContent = '✓ 连接成功';
|
||||||
|
resultEl.className = 'test-result success';
|
||||||
|
} else {
|
||||||
|
resultEl.textContent = '✗ ' + (data.message || '未知错误');
|
||||||
|
resultEl.className = 'test-result error';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
resultEl.textContent = '✗ 连接失败: ' + (error.message || '未知错误');
|
||||||
|
resultEl.className = 'test-result error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveConfig() {
|
||||||
|
const models = currentConfig?.models || [];
|
||||||
|
const existingIndex = models.findIndex(m => m.provider === selectedProvider);
|
||||||
|
|
||||||
|
const customModel = document.getElementById('custom-model').value.trim();
|
||||||
|
const selectedModel = document.getElementById('model-id').value;
|
||||||
|
const modelId = customModel || (selectedModel === 'custom' ? '' : selectedModel);
|
||||||
|
|
||||||
|
const newModel = {
|
||||||
|
model_name: modelId,
|
||||||
|
provider: selectedProvider,
|
||||||
|
api_key: document.getElementById('api-key').value,
|
||||||
|
api_base: document.getElementById('api-base').value,
|
||||||
|
model_id: modelId,
|
||||||
|
temperature: parseFloat(document.getElementById('temperature').value),
|
||||||
|
max_tokens: parseInt(document.getElementById('max-tokens').value),
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
models[existingIndex] = { ...models[existingIndex], ...newModel };
|
||||||
|
} else {
|
||||||
|
models.push(newModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
models.forEach(m => {
|
||||||
|
m.enabled = (m.provider === selectedProvider && m.model_id === modelId);
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
models: models,
|
||||||
|
active_model: selectedProvider,
|
||||||
|
analysis_settings: {
|
||||||
|
enable_technical_analysis: document.getElementById('enable-technical').checked,
|
||||||
|
enable_fundamental_analysis: document.getElementById('enable-fundamental').checked,
|
||||||
|
enable_sentiment_analysis: document.getElementById('enable-sentiment').checked,
|
||||||
|
risk_tolerance: document.getElementById('risk-tolerance').value,
|
||||||
|
max_position_pct: parseInt(document.getElementById('max-position').value)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(API_BASE, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(config)
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
alert('配置保存成功!');
|
||||||
|
currentConfig = config;
|
||||||
|
renderModelsList(models);
|
||||||
|
} else {
|
||||||
|
alert('保存失败: ' + data.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('保存失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveModel(index) {
|
||||||
|
if (!currentConfig || !currentConfig.models) return;
|
||||||
|
|
||||||
|
currentConfig.models.forEach((m, i) => {
|
||||||
|
m.enabled = i === index;
|
||||||
|
});
|
||||||
|
|
||||||
|
saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteModel(index) {
|
||||||
|
if (!confirm('确定要删除这个模型吗?')) return;
|
||||||
|
|
||||||
|
if (!currentConfig || !currentConfig.models) return;
|
||||||
|
|
||||||
|
currentConfig.models.splice(index, 1);
|
||||||
|
saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNewModel() {
|
||||||
|
document.getElementById('api-key').value = '';
|
||||||
|
document.getElementById('api-key').focus();
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,416 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>期货智析 - 智能期货期权分析系统</title>
|
||||||
|
<link rel="stylesheet" href="/static/futures_analysis.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body class="theme-minimal">
|
||||||
|
<div class="bg-grid"></div>
|
||||||
|
<div class="bg-glow"></div>
|
||||||
|
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 顶部导航 -->
|
||||||
|
<header class="top-nav">
|
||||||
|
<div class="nav-left">
|
||||||
|
<div class="logo">
|
||||||
|
<div class="logo-icon">
|
||||||
|
<i class="fas fa-chart-line"></i>
|
||||||
|
<div class="logo-pulse"></div>
|
||||||
|
</div>
|
||||||
|
<div class="logo-text">
|
||||||
|
<span class="logo-title">期货智析</span>
|
||||||
|
<span class="logo-subtitle">FUTURES INTELLIGENCE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav class="nav-center">
|
||||||
|
<a href="#" class="nav-item active" data-page="analysis">
|
||||||
|
<span class="nav-icon"><i class="fas fa-microchip"></i></span>
|
||||||
|
<span>品种分析</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="nav-item" data-page="watched">
|
||||||
|
<span class="nav-icon"><i class="fas fa-star"></i></span>
|
||||||
|
<span>自选</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="nav-item" data-page="market">
|
||||||
|
<span class="nav-icon"><i class="fas fa-globe"></i></span>
|
||||||
|
<span>市场概览</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="nav-item" data-page="risk">
|
||||||
|
<span class="nav-icon"><i class="fas fa-shield-alt"></i></span>
|
||||||
|
<span>风险预警</span>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
<div class="nav-right">
|
||||||
|
<div class="system-status">
|
||||||
|
<span class="status-dot"></span>
|
||||||
|
<span class="status-text">LIVE</span>
|
||||||
|
</div>
|
||||||
|
<div class="datetime" id="current-time">--</div>
|
||||||
|
<a href="/ai-config" class="nav-btn" title="AI配置">
|
||||||
|
<i class="fas fa-brain"></i>
|
||||||
|
</a>
|
||||||
|
<button class="nav-btn theme-toggle" id="theme-toggle" title="切换主题">
|
||||||
|
<i class="fas fa-moon"></i>
|
||||||
|
</button>
|
||||||
|
<a href="/" class="nav-btn" title="返回首页">
|
||||||
|
<i class="fas fa-th-large"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Toast 提示容器 -->
|
||||||
|
<div class="toast-container" id="toast-container"></div>
|
||||||
|
|
||||||
|
<!-- 主内容区 -->
|
||||||
|
<main class="main-content">
|
||||||
|
<!-- 品种列表视图 -->
|
||||||
|
<div id="list-view" class="view active">
|
||||||
|
<!-- 搜索栏 -->
|
||||||
|
<div class="search-section">
|
||||||
|
<div class="search-box">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<input type="text" id="search-input" placeholder="搜索品种名称或代码...">
|
||||||
|
<kbd>⌘K</kbd>
|
||||||
|
</div>
|
||||||
|
<div class="view-controls">
|
||||||
|
<button class="view-btn active" data-view="grid" title="网格视图">
|
||||||
|
<i class="fas fa-grid-2"></i>
|
||||||
|
<span>网格</span>
|
||||||
|
</button>
|
||||||
|
<button class="view-btn" data-view="list" title="列表视图">
|
||||||
|
<i class="fas fa-list"></i>
|
||||||
|
<span>列表</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选栏 -->
|
||||||
|
<div class="filter-bar">
|
||||||
|
<div class="filter-tabs">
|
||||||
|
<button class="filter-tab active" data-category="all">
|
||||||
|
<span>全部</span>
|
||||||
|
<span class="filter-count" id="count-all">0</span>
|
||||||
|
</button>
|
||||||
|
<button class="filter-tab" data-category="watched">
|
||||||
|
<i class="fas fa-star"></i>
|
||||||
|
<span>自选</span>
|
||||||
|
<span class="filter-count" id="count-watched">0</span>
|
||||||
|
</button>
|
||||||
|
<button class="filter-tab" data-category="energy">
|
||||||
|
<i class="fas fa-fire"></i>
|
||||||
|
<span>能源</span>
|
||||||
|
</button>
|
||||||
|
<button class="filter-tab" data-category="metal">
|
||||||
|
<i class="fas fa-cube"></i>
|
||||||
|
<span>金属</span>
|
||||||
|
</button>
|
||||||
|
<button class="filter-tab" data-category="agriculture">
|
||||||
|
<i class="fas fa-seedling"></i>
|
||||||
|
<span>农产品</span>
|
||||||
|
</button>
|
||||||
|
<button class="filter-tab" data-category="finance">
|
||||||
|
<i class="fas fa-chart-pie"></i>
|
||||||
|
<span>金融</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="filter-actions">
|
||||||
|
<button class="refresh-all-btn" id="refresh-all-btn" title="刷新全部品种">
|
||||||
|
<i class="fas fa-sync-alt"></i>
|
||||||
|
<span>刷新全部</span>
|
||||||
|
</button>
|
||||||
|
<button class="ai-analyze-all-btn" id="ai-analyze-all-btn" title="AI分析全部品种">
|
||||||
|
<i class="fas fa-brain"></i>
|
||||||
|
<span>全部分析</span>
|
||||||
|
</button>
|
||||||
|
<div class="sort-select">
|
||||||
|
<select id="sort-select">
|
||||||
|
<option value="trend_score">趋势评分</option>
|
||||||
|
<option value="success_rate">成功率</option>
|
||||||
|
<option value="change_pct">涨跌幅</option>
|
||||||
|
<option value="name">名称</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计概览 -->
|
||||||
|
<div class="stats-overview">
|
||||||
|
<div class="stat-card" onclick="filterByTrend('all')" style="cursor: pointer;" title="显示全部">
|
||||||
|
<div class="stat-icon"><i class="fas fa-layer-group"></i></div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<span class="stat-value" id="total-count">0</span>
|
||||||
|
<span class="stat-label">监控品种</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card up" onclick="filterByTrend('up')" style="cursor: pointer;" title="筛选上涨趋势">
|
||||||
|
<div class="stat-icon"><i class="fas fa-arrow-trend-up"></i></div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<span class="stat-value" id="up-count">0</span>
|
||||||
|
<span class="stat-label">上涨趋势</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card down" onclick="filterByTrend('down')" style="cursor: pointer;" title="筛选下跌趋势">
|
||||||
|
<div class="stat-icon"><i class="fas fa-arrow-trend-down"></i></div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<span class="stat-value" id="down-count">0</span>
|
||||||
|
<span class="stat-label">下跌趋势</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card neutral" onclick="filterByTrend('neutral')" style="cursor: pointer;" title="筛选震荡整理">
|
||||||
|
<div class="stat-icon"><i class="fas fa-arrows-left-right"></i></div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<span class="stat-value" id="neutral-count">0</span>
|
||||||
|
<span class="stat-label">震荡整理</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 品种卡片网格 -->
|
||||||
|
<div id="futures-grid" class="futures-grid">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情分析视图 -->
|
||||||
|
<div id="detail-view" class="view">
|
||||||
|
<!-- 返回按钮 -->
|
||||||
|
<div class="detail-actions">
|
||||||
|
<button class="back-btn" id="back-btn">
|
||||||
|
<i class="fas fa-arrow-left"></i>
|
||||||
|
<span>返回</span>
|
||||||
|
</button>
|
||||||
|
<button class="refresh-btn" id="refresh-symbol-btn" title="刷新合约数据">
|
||||||
|
<i class="fas fa-sync-alt"></i>
|
||||||
|
<span>刷新数据</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 品种标题区 -->
|
||||||
|
<div class="detail-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<div class="symbol-info">
|
||||||
|
<span class="symbol-name" id="detail-name">--</span>
|
||||||
|
<span class="symbol-code" id="detail-symbol">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="price-main">
|
||||||
|
<span class="price-value" id="detail-price">--</span>
|
||||||
|
<span class="price-change" id="detail-change">--</span>
|
||||||
|
<div class="price-levels">
|
||||||
|
<span class="level-tag resistance" id="detail-r1">R1: --</span>
|
||||||
|
<span class="level-tag support" id="detail-s1">S1: --</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<div class="quote-grid">
|
||||||
|
<div class="quote-item">
|
||||||
|
<span class="quote-label">开盘</span>
|
||||||
|
<span class="quote-value" id="detail-open">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="quote-item">
|
||||||
|
<span class="quote-label">最高</span>
|
||||||
|
<span class="quote-value up" id="detail-high">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="quote-item">
|
||||||
|
<span class="quote-label">最低</span>
|
||||||
|
<span class="quote-value down" id="detail-low">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="quote-item">
|
||||||
|
<span class="quote-label">成交量</span>
|
||||||
|
<span class="quote-value" id="detail-volume">--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 周期选择 -->
|
||||||
|
<div class="period-bar">
|
||||||
|
<span class="period-label"><i class="fas fa-clock"></i> 周期</span>
|
||||||
|
<div class="period-btns">
|
||||||
|
<button class="period-btn" data-period="5">5M</button>
|
||||||
|
<button class="period-btn active" data-period="15">15M</button>
|
||||||
|
<button class="period-btn" data-period="30">30M</button>
|
||||||
|
<button class="period-btn" data-period="60">1H</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图表和侧边栏 -->
|
||||||
|
<div class="detail-body">
|
||||||
|
<div class="chart-section">
|
||||||
|
<!-- K线图表区 -->
|
||||||
|
<div class="chart-container">
|
||||||
|
<div class="chart-header">
|
||||||
|
<span class="chart-title">K线图</span>
|
||||||
|
<div class="chart-legend">
|
||||||
|
<span class="legend-item"><span class="legend-dot ma5"></span>MA5</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot ma10"></span>MA10</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot ma20"></span>MA20</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="kline-chart" class="kline-chart"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 历史分析记录 -->
|
||||||
|
<div class="history-container">
|
||||||
|
<div class="history-header">
|
||||||
|
<i class="fas fa-clock-rotate-left"></i>
|
||||||
|
<span>历史分析记录</span>
|
||||||
|
</div>
|
||||||
|
<div class="history-list" id="history-list">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧分析面板 -->
|
||||||
|
<div class="analysis-sidebar">
|
||||||
|
<!-- AI智能分析 -->
|
||||||
|
<div class="panel-card ai-analysis-card" id="ai-analysis-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<i class="fas fa-brain"></i>
|
||||||
|
<span>AI 思维分析</span>
|
||||||
|
<div class="panel-header-actions">
|
||||||
|
<button class="ai-analyze-btn" id="ai-analyze-btn" onclick="runAIAnalysis()" title="执行AI分析">
|
||||||
|
<i class="fas fa-play"></i>
|
||||||
|
<span>智能分析</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ai-analysis-content" id="ai-analysis-content">
|
||||||
|
<div class="ai-analysis-placeholder">
|
||||||
|
<i class="fas fa-brain"></i>
|
||||||
|
<p>点击"智能分析"按钮获取AI分析结果</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 技术指标 -->
|
||||||
|
<div class="panel-card indicators-card">
|
||||||
|
<div class="panel-header">
|
||||||
|
<i class="fas fa-wave-pulse"></i>
|
||||||
|
<span>技术指标</span>
|
||||||
|
</div>
|
||||||
|
<div class="indicators-grid">
|
||||||
|
<div class="indicator-cell">
|
||||||
|
<span class="indicator-label">MACD</span>
|
||||||
|
<span class="indicator-value" id="macd-signal">--</span>
|
||||||
|
<span class="indicator-detail" id="macd-detail">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="indicator-cell">
|
||||||
|
<span class="indicator-label">RSI</span>
|
||||||
|
<span class="indicator-value" id="rsi-value">--</span>
|
||||||
|
<span class="indicator-detail" id="rsi-status">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="indicator-cell">
|
||||||
|
<span class="indicator-label">BOLL</span>
|
||||||
|
<span class="indicator-value" id="boll-signal">--</span>
|
||||||
|
<span class="indicator-detail" id="boll-detail">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="indicator-cell">
|
||||||
|
<span class="indicator-label">KDJ</span>
|
||||||
|
<span class="indicator-value" id="kdj-signal">--</span>
|
||||||
|
<span class="indicator-detail" id="kdj-detail">--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 关键点位 -->
|
||||||
|
<div class="panel-card levels-card">
|
||||||
|
<div class="panel-header">
|
||||||
|
<i class="fas fa-crosshairs"></i>
|
||||||
|
<span>关键点位</span>
|
||||||
|
</div>
|
||||||
|
<div class="levels-container">
|
||||||
|
<div class="level-group resistance">
|
||||||
|
<span class="level-group-label">压力</span>
|
||||||
|
<div class="level-item" id="resistance-1"><span>R1</span><span>--</span></div>
|
||||||
|
<div class="level-item" id="resistance-2"><span>R2</span><span>--</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="level-divider"></div>
|
||||||
|
<div class="level-item pivot-point" id="pivot-point">
|
||||||
|
<span>中枢 (PP)</span>
|
||||||
|
<span>--</span>
|
||||||
|
</div>
|
||||||
|
<div class="level-divider"></div>
|
||||||
|
<div class="level-group support">
|
||||||
|
<span class="level-group-label">支撑</span>
|
||||||
|
<div class="level-item" id="support-1"><span>S1</span><span>--</span></div>
|
||||||
|
<div class="level-item" id="support-2"><span>S2</span><span>--</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 多周期趋势 -->
|
||||||
|
<div class="panel-card trends-card">
|
||||||
|
<div class="panel-header">
|
||||||
|
<i class="fas fa-timeline"></i>
|
||||||
|
<span>多周期趋势</span>
|
||||||
|
</div>
|
||||||
|
<div class="trends-container" id="period-trends">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 情景预案 -->
|
||||||
|
<div class="panel-card scenario-card" id="scenario-panel" style="display:none;">
|
||||||
|
<div class="panel-header">
|
||||||
|
<i class="fas fa-chess"></i>
|
||||||
|
<span>情景预案</span>
|
||||||
|
</div>
|
||||||
|
<div class="scenario-container" id="scenario-plans">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- AI分析详情对话框 -->
|
||||||
|
<div class="modal-overlay" id="ai-analysis-modal">
|
||||||
|
<div class="modal-content modal-large">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><i class="fas fa-brain"></i> AI 四维联合分析报告</h3>
|
||||||
|
<button class="modal-close" onclick="closeModal('ai-analysis-modal')"><i class="fas fa-xmark"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="ai-analysis-modal-body">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- AI建议详情对话框 -->
|
||||||
|
<div class="modal-overlay" id="suggestion-modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><i class="fas fa-robot"></i> AI 交易建议详情</h3>
|
||||||
|
<button class="modal-close" onclick="closeModal('suggestion-modal')"><i class="fas fa-xmark"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="suggestion-modal-body">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 历史记录详情对话框 -->
|
||||||
|
<div class="modal-overlay" id="history-modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><i class="fas fa-clock-rotate-left"></i> 分析记录详情</h3>
|
||||||
|
<button class="modal-close" onclick="closeModal('history-modal')"><i class="fas fa-xmark"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="history-modal-body">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||||
|
<script src="/static/futures_analysis.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
db_path = 'data/futures_analysis.db'
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
|
||||||
|
print('=== AI模型配置 ===')
|
||||||
|
cursor = conn.execute("SELECT * FROM ai_model_configs")
|
||||||
|
cols = [desc[0] for desc in cursor.description]
|
||||||
|
print(f'字段: {cols}')
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
print(f'记录数: {len(rows)}')
|
||||||
|
for row in rows:
|
||||||
|
print(dict(zip(cols, row)))
|
||||||
|
|
||||||
|
print('\n=== 分析设置 ===')
|
||||||
|
cursor = conn.execute("SELECT * FROM analysis_settings")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
print(row)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
conn = sqlite3.connect('data/futures_analysis.db')
|
||||||
|
cursor = conn.execute('SELECT id, symbol, created_at FROM ai_analysis_cache ORDER BY created_at DESC LIMIT 10')
|
||||||
|
|
||||||
|
print('AI分析缓存记录:')
|
||||||
|
print('ID | 合约 | 创建时间')
|
||||||
|
print('-' * 60)
|
||||||
|
for row in cursor:
|
||||||
|
print(f'{row[0]} | {row[1]} | {row[2]}')
|
||||||
|
|
||||||
|
conn.close()
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
|
||||||
|
db_path = 'data/futures_analysis.db'
|
||||||
|
|
||||||
|
print(f'数据库文件路径: {os.path.abspath(db_path)}')
|
||||||
|
print(f'数据库存在: {os.path.exists(db_path)}')
|
||||||
|
|
||||||
|
if os.path.exists(db_path):
|
||||||
|
print(f'数据库大小: {os.path.getsize(db_path)} 字节')
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
||||||
|
tables = [row[0] for row in cursor.fetchall()]
|
||||||
|
print(f'数据库表列表: {tables}')
|
||||||
|
|
||||||
|
if 'ai_analysis_cache' in tables:
|
||||||
|
cursor = conn.execute("SELECT COUNT(*) FROM ai_analysis_cache")
|
||||||
|
count = cursor.fetchone()[0]
|
||||||
|
print(f'AI分析记录数: {count}')
|
||||||
|
|
||||||
|
cursor = conn.execute("SELECT id, symbol, created_at FROM ai_analysis_cache ORDER BY created_at DESC LIMIT 5")
|
||||||
|
records = cursor.fetchall()
|
||||||
|
print(f'最近5条记录:')
|
||||||
|
for r in records:
|
||||||
|
print(f' ID: {r[0]}, 合约: {r[1]}, 时间: {r[2]}')
|
||||||
|
else:
|
||||||
|
print('❌ ai_analysis_cache 表不存在!')
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
else:
|
||||||
|
print('❌ 数据库文件不存在!')
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"model_name": "qwen3.6-plus",
|
||||||
|
"provider": "bailian",
|
||||||
|
"api_key": "sk-sp-51d0695ab1114470b913146d21baf68f",
|
||||||
|
"api_base": "https://coding.dashscope.aliyuncs.com/v1",
|
||||||
|
"model_id": "qwen3.6-plus",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 2000,
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"active_model": "bailian",
|
||||||
|
"analysis_settings": {
|
||||||
|
"enable_technical_analysis": true,
|
||||||
|
"enable_fundamental_analysis": false,
|
||||||
|
"enable_sentiment_analysis": false,
|
||||||
|
"risk_tolerance": "medium",
|
||||||
|
"max_position_pct": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@ -0,0 +1,24 @@
|
|||||||
|
"""
|
||||||
|
手动初始化期货智析数据库
|
||||||
|
用于在应用外部创建数据库表
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 添加项目根目录到 Python 路径
|
||||||
|
project_root = Path(__file__).parent
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
|
from app.analysis_db import init_analysis_db
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("开始初始化期货智析数据库...")
|
||||||
|
init_analysis_db()
|
||||||
|
print("✅ 数据库初始化完成!")
|
||||||
|
|
||||||
|
# 验证数据库文件是否创建
|
||||||
|
from app.analysis_db import ANALYSIS_DB_PATH
|
||||||
|
if ANALYSIS_DB_PATH.exists():
|
||||||
|
print(f"✅ 数据库文件已创建: {ANALYSIS_DB_PATH}")
|
||||||
|
else:
|
||||||
|
print(f"❌ 数据库文件未创建: {ANALYSIS_DB_PATH}")
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
"""
|
||||||
|
更新AI分析缓存表结构
|
||||||
|
添加 kline_timestamp 字段
|
||||||
|
"""
|
||||||
|
import sqlite3
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
project_root = Path(__file__).parent
|
||||||
|
ANALYSIS_DB_PATH = project_root / "data" / "futures_analysis.db"
|
||||||
|
|
||||||
|
def add_kline_timestamp_column():
|
||||||
|
"""添加 kline_timestamp 列"""
|
||||||
|
conn = sqlite3.connect(str(ANALYSIS_DB_PATH))
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# 检查列是否已存在
|
||||||
|
cursor.execute("PRAGMA table_info(ai_analysis_cache)")
|
||||||
|
columns = [col[1] for col in cursor.fetchall()]
|
||||||
|
|
||||||
|
if 'kline_timestamp' in columns:
|
||||||
|
print("✅ kline_timestamp 列已存在")
|
||||||
|
else:
|
||||||
|
print("添加 kline_timestamp 列...")
|
||||||
|
cursor.execute("ALTER TABLE ai_analysis_cache ADD COLUMN kline_timestamp DATETIME")
|
||||||
|
conn.commit()
|
||||||
|
print("✅ kline_timestamp 列添加成功")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
add_kline_timestamp_column()
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
"""
|
||||||
|
验证数据库表是否正确创建
|
||||||
|
"""
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
db_path = "data/futures_analysis.db"
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.execute('SELECT name FROM sqlite_master WHERE type="table"')
|
||||||
|
|
||||||
|
print("✅ 数据库表列表:")
|
||||||
|
for row in cursor:
|
||||||
|
print(f" - {row[0]}")
|
||||||
|
|
||||||
|
# 检查 ai_analysis_cache 表是否存在
|
||||||
|
cursor.execute('SELECT name FROM sqlite_master WHERE type="table" AND name="ai_analysis_cache"')
|
||||||
|
if cursor.fetchone():
|
||||||
|
print("\n✅ ai_analysis_cache 表已存在!")
|
||||||
|
# 显示表结构
|
||||||
|
cursor.execute("PRAGMA table_info(ai_analysis_cache)")
|
||||||
|
print("\n表结构:")
|
||||||
|
for col in cursor:
|
||||||
|
print(f" {col[1]} ({col[2]})")
|
||||||
|
else:
|
||||||
|
print("\n❌ ai_analysis_cache 表不存在!")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
Loading…
Reference in new issue