Files

1 line
19 KiB
JSON

{"repo": "chriszhf2019/daily-news-bot", "files": {"render.yaml": {"3f0d4c77ab9b5502615e2bc2647e5ba0bfb66e4f": {"t": 1785745210.1317866, "n": 870, "h": "81b33e85a9999688", "c": "# Render Blueprint — 免费部署 Flask 后端\n# https://render.com\n\nservices:\n - type: web\n name: newsbrief-api\n env: python\n region: singapore # 亚洲延迟最低\n buildCommand: cd backend && pip install -r requirements.txt\n startCommand: cd backend && python -c \"from models import init_database; from config import get_config; engine=init_database(get_config().DATABASE_URL); print('DB ready')\" && gunicorn -w 2 -b 0.0.0.0:$PORT --timeout 60 app:app --chdir backend\n envVars:\n - key: FLASK_ENV\n value: production\n - key: JWT_SECRET_KEY\n generateValue: true\n - key: SECRET_KEY\n generateValue: true\n - key: DATABASE_URL\n sync: false # 从 Neon 控制台获取\n - key: DEEPSEEK_API_KEY\n sync: false # 可选,用于 AI 分析\n - key: CORS_ORIGINS\n value: \"*\"\n - key: LOG_LEVEL\n value: INFO\n autoDeploy: true\n"}, "7e417fc8a2d7101cbfd7a23263e98f1987035bff": {"t": 1785748713.8583689, "n": 870, "h": "81b33e85a9999688", "c": "# Render Blueprint — 免费部署 Flask 后端\n# https://render.com\n\nservices:\n - type: web\n name: newsbrief-api\n env: python\n region: singapore # 亚洲延迟最低\n buildCommand: cd backend && pip install -r requirements.txt\n startCommand: cd backend && python -c \"from models import init_database; from config import get_config; engine=init_database(get_config().DATABASE_URL); print('DB ready')\" && gunicorn -w 2 -b 0.0.0.0:$PORT --timeout 60 app:app --chdir backend\n envVars:\n - key: FLASK_ENV\n value: production\n - key: JWT_SECRET_KEY\n generateValue: true\n - key: SECRET_KEY\n generateValue: true\n - key: DATABASE_URL\n sync: false # 从 Neon 控制台获取\n - key: DEEPSEEK_API_KEY\n sync: false # 可选,用于 AI 分析\n - key: CORS_ORIGINS\n value: \"*\"\n - key: LOG_LEVEL\n value: INFO\n autoDeploy: true\n"}}, "backend/docker-compose.yml": {"3f0d4c77ab9b5502615e2bc2647e5ba0bfb66e4f": {"t": 1785745251.7253628, "n": 1287, "h": "17236fbfd202169f", "c": "services:\n # PostgreSQL数据库\n postgres:\n image: postgres:15\n container_name: news-analysis-postgres\n environment:\n POSTGRES_DB: news_analysis\n POSTGRES_USER: postgres\n POSTGRES_PASSWORD: postgres\n ports:\n - \"5432:5432\"\n volumes:\n - postgres_data:/var/lib/postgresql/data\n networks:\n - news-analysis-network\n\n # Redis缓存\n redis:\n image: redis:7-alpine\n container_name: news-analysis-redis\n ports:\n - \"6379:6379\"\n networks:\n - news-analysis-network\n\n # Flask应用\n web:\n build: .\n container_name: news-analysis-backend\n environment:\n FLASK_ENV: production\n DATABASE_URL: postgresql://postgres:postgres@postgres:5432/news_analysis\n REDIS_URL: redis://redis:6379/0\n JWT_SECRET_KEY: ${JWT_SECRET_KEY:-your-jwt-secret-key-change-in-production}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-your-deepseek-api-key-here}\n DEEPSEEK_API_ENDPOINT: ${DEEPSEEK_API_ENDPOINT:-https://api.deepseek.com/v1}\n LOG_LEVEL: ${LOG_LEVEL:-INFO}\n ports:\n - \"5000:5000\"\n depends_on:\n - postgres\n - redis\n networks:\n - news-analysis-network\n volumes:\n - .:/app\n restart: unless-stopped\n\nnetworks:\n news-analysis-network:\n driver: bridge\n\nvolumes:\n postgres_data:"}, "7e417fc8a2d7101cbfd7a23263e98f1987035bff": {"t": 1785748715.200658, "n": 1287, "h": "17236fbfd202169f", "c": "services:\n # PostgreSQL数据库\n postgres:\n image: postgres:15\n container_name: news-analysis-postgres\n environment:\n POSTGRES_DB: news_analysis\n POSTGRES_USER: postgres\n POSTGRES_PASSWORD: postgres\n ports:\n - \"5432:5432\"\n volumes:\n - postgres_data:/var/lib/postgresql/data\n networks:\n - news-analysis-network\n\n # Redis缓存\n redis:\n image: redis:7-alpine\n container_name: news-analysis-redis\n ports:\n - \"6379:6379\"\n networks:\n - news-analysis-network\n\n # Flask应用\n web:\n build: .\n container_name: news-analysis-backend\n environment:\n FLASK_ENV: production\n DATABASE_URL: postgresql://postgres:postgres@postgres:5432/news_analysis\n REDIS_URL: redis://redis:6379/0\n JWT_SECRET_KEY: ${JWT_SECRET_KEY:-your-jwt-secret-key-change-in-production}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-your-deepseek-api-key-here}\n DEEPSEEK_API_ENDPOINT: ${DEEPSEEK_API_ENDPOINT:-https://api.deepseek.com/v1}\n LOG_LEVEL: ${LOG_LEVEL:-INFO}\n ports:\n - \"5000:5000\"\n depends_on:\n - postgres\n - redis\n networks:\n - news-analysis-network\n volumes:\n - .:/app\n restart: unless-stopped\n\nnetworks:\n news-analysis-network:\n driver: bridge\n\nvolumes:\n postgres_data:"}}, "NewsBrief/Config/APIConfig.swift": {"3f0d4c77ab9b5502615e2bc2647e5ba0bfb66e4f": {"t": 1785745884.410143, "n": 1184, "h": "27e3ec8202bcb329", "c": "//\n// APIConfig.swift\n// NewsBrief\n//\n\nimport Foundation\n\nstruct APIConfig {\n // 后端 API 地址 — 生产环境应替换为实际服务器地址\n static let backendBaseURL: String = {\n if let url = Bundle.main.object(forInfoDictionaryKey: \"BACKEND_BASE_URL\") as? String {\n return url\n }\n #if DEBUG\n return \"http://localhost:5000\"\n #else\n return \"https://api.example.com\"\n #endif\n }()\n\n // DeepSeek API 配置 — 优先从 Info.plist 读取\n static let deepSeekAPIKey: String = {\n if let key = Bundle.main.object(forInfoDictionaryKey: \"DEEPSEEK_API_KEY\") as? String,\n !key.isEmpty, key != \"your_deepseek_api_key_here\" {\n return key\n }\n if let key = ProcessInfo.processInfo.environment[\"DEEPSEEK_API_KEY\"] {\n return key\n }\n return \"\"\n }()\n\n static let deepSeekAPIURL = \"https://api.deepseek.com/v1/chat/completions\"\n static let deepSeekModel = \"deepseek-chat\"\n\n // 请求配置\n static let requestTimeout: TimeInterval = 30\n static let maxTokens = 1000\n static let temperature = 0.7\n\n // 当 API Key 不可用时使用本地 JSON 数据\n static var useMockData: Bool { deepSeekAPIKey.isEmpty }\n}\n"}, "7e417fc8a2d7101cbfd7a23263e98f1987035bff": {"t": 1785748714.8689659, "n": 1184, "h": "27e3ec8202bcb329", "c": "//\n// APIConfig.swift\n// NewsBrief\n//\n\nimport Foundation\n\nstruct APIConfig {\n // 后端 API 地址 — 生产环境应替换为实际服务器地址\n static let backendBaseURL: String = {\n if let url = Bundle.main.object(forInfoDictionaryKey: \"BACKEND_BASE_URL\") as? String {\n return url\n }\n #if DEBUG\n return \"http://localhost:5000\"\n #else\n return \"https://api.example.com\"\n #endif\n }()\n\n // DeepSeek API 配置 — 优先从 Info.plist 读取\n static let deepSeekAPIKey: String = {\n if let key = Bundle.main.object(forInfoDictionaryKey: \"DEEPSEEK_API_KEY\") as? String,\n !key.isEmpty, key != \"your_deepseek_api_key_here\" {\n return key\n }\n if let key = ProcessInfo.processInfo.environment[\"DEEPSEEK_API_KEY\"] {\n return key\n }\n return \"\"\n }()\n\n static let deepSeekAPIURL = \"https://api.deepseek.com/v1/chat/completions\"\n static let deepSeekModel = \"deepseek-chat\"\n\n // 请求配置\n static let requestTimeout: TimeInterval = 30\n static let maxTokens = 1000\n static let temperature = 0.7\n\n // 当 API Key 不可用时使用本地 JSON 数据\n static var useMockData: Bool { deepSeekAPIKey.isEmpty }\n}\n"}}, "web/server/routes/analysis.js": {"3f0d4c77ab9b5502615e2bc2647e5ba0bfb66e4f": {"t": 1785746021.6617897, "n": 4596, "h": "8497393cbcbac455", "c": "import { Router } from 'express'\n\nconst router = Router()\n\n// 获取分析结果(带缓存)\nrouter.get('/:newsId/:type', async (req, res) => {\n try {\n const prisma = req.app.get('prisma')\n const { newsId, type } = req.params\n const { userId } = req.query\n \n // 先查找缓存\n const cached = await prisma.analysis.findFirst({\n where: { newsId, type },\n orderBy: { createdAt: 'desc' }\n })\n \n if (cached) {\n return res.json({ cached: true, result: cached.result })\n }\n \n res.json({ cached: false, result: null })\n } catch (error) {\n console.error('获取分析失败:', error)\n res.status(500).json({ error: '获取分析失败' })\n }\n})\n\n// 保存分析结果\nrouter.post('/:newsId/:type', async (req, res) => {\n try {\n const prisma = req.app.get('prisma')\n const { newsId, type } = req.params\n const { userId, result } = req.body\n \n const analysis = await prisma.analysis.create({\n data: {\n newsId,\n type,\n userId: userId || null,\n result\n }\n })\n \n res.json(analysis)\n } catch (error) {\n console.error('保存分析失败:', error)\n res.status(500).json({ error: '保存分析失败' })\n }\n})\n\n// 调用 DeepSeek API 进行分析\nrouter.post('/analyze', async (req, res) => {\n try {\n const { apiKey, endpoint, newsItem, type } = req.body\n \n if (!apiKey) {\n return res.status(400).json({ error: '缺少 API Key' })\n }\n \n const prompts = {\n 'seven-elements': getSevenElementsPrompt(newsItem),\n 'relevance': getRelevancePrompt(newsItem),\n 'deep-exploration': getDeepExplorationPrompt(newsItem)\n }\n \n const systemPrompts = {\n 'seven-elements': '你是专业的情报审计分析师,擅长对新闻进行多维度审计。只返回JSON,不要有其他文字。',\n 'relevance': '你是专业的私人顾问,正在为用户提供决策建议。只返回JSON。',\n 'deep-exploration': '你是顶级战略智库研究员,擅长因果溯源和战略建模。只返回JSON。'\n }\n \n const response = await fetch(`${endpoint || 'https://api.deepseek.com/v1'}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: 'deepseek-chat',\n messages: [\n { role: 'system', content: systemPrompts[type] },\n { role: 'user', content: prompts[type] }\n ],\n temperature: 0.5,\n max_tokens: 2000\n })\n })\n \n if (!response.ok) {\n throw new Error(`API 请求失败: ${response.status}`)\n }\n \n const data = await response.json()\n const content = data.choices?.[0]?.message?.content || ''\n \n // 解析 JSON\n const cleaned = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim()\n const result = JSON.parse(cleaned)\n \n res.json(result)\n } catch (error) {\n console.error('分析失败:', error)\n res.status(500).json({ error: '分析失败: ' + error.message })\n }\n})\n\n// 七要素分析 Prompt\nfunction getSevenElementsPrompt(news) {\n return `请对以下新闻进行七要素审计分析。\n\n新闻标题:${news.title}\n新闻摘要:${news.summary || ''}\n新闻内容:${news.content || news.summary || ''}\n\n请返回JSON格式:\n{\n \"audit01\": { \"score\": 85, \"verifiedFacts\": [\"事实1\"], \"conflicts\": [\"疑点1\"], \"verdict\": \"判词\" },\n \"audit02\": { \"consensus\": [\"共识1\"], \"gaps\": [\"漏洞1\"], \"alignment\": 78 },\n \"audit03\": { \"keyData\": [{\"item\": \"指标\", \"value\": \"数值\", \"status\": \"verified\"}] },\n \"audit04\": { \"logicPath\": [\"A\", \"B\", \"C\"], \"glossary\": {\"term\": \"术语\", \"explain\": \"解释\"} },\n \"audit05\": { \"redlines\": [{\"point\": \"风险点\", \"level\": \"high\"}], \"impact\": \"影响\" },\n \"audit06\": { \"finalScore\": 82, \"verdictText\": \"点评\", \"actionStar\": 4 },\n \"audit07\": [{\"phase\": \"起源\", \"time\": \"时间\", \"event\": \"事件\"}]\n}`\n}\n\n// 相关性分析 Prompt\nfunction getRelevancePrompt(news) {\n return `请对以下新闻进行相关性分析。\n\n新闻标题:${news.title}\n新闻摘要:${news.summary || ''}\n\n请返回JSON格式:\n{\n \"roi\": {\"score\": 85, \"reason\": \"影响说明\"},\n \"ripple\": {\"upstream\": \"上游影响\", \"downstream\": \"下游影响\", \"rivals\": \"竞争格局\"},\n \"decision\": {\"options\": [{\"name\": \"方案A\", \"pros\": \"优势\", \"cons\": \"劣势\"}]},\n \"pitfall\": [{\"trap\": \"陷阱\", \"avoid\": \"规避方法\"}],\n \"action\": [{\"step\": 1, \"action\": \"行动\", \"deadline\": \"时间\"}],\n \"timing\": {\"window\": \"时间窗口\", \"urgency\": \"high\"},\n \"goldQuote\": \"金句\"\n}`\n}\n\n// 深度探索 Prompt\nfunction getDeepExplorationPrompt(news) {\n return `请对以下新闻进行深度因果溯源分析。\n\n新闻标题:${news.title}\n新闻摘要:${news.summary || ''}\n\n请返回JSON格式:\n{\n \"microAudit\": {\"target\": \"核查目标\", \"logic\": \"核查逻辑\"},\n \"reasoningChain\": [\"推理步骤1\", \"推理步骤2\"],\n \"historyMatch\": {\"event\": \"历史事件\", \"lesson\": \"教训\"},\n \"interestsMap\": [{\"entity\": \"利益方\", \"motivation\": \"动机\"}],\n \"ecosystem\": {\"upstream\": [\"上游\"], \"downstream\": [\"下游\"], \"rivals\": [\"竞争者\"]},\n \"blackSwan\": {\"trigger\": \"触发条件\", \"probability\": \"low\"},\n \"strategicOutlook\": \"战略预言\"\n}`\n}\n\nexport default router\n"}, "7e417fc8a2d7101cbfd7a23263e98f1987035bff": {"t": 1785748715.2254696, "n": 4596, "h": "8497393cbcbac455", "c": "import { Router } from 'express'\n\nconst router = Router()\n\n// 获取分析结果(带缓存)\nrouter.get('/:newsId/:type', async (req, res) => {\n try {\n const prisma = req.app.get('prisma')\n const { newsId, type } = req.params\n const { userId } = req.query\n \n // 先查找缓存\n const cached = await prisma.analysis.findFirst({\n where: { newsId, type },\n orderBy: { createdAt: 'desc' }\n })\n \n if (cached) {\n return res.json({ cached: true, result: cached.result })\n }\n \n res.json({ cached: false, result: null })\n } catch (error) {\n console.error('获取分析失败:', error)\n res.status(500).json({ error: '获取分析失败' })\n }\n})\n\n// 保存分析结果\nrouter.post('/:newsId/:type', async (req, res) => {\n try {\n const prisma = req.app.get('prisma')\n const { newsId, type } = req.params\n const { userId, result } = req.body\n \n const analysis = await prisma.analysis.create({\n data: {\n newsId,\n type,\n userId: userId || null,\n result\n }\n })\n \n res.json(analysis)\n } catch (error) {\n console.error('保存分析失败:', error)\n res.status(500).json({ error: '保存分析失败' })\n }\n})\n\n// 调用 DeepSeek API 进行分析\nrouter.post('/analyze', async (req, res) => {\n try {\n const { apiKey, endpoint, newsItem, type } = req.body\n \n if (!apiKey) {\n return res.status(400).json({ error: '缺少 API Key' })\n }\n \n const prompts = {\n 'seven-elements': getSevenElementsPrompt(newsItem),\n 'relevance': getRelevancePrompt(newsItem),\n 'deep-exploration': getDeepExplorationPrompt(newsItem)\n }\n \n const systemPrompts = {\n 'seven-elements': '你是专业的情报审计分析师,擅长对新闻进行多维度审计。只返回JSON,不要有其他文字。',\n 'relevance': '你是专业的私人顾问,正在为用户提供决策建议。只返回JSON。',\n 'deep-exploration': '你是顶级战略智库研究员,擅长因果溯源和战略建模。只返回JSON。'\n }\n \n const response = await fetch(`${endpoint || 'https://api.deepseek.com/v1'}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: 'deepseek-chat',\n messages: [\n { role: 'system', content: systemPrompts[type] },\n { role: 'user', content: prompts[type] }\n ],\n temperature: 0.5,\n max_tokens: 2000\n })\n })\n \n if (!response.ok) {\n throw new Error(`API 请求失败: ${response.status}`)\n }\n \n const data = await response.json()\n const content = data.choices?.[0]?.message?.content || ''\n \n // 解析 JSON\n const cleaned = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim()\n const result = JSON.parse(cleaned)\n \n res.json(result)\n } catch (error) {\n console.error('分析失败:', error)\n res.status(500).json({ error: '分析失败: ' + error.message })\n }\n})\n\n// 七要素分析 Prompt\nfunction getSevenElementsPrompt(news) {\n return `请对以下新闻进行七要素审计分析。\n\n新闻标题:${news.title}\n新闻摘要:${news.summary || ''}\n新闻内容:${news.content || news.summary || ''}\n\n请返回JSON格式:\n{\n \"audit01\": { \"score\": 85, \"verifiedFacts\": [\"事实1\"], \"conflicts\": [\"疑点1\"], \"verdict\": \"判词\" },\n \"audit02\": { \"consensus\": [\"共识1\"], \"gaps\": [\"漏洞1\"], \"alignment\": 78 },\n \"audit03\": { \"keyData\": [{\"item\": \"指标\", \"value\": \"数值\", \"status\": \"verified\"}] },\n \"audit04\": { \"logicPath\": [\"A\", \"B\", \"C\"], \"glossary\": {\"term\": \"术语\", \"explain\": \"解释\"} },\n \"audit05\": { \"redlines\": [{\"point\": \"风险点\", \"level\": \"high\"}], \"impact\": \"影响\" },\n \"audit06\": { \"finalScore\": 82, \"verdictText\": \"点评\", \"actionStar\": 4 },\n \"audit07\": [{\"phase\": \"起源\", \"time\": \"时间\", \"event\": \"事件\"}]\n}`\n}\n\n// 相关性分析 Prompt\nfunction getRelevancePrompt(news) {\n return `请对以下新闻进行相关性分析。\n\n新闻标题:${news.title}\n新闻摘要:${news.summary || ''}\n\n请返回JSON格式:\n{\n \"roi\": {\"score\": 85, \"reason\": \"影响说明\"},\n \"ripple\": {\"upstream\": \"上游影响\", \"downstream\": \"下游影响\", \"rivals\": \"竞争格局\"},\n \"decision\": {\"options\": [{\"name\": \"方案A\", \"pros\": \"优势\", \"cons\": \"劣势\"}]},\n \"pitfall\": [{\"trap\": \"陷阱\", \"avoid\": \"规避方法\"}],\n \"action\": [{\"step\": 1, \"action\": \"行动\", \"deadline\": \"时间\"}],\n \"timing\": {\"window\": \"时间窗口\", \"urgency\": \"high\"},\n \"goldQuote\": \"金句\"\n}`\n}\n\n// 深度探索 Prompt\nfunction getDeepExplorationPrompt(news) {\n return `请对以下新闻进行深度因果溯源分析。\n\n新闻标题:${news.title}\n新闻摘要:${news.summary || ''}\n\n请返回JSON格式:\n{\n \"microAudit\": {\"target\": \"核查目标\", \"logic\": \"核查逻辑\"},\n \"reasoningChain\": [\"推理步骤1\", \"推理步骤2\"],\n \"historyMatch\": {\"event\": \"历史事件\", \"lesson\": \"教训\"},\n \"interestsMap\": [{\"entity\": \"利益方\", \"motivation\": \"动机\"}],\n \"ecosystem\": {\"upstream\": [\"上游\"], \"downstream\": [\"下游\"], \"rivals\": [\"竞争者\"]},\n \"blackSwan\": {\"trigger\": \"触发条件\", \"probability\": \"low\"},\n \"strategicOutlook\": \"战略预言\"\n}`\n}\n\nexport default router\n"}}}}