Trae Assistant commited on
Commit
038fa15
·
0 Parent(s):

Initial commit with SiliconFlow integration

Browse files
Files changed (5) hide show
  1. Dockerfile +18 -0
  2. README.md +44 -0
  3. app.py +177 -0
  4. requirements.txt +4 -0
  5. templates/index.html +342 -0
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ # Create a non-root user for security (optional but recommended for Spaces)
11
+ RUN useradd -m -u 1000 user
12
+ USER user
13
+ ENV HOME=/home/user \
14
+ PATH=/home/user/.local/bin:$PATH
15
+
16
+ EXPOSE 7860
17
+
18
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: 知识提炼与资产化智能体
3
+ emoji: ⛏️
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ pinned: false
8
+ short_description: 深度挖掘信息,自动生成商业洞察与知识资产的 AI 智能体
9
+ ---
10
+
11
+ # 知识提炼与资产化智能体 (Knowledge Refinery & Assetization Agent)
12
+
13
+ ## 项目简介
14
+ 这是一个面向商业场景的高生产力 AI Agent,旨在解决信息过载问题。它能够从海量非结构化数据(文本、URL)中“挖掘”关键洞察,并将其“提炼”为高价值的结构化知识资产(如深度报告、思维导图、趋势雷达)。
15
+
16
+ ## 核心功能
17
+ - **深度挖掘 (Deep Mining)**: 自动分析输入源(文章、网页),提取核心观点。
18
+ - **商业价值评估**: 基于多维指标(商业潜力、技术可行性、市场热度)对信息进行评分。
19
+ - **资产生成 (Assetization)**: 将分析结果转化为可交付的资产(PDF 报告、结构化 JSON)。
20
+ - **趋势雷达**: 可视化展示信息的价值分布。
21
+
22
+ ## 技术栈
23
+ - **Backend**: Python Flask (Integrated with SiliconFlow API)
24
+ - **Frontend**: Vue.js 3 + Tailwind CSS (Mobile-First Design)
25
+ - **Visualization**: Apache ECharts
26
+ - **Deployment**: Docker (Compatible with Hugging Face Spaces)
27
+
28
+ ## 商业应用场景
29
+ 1. **投资研究**: 快速筛选高价值标的与赛道。
30
+ 2. **内容创作**: 自动生成深度选题与素材库。
31
+ 3. **竞争情报**: 实时监控竞对动态与市场风向。
32
+
33
+ ## 运行方式
34
+ ```bash
35
+ # 本地运行
36
+ pip install -r requirements.txt
37
+ python app.py
38
+ ```
39
+
40
+ ## Docker 部署
41
+ ```bash
42
+ docker build -t knowledge-refinery .
43
+ docker run -p 7860:7860 knowledge-refinery
44
+ ```
app.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import uuid
4
+ import random
5
+ import json
6
+ from flask import Flask, render_template, request, jsonify, send_file
7
+ from openai import OpenAI
8
+ from werkzeug.utils import secure_filename
9
+
10
+ app = Flask(__name__)
11
+ app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # Increased to 50MB
12
+ app.config['UPLOAD_FOLDER'] = '/tmp/uploads'
13
+
14
+ if not os.path.exists(app.config['UPLOAD_FOLDER']):
15
+ os.makedirs(app.config['UPLOAD_FOLDER'])
16
+
17
+ # SiliconFlow Configuration
18
+ API_KEY = "sk-vimuseiptfbomzegyuvmebjzooncsqbyjtlddrfodzcdskgi"
19
+ BASE_URL = "https://api.siliconflow.cn/v1"
20
+
21
+ client = OpenAI(
22
+ api_key=API_KEY,
23
+ base_url=BASE_URL
24
+ )
25
+
26
+ # In-memory storage for demo
27
+ KNOWLEDGE_VAULT = []
28
+
29
+ # Mock Data for Fallback
30
+ MOCK_INSIGHTS = [
31
+ "市场需求呈现指数级增长,尤其在亚太地区。",
32
+ "当前技术栈存在明显的性能瓶颈,建议迁移至 Rust 或 Go。",
33
+ "用户留存率与响应速度呈正相关,相关系数 0.85。",
34
+ ]
35
+ MOCK_TAGS = ["高增长", "风险警示", "技术债务", "蓝海市场", "自动化"]
36
+
37
+ @app.route('/')
38
+ def index():
39
+ return render_template('index.html')
40
+
41
+ def analyze_content(content):
42
+ """
43
+ Call SiliconFlow API to analyze content.
44
+ """
45
+ try:
46
+ response = client.chat.completions.create(
47
+ model="Qwen/Qwen2.5-7B-Instruct", # Using a common model
48
+ messages=[
49
+ {"role": "system", "content": """你是一个专业的商业分析师和知识提炼专家。
50
+ 请分析用户提供的内容,并返回一个严格的 JSON 格式响应。
51
+ JSON 结构必须包含以下字段:
52
+ - summary (string): 内容的简明摘要(200字以内)
53
+ - insights (list of strings): 3-5个关键洞察
54
+ - tags (list of strings): 3-5个相关标签
55
+ - metrics (object): 包含 commercial_value (0-100), complexity (0-100), sentiment (0.0-1.0)
56
+
57
+ 只返回 JSON,不要包含 Markdown 格式标记。"""},
58
+ {"role": "user", "content": content}
59
+ ],
60
+ temperature=0.7,
61
+ max_tokens=1000,
62
+ response_format={"type": "json_object"}
63
+ )
64
+
65
+ result_text = response.choices[0].message.content
66
+ # Try to clean markdown code blocks if present
67
+ if "```json" in result_text:
68
+ result_text = result_text.split("```json")[1].split("```")[0].strip()
69
+ elif "```" in result_text:
70
+ result_text = result_text.split("```")[1].split("```")[0].strip()
71
+
72
+ return json.loads(result_text)
73
+ except Exception as e:
74
+ print(f"API Error: {e}")
75
+ # Fallback to mock if API fails
76
+ return {
77
+ "summary": "分析服务暂时不可用或超时,已切换至演示模式。原始内容:" + content[:50] + "...",
78
+ "insights": random.sample(MOCK_INSIGHTS, k=min(3, len(MOCK_INSIGHTS))),
79
+ "tags": random.sample(MOCK_TAGS, k=3),
80
+ "metrics": {
81
+ "commercial_value": random.randint(50, 90),
82
+ "complexity": random.randint(30, 70),
83
+ "sentiment": 0.5
84
+ }
85
+ }
86
+
87
+ @app.route('/api/mine', methods=['POST'])
88
+ def mine_knowledge():
89
+ """
90
+ Extracts info from URL, Text, or File.
91
+ """
92
+ content = ""
93
+ source_type = request.form.get('type', 'text') # Changed to form data for file upload support
94
+
95
+ if source_type == 'file':
96
+ if 'file' not in request.files:
97
+ return jsonify({"error": "No file part"}), 400
98
+ file = request.files['file']
99
+ if file.filename == '':
100
+ return jsonify({"error": "No selected file"}), 400
101
+ if file:
102
+ filename = secure_filename(file.filename)
103
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
104
+ file.save(filepath)
105
+
106
+ # Read file content (Simple text read for now)
107
+ try:
108
+ with open(filepath, 'r', encoding='utf-8') as f:
109
+ content = f.read()
110
+ except UnicodeDecodeError:
111
+ return jsonify({"error": "File encoding not supported. Please upload UTF-8 text files."}), 400
112
+ except Exception as e:
113
+ return jsonify({"error": str(e)}), 500
114
+ else:
115
+ # For JSON requests (text/url), we might need to handle differently or expect form data
116
+ # Let's support both JSON and Form for text/url to be safe, but since we use FormData for file,
117
+ # let's try to unify or check content-type.
118
+ if request.is_json:
119
+ data = request.json
120
+ content = data.get('content', '')
121
+ source_type = data.get('type', 'text')
122
+ else:
123
+ content = request.form.get('content', '')
124
+
125
+ if not content and source_type != 'file':
126
+ return jsonify({"error": "No content provided"}), 400
127
+
128
+ # API Analysis
129
+ analysis_result = analyze_content(content)
130
+
131
+ analysis_id = str(uuid.uuid4())
132
+
133
+ result = {
134
+ "id": analysis_id,
135
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
136
+ "source_type": source_type,
137
+ "summary": analysis_result.get('summary', '无摘要'),
138
+ "insights": analysis_result.get('insights', []),
139
+ "tags": analysis_result.get('tags', []),
140
+ "metrics": analysis_result.get('metrics', {
141
+ "commercial_value": 0,
142
+ "complexity": 0,
143
+ "sentiment": 0.5
144
+ })
145
+ }
146
+
147
+ # Store in vault
148
+ KNOWLEDGE_VAULT.insert(0, result)
149
+
150
+ return jsonify(result)
151
+
152
+ @app.route('/api/vault', methods=['GET'])
153
+ def get_vault():
154
+ """
155
+ Returns stored knowledge assets.
156
+ """
157
+ return jsonify(KNOWLEDGE_VAULT)
158
+
159
+ @app.route('/api/generate_asset', methods=['POST'])
160
+ def generate_asset():
161
+ """
162
+ Simulates generating a downloadable asset (PDF/Markdown).
163
+ """
164
+ data = request.json
165
+ asset_id = data.get('id')
166
+ asset_type = data.get('type', 'report')
167
+
168
+ time.sleep(1)
169
+
170
+ return jsonify({
171
+ "status": "success",
172
+ "message": f"{asset_type.upper()} 资产已生成",
173
+ "download_url": "#" # Mock URL
174
+ })
175
+
176
+ if __name__ == '__main__':
177
+ app.run(host='0.0.0.0', port=7860, debug=True)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Flask==3.0.0
2
+ gunicorn==21.2.0
3
+ python-dotenv==1.0.0
4
+ openai>=1.0.0
templates/index.html ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>知识提炼与资产化智能体 | Knowledge Refinery Agent</title>
7
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
11
+ <style>
12
+ body { font-family: 'Inter', sans-serif; background-color: #f8fafc; color: #1e293b; }
13
+ .card { background: white; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border: 1px solid #e2e8f0; }
14
+ .btn-primary { background-color: #0f172a; color: white; transition: all 0.2s; }
15
+ .btn-primary:hover { background-color: #334155; }
16
+ .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
17
+ .tag { @apply px-2 py-1 rounded-full text-xs font-medium; }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <div id="app" class="min-h-screen flex flex-col">
22
+ <!-- Header -->
23
+ <header class="bg-white border-b border-gray-200 sticky top-0 z-50">
24
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
25
+ <div class="flex items-center gap-2">
26
+ <div class="w-8 h-8 bg-slate-900 rounded-lg flex items-center justify-center text-white font-bold">K</div>
27
+ <h1 class="text-xl font-semibold text-slate-900 tracking-tight">知识提炼与资产化智能体</h1>
28
+ </div>
29
+ <div class="text-sm text-slate-500 hidden sm:block">v1.0.0 | Powered by Mock AI</div>
30
+ </div>
31
+ </header>
32
+
33
+ <main class="flex-1 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 w-full grid grid-cols-1 lg:grid-cols-12 gap-8">
34
+
35
+ <!-- Left Column: Input & Controls -->
36
+ <div class="lg:col-span-4 space-y-6">
37
+ <!-- Input Card -->
38
+ <div class="card p-6">
39
+ <h2 class="text-lg font-medium mb-4 flex items-center gap-2">
40
+ <svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"></path></svg>
41
+ 输入源挖掘
42
+ </h2>
43
+
44
+ <div class="flex gap-2 mb-4 bg-slate-100 p-1 rounded-lg">
45
+ <button @click="inputType = 'text'" :class="{'bg-white shadow text-slate-900': inputType === 'text', 'text-slate-500': inputType !== 'text'}" class="flex-1 py-1.5 px-3 rounded-md text-sm font-medium transition-all">文本内容</button>
46
+ <button @click="inputType = 'url'" :class="{'bg-white shadow text-slate-900': inputType === 'url', 'text-slate-500': inputType !== 'url'}" class="flex-1 py-1.5 px-3 rounded-md text-sm font-medium transition-all">URL 链接</button>
47
+ <button @click="inputType = 'file'" :class="{'bg-white shadow text-slate-900': inputType === 'file', 'text-slate-500': inputType !== 'file'}" class="flex-1 py-1.5 px-3 rounded-md text-sm font-medium transition-all">文件上传</button>
48
+ </div>
49
+
50
+ <div class="space-y-4">
51
+ <div v-if="inputType === 'text'">
52
+ <label class="block text-sm font-medium text-slate-700 mb-1">原始文本</label>
53
+ <textarea v-model="inputText" rows="6" class="w-full rounded-lg border-gray-300 shadow-sm focus:border-slate-500 focus:ring-slate-500 text-sm p-3 border" placeholder="输入需要分析的文本..."></textarea>
54
+ </div>
55
+ <div v-else-if="inputType === 'url'">
56
+ <label class="block text-sm font-medium text-slate-700 mb-1">目标 URL</label>
57
+ <input v-model="inputUrl" type="text" class="w-full rounded-lg border-gray-300 shadow-sm focus:border-slate-500 focus:ring-slate-500 text-sm p-3 border" placeholder="https://example.com/article">
58
+ </div>
59
+ <div v-else>
60
+ <label class="block text-sm font-medium text-slate-700 mb-1">上传文件 (支持 .txt, .md)</label>
61
+ <div @click="triggerUpload" class="border-2 border-dashed border-slate-300 rounded-lg p-6 text-center hover:border-slate-500 cursor-pointer transition-colors bg-slate-50">
62
+ <input type="file" ref="fileInput" @change="handleFileChange" class="hidden">
63
+ <div v-if="selectedFile" class="text-sm text-slate-900 font-medium flex items-center justify-center gap-2">
64
+ <svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
65
+ ${ selectedFile.name }
66
+ </div>
67
+ <div v-else class="text-slate-500 text-sm">
68
+ <p>点击选择文件或拖拽至此处</p>
69
+ <p class="text-xs mt-1 text-slate-400">最大支持 50MB</p>
70
+ </div>
71
+ </div>
72
+ </div>
73
+
74
+ <button @click="startMining" :disabled="isProcessing" class="w-full btn-primary py-2.5 rounded-lg font-medium flex items-center justify-center gap-2">
75
+ <span v-if="isProcessing">
76
+ <svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
77
+ 深度挖掘中...
78
+ </span>
79
+ <span v-else>开始提炼资产</span>
80
+ </button>
81
+ </div>
82
+ </div>
83
+
84
+ <!-- History / Vault Preview -->
85
+ <div class="card p-6">
86
+ <h2 class="text-lg font-medium mb-4">资产库</h2>
87
+ <div v-if="vault.length === 0" class="text-center text-slate-400 py-8 text-sm">
88
+ 暂无提炼资产
89
+ </div>
90
+ <ul v-else class="space-y-3">
91
+ <li v-for="item in vault" :key="item.id" @click="loadItem(item)" class="p-3 hover:bg-slate-50 rounded-lg cursor-pointer border border-transparent hover:border-slate-200 transition-all">
92
+ <div class="flex justify-between items-start mb-1">
93
+ <span class="text-xs font-mono text-slate-400">${ item.timestamp.split(' ')[1] }</span>
94
+ <span class="tag bg-blue-100 text-blue-700">${ item.metrics.commercial_value }分</span>
95
+ </div>
96
+ <div class="text-sm text-slate-700 line-clamp-2">${ item.summary }</div>
97
+ </li>
98
+ </ul>
99
+ </div>
100
+ </div>
101
+
102
+ <!-- Right Column: Results & Visualization -->
103
+ <div class="lg:col-span-8 space-y-6">
104
+ <!-- Welcome State -->
105
+ <div v-if="!currentAnalysis" class="h-full flex flex-col items-center justify-center text-center p-12 border-2 border-dashed border-slate-200 rounded-xl bg-slate-50/50">
106
+ <div class="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mb-4">
107
+ <svg class="w-8 h-8 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
108
+ </div>
109
+ <h3 class="text-lg font-medium text-slate-900">准备就绪</h3>
110
+ <p class="text-slate-500 max-w-md mt-2">输入文本或 URL,智能体将自动提炼关键信息、评估商业价值并生成结构化知识资产。</p>
111
+ </div>
112
+
113
+ <!-- Analysis Result -->
114
+ <div v-else class="space-y-6">
115
+ <!-- Metrics & Summary -->
116
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
117
+ <div class="card p-4 flex flex-col items-center justify-center text-center">
118
+ <div class="text-sm text-slate-500 mb-1">商业价值评分</div>
119
+ <div class="text-3xl font-bold text-slate-900">${ currentAnalysis.metrics.commercial_value }</div>
120
+ <div class="w-full bg-gray-200 rounded-full h-1.5 mt-3">
121
+ <div class="bg-slate-900 h-1.5 rounded-full" :style="{ width: currentAnalysis.metrics.commercial_value + '%' }"></div>
122
+ </div>
123
+ </div>
124
+ <div class="card p-4 flex flex-col items-center justify-center text-center">
125
+ <div class="text-sm text-slate-500 mb-1">信息复杂度</div>
126
+ <div class="text-3xl font-bold text-slate-900">${ currentAnalysis.metrics.complexity }</div>
127
+ <div class="text-xs text-slate-400 mt-1">Mock Analysis</div>
128
+ </div>
129
+ <div class="card p-4 flex flex-col items-center justify-center text-center">
130
+ <div class="text-sm text-slate-500 mb-1">情感倾向</div>
131
+ <div class="text-3xl font-bold text-green-600" v-if="currentAnalysis.metrics.sentiment > 0.6">积极</div>
132
+ <div class="text-3xl font-bold text-yellow-600" v-else-if="currentAnalysis.metrics.sentiment > 0.4">中性</div>
133
+ <div class="text-3xl font-bold text-red-600" v-else>消极</div>
134
+ </div>
135
+ </div>
136
+
137
+ <!-- Main Report -->
138
+ <div class="card p-6">
139
+ <div class="flex justify-between items-center mb-6">
140
+ <h2 class="text-xl font-bold text-slate-900">提炼报告</h2>
141
+ <div class="flex gap-2">
142
+ <button @click="generateAsset('report')" class="text-sm border border-slate-300 px-3 py-1.5 rounded hover:bg-slate-50">下载 PDF</button>
143
+ <button @click="generateAsset('mindmap')" class="text-sm border border-slate-300 px-3 py-1.5 rounded hover:bg-slate-50">导出脑图</button>
144
+ </div>
145
+ </div>
146
+
147
+ <div class="bg-slate-50 p-4 rounded-lg border border-slate-100 mb-6">
148
+ <h3 class="text-sm font-semibold text-slate-700 uppercase tracking-wider mb-2">摘要</h3>
149
+ <p class="text-slate-600 leading-relaxed">${ currentAnalysis.summary }</p>
150
+ </div>
151
+
152
+ <div class="mb-6">
153
+ <h3 class="text-sm font-semibold text-slate-700 uppercase tracking-wider mb-3">关键洞察</h3>
154
+ <ul class="space-y-2">
155
+ <li v-for="(insight, index) in currentAnalysis.insights" :key="index" class="flex items-start gap-3">
156
+ <span class="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-xs font-bold">${ index + 1 }</span>
157
+ <span class="text-slate-700">${ insight }</span>
158
+ </li>
159
+ </ul>
160
+ </div>
161
+
162
+ <div>
163
+ <h3 class="text-sm font-semibold text-slate-700 uppercase tracking-wider mb-3">关联标签</h3>
164
+ <div class="flex flex-wrap gap-2">
165
+ <span v-for="tag in currentAnalysis.tags" :key="tag" class="tag bg-slate-100 text-slate-600 border border-slate-200">#${ tag }</span>
166
+ </div>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- Chart -->
171
+ <div class="card p-6">
172
+ <h2 class="text-lg font-medium mb-4">趋势雷达</h2>
173
+ <div ref="radarChart" class="w-full h-64"></div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ </main>
178
+ </div>
179
+
180
+ <script>
181
+ const { createApp, ref, onMounted, nextTick } = Vue;
182
+
183
+ createApp({
184
+ delimiters: ['${', '}'],
185
+ setup() {
186
+ const inputType = ref('text');
187
+ const inputText = ref('2024年 AI 代理(AI Agents)市场正迎来爆发式增长。企业不再仅仅满足于聊天机器人,而是寻求能够执行复杂任务、自主决策的智能体。从软件开发到市场营销,Agent 正在重塑工作流程。然而,数据安全和幻觉问题依然是主要挑战。预计到 2025 年,Agent 市场规模将突破 500 亿美元。');
188
+ const inputUrl = ref('');
189
+ const isProcessing = ref(false);
190
+ const currentAnalysis = ref(null);
191
+ const vault = ref([]);
192
+ const radarChart = ref(null);
193
+ const fileInput = ref(null);
194
+ const selectedFile = ref(null);
195
+ let chartInstance = null;
196
+
197
+ const triggerUpload = () => {
198
+ fileInput.value.click();
199
+ };
200
+
201
+ const handleFileChange = (event) => {
202
+ const file = event.target.files[0];
203
+ if (file) {
204
+ if (file.size > 50 * 1024 * 1024) {
205
+ alert('文件大小超过 50MB 限制');
206
+ event.target.value = '';
207
+ return;
208
+ }
209
+ selectedFile.value = file;
210
+ }
211
+ };
212
+
213
+ const startMining = async () => {
214
+ isProcessing.value = true;
215
+ try {
216
+ let response;
217
+
218
+ if (inputType.value === 'file') {
219
+ if (!selectedFile.value) {
220
+ alert('请先选择文件');
221
+ isProcessing.value = false;
222
+ return;
223
+ }
224
+ const formData = new FormData();
225
+ formData.append('file', selectedFile.value);
226
+ formData.append('type', 'file');
227
+
228
+ response = await fetch('/api/mine', {
229
+ method: 'POST',
230
+ body: formData
231
+ });
232
+ } else {
233
+ const content = inputType.value === 'text' ? inputText.value : inputUrl.value;
234
+ if (!content) {
235
+ alert('请输入内容');
236
+ isProcessing.value = false;
237
+ return;
238
+ }
239
+ response = await fetch('/api/mine', {
240
+ method: 'POST',
241
+ headers: { 'Content-Type': 'application/json' },
242
+ body: JSON.stringify({ content, type: inputType.value })
243
+ });
244
+ }
245
+
246
+ if (!response.ok) {
247
+ const errorData = await response.json();
248
+ throw new Error(errorData.error || 'Request failed');
249
+ }
250
+
251
+ const data = await response.json();
252
+ currentAnalysis.value = data;
253
+ vault.value.unshift(data);
254
+
255
+ await nextTick();
256
+ initChart();
257
+ } catch (error) {
258
+ console.error("Mining failed", error);
259
+ alert("分析失败: " + error.message);
260
+ } finally {
261
+ isProcessing.value = false;
262
+ }
263
+ };
264
+
265
+ const loadItem = async (item) => {
266
+ currentAnalysis.value = item;
267
+ await nextTick();
268
+ initChart();
269
+ };
270
+
271
+ const generateAsset = async (type) => {
272
+ alert(`正在生成 ${type === 'report' ? 'PDF 报告' : '思维导图'}... \n(Mock Function: Asset Generated)`);
273
+ };
274
+
275
+ const initChart = () => {
276
+ if (!radarChart.value) return;
277
+
278
+ if (chartInstance) {
279
+ chartInstance.dispose();
280
+ }
281
+
282
+ chartInstance = echarts.init(radarChart.value);
283
+ const metrics = currentAnalysis.value.metrics;
284
+
285
+ const option = {
286
+ radar: {
287
+ indicator: [
288
+ { name: '商业价值', max: 100 },
289
+ { name: '技术可行性', max: 100 },
290
+ { name: '市场热度', max: 100 },
291
+ { name: '合规风险', max: 100 },
292
+ { name: '创新度', max: 100 }
293
+ ],
294
+ radius: '70%'
295
+ },
296
+ series: [{
297
+ name: 'Asset Analysis',
298
+ type: 'radar',
299
+ data: [{
300
+ value: [
301
+ metrics.commercial_value,
302
+ Math.random() * 40 + 60,
303
+ Math.random() * 40 + 60,
304
+ 100 - metrics.complexity,
305
+ Math.random() * 50 + 50
306
+ ],
307
+ name: '当前项目',
308
+ itemStyle: { color: '#0f172a' },
309
+ areaStyle: { opacity: 0.2 }
310
+ }]
311
+ }]
312
+ };
313
+ chartInstance.setOption(option);
314
+ window.addEventListener('resize', () => chartInstance.resize());
315
+ };
316
+
317
+ onMounted(() => {
318
+ // Optional: Fetch initial history
319
+ // fetch('/api/vault').then(r => r.json()).then(d => vault.value = d);
320
+ });
321
+
322
+ return {
323
+ inputType,
324
+ inputText,
325
+ inputUrl,
326
+ isProcessing,
327
+ currentAnalysis,
328
+ vault,
329
+ radarChart,
330
+ fileInput,
331
+ selectedFile,
332
+ triggerUpload,
333
+ handleFileChange,
334
+ startMining,
335
+ loadItem,
336
+ generateAsset
337
+ };
338
+ }
339
+ }).mount('#app');
340
+ </script>
341
+ </body>
342
+ </html>