Trae Assistant commited on
Commit
135695c
·
0 Parent(s):

feat: enhance skill factory with siliconflow api and file upload

Browse files
Files changed (6) hide show
  1. Dockerfile +17 -0
  2. README.md +45 -0
  3. __pycache__/app.cpython-314.pyc +0 -0
  4. app.py +238 -0
  5. requirements.txt +4 -0
  6. templates/index.html +325 -0
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-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 to run the application (Best Practice)
11
+ RUN useradd -m -u 1000 user
12
+ USER user
13
+ ENV PATH="/home/user/.local/bin:$PATH"
14
+
15
+ EXPOSE 7860
16
+
17
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Skill Factory Agent
3
+ emoji: 🏭
4
+ colorFrom: indigo
5
+ colorTo: blue
6
+ sdk: docker
7
+ pinned: false
8
+ short_description: AI自动化工具生成与管理平台 (AI Automation Tool Factory)
9
+ ---
10
+
11
+ # Skill Factory Agent (技能工坊 Agent)
12
+
13
+ ## 📖 项目介绍
14
+ Skill Factory 是一个面向未来的 AI 自动化工具生成与管理平台。它允许用户通过自然语言描述任务(例如“提取 PDF 表格”或“监控网页价格”),AI Agent 会自动生成相应的 Python 工具代码(Skill),并在沙箱环境中进行测试和验证。
15
+
16
+ 生成的工具会被保存到“技能资产库”中,形成可复用、可交易的数字资产。
17
+
18
+ ## 🚀 核心功能
19
+ - **AI 技能生成器**: 输入需求,一键生成 Python 自动化脚本。
20
+ - **技能资产库**: 管理、搜索和复用已生成的工具。
21
+ - **模拟执行沙箱**: 在线测试生成的代码,查看运行结果。
22
+ - **商业化仪表盘**: 追踪技能调用次数和虚拟收益。
23
+
24
+ ## 🛠️ 技术栈
25
+ - **Backend**: Python Flask
26
+ - **Frontend**: Vue.js 3 + Tailwind CSS
27
+ - **Visualization**: Apache ECharts
28
+ - **Deployment**: Docker / Hugging Face Spaces
29
+
30
+ ## 💡 使用说明
31
+ 1. 在左侧输入框描述你想要自动化的任务。
32
+ 2. 点击“立即生成”,等待 AI 构建代码。
33
+ 3. 在代码预览区查看生成的 Python 脚本。
34
+ 4. 点击“运行测试”验证功能。
35
+ 5. 在右侧市场查看和加载其他热门技能。
36
+
37
+ ## 📦 本地运行
38
+ ```bash
39
+ docker build -t skill-factory .
40
+ docker run -p 7860:7860 skill-factory
41
+ ```
42
+ 打开浏览器访问 `http://localhost:7860`
43
+
44
+ ## ⚖️ 商业前景
45
+ 本项目旨在解决“长尾自动化需求”,通过 AI 降低开发门槛,积累高价值的自动化脚本库,未来可转型为 API 市场或 Rouss (Robot-as-a-Service) 平台。
__pycache__/app.cpython-314.pyc ADDED
Binary file (5.39 kB). View file
 
app.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import time
4
+ import random
5
+ import requests
6
+ from flask import Flask, jsonify, request, render_template
7
+ from werkzeug.utils import secure_filename
8
+
9
+ app = Flask(__name__, template_folder='templates', static_folder='static')
10
+
11
+ # Configuration
12
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max upload
13
+ app.config['UPLOAD_FOLDER'] = 'uploads'
14
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
15
+
16
+ # SiliconFlow API Configuration
17
+ SILICONFLOW_API_KEY = "sk-vimuseiptfbomzegyuvmebjzooncsqbyjtlddrfodzcdskgi"
18
+ SILICONFLOW_API_URL = "https://api.siliconflow.cn/v1/chat/completions"
19
+
20
+ # Mock Data: Skill Registry (Translated to Chinese)
21
+ SKILL_REGISTRY = [
22
+ {
23
+ "id": "sk_001",
24
+ "name": "PDF 文本提取器",
25
+ "description": "使用 OCR 技术从 PDF 文档中提取纯文本内容。",
26
+ "author": "System",
27
+ "downloads": 1240,
28
+ "price": 0.00,
29
+ "rating": 4.8,
30
+ "tags": ["文档", "OCR", "自动化"],
31
+ "code": "def extract_pdf(file_path):\n # 模拟实现\n return '提取的文本内容...'"
32
+ },
33
+ {
34
+ "id": "sk_002",
35
+ "name": "YouTube 视频总结",
36
+ "description": "根据 YouTube 视频 URL 生成简洁的要点总结。",
37
+ "author": "AI_Architect",
38
+ "downloads": 890,
39
+ "price": 4.99,
40
+ "rating": 4.9,
41
+ "tags": ["视频", "总结", "内容生成"],
42
+ "code": "def summarize_video(url):\n return {'summary': '提取的关键点...'}"
43
+ },
44
+ {
45
+ "id": "sk_003",
46
+ "name": "CSV 数据清洗助手",
47
+ "description": "自动修复 CSV 文件中的缺失值并标准化日期格式。",
48
+ "author": "DataWizard",
49
+ "downloads": 2100,
50
+ "price": 0.00,
51
+ "rating": 4.7,
52
+ "tags": ["数据", "CSV", "清洗"],
53
+ "code": "def clean_csv(df):\n df.fillna(0, inplace=True)\n return df"
54
+ },
55
+ {
56
+ "id": "sk_004",
57
+ "name": "SEO 关键词生成器",
58
+ "description": "根据输入的主题生成高排名的 SEO 关键词。",
59
+ "author": "MarketingBot",
60
+ "downloads": 560,
61
+ "price": 9.99,
62
+ "rating": 4.5,
63
+ "tags": ["营销", "SEO", "增长"],
64
+ "code": "def gen_keywords(topic):\n return ['最佳 ' + topic, topic + ' 测评']"
65
+ },
66
+ {
67
+ "id": "sk_005",
68
+ "name": "社交媒体情感雷达",
69
+ "description": "分析品牌在 Twitter 和 Reddit 上的情感倾向。",
70
+ "author": "System",
71
+ "downloads": 1500,
72
+ "price": 19.99,
73
+ "rating": 4.9,
74
+ "tags": ["社交", "分析", "情感"],
75
+ "code": "def analyze_sentiment(brand):\n return {'positive': 80, 'negative': 20}"
76
+ }
77
+ ]
78
+
79
+ # Mock Data: Stats
80
+ STATS = {
81
+ "total_skills": 142,
82
+ "active_users": 3500,
83
+ "total_executions": 45000,
84
+ "revenue": 12500.00
85
+ }
86
+
87
+ @app.route('/')
88
+ def index():
89
+ return render_template('index.html')
90
+
91
+ @app.route('/api/stats', methods=['GET'])
92
+ def get_stats():
93
+ # Simulate live updates
94
+ STATS['total_executions'] += random.randint(1, 10)
95
+ STATS['revenue'] += random.uniform(0, 5)
96
+ return jsonify(STATS)
97
+
98
+ @app.route('/api/skills', methods=['GET'])
99
+ def get_skills():
100
+ return jsonify(SKILL_REGISTRY)
101
+
102
+ @app.route('/api/generate', methods=['POST'])
103
+ def generate_skill():
104
+ data = request.json
105
+ prompt = data.get('prompt', '')
106
+
107
+ if not prompt:
108
+ return jsonify({"error": "Prompt is required"}), 400
109
+
110
+ # Try SiliconFlow API
111
+ try:
112
+ headers = {
113
+ "Authorization": f"Bearer {SILICONFLOW_API_KEY}",
114
+ "Content-Type": "application/json"
115
+ }
116
+
117
+ # Using a reliable model on SiliconFlow
118
+ payload = {
119
+ "model": "deepseek-ai/DeepSeek-V3",
120
+ "messages": [
121
+ {
122
+ "role": "system",
123
+ "content": """你是一个专业的 Python 代码生成助手。请根据用户的需求生成一个 Python 函数。
124
+ 返回格式必须是 JSON,包含以下字段:
125
+ - name: 技能名称 (中文)
126
+ - description: 简短描述 (中文)
127
+ - code: 完整的 Python 函数代码 (包含注释)
128
+ - tags: 3-5个标签 (List[str])
129
+
130
+ 只返回 JSON 字符串,不要包含 markdown 格式化符号。"""
131
+ },
132
+ {
133
+ "role": "user",
134
+ "content": f"生成一个 Python 任务脚本: {prompt}"
135
+ }
136
+ ],
137
+ "temperature": 0.7
138
+ }
139
+
140
+ response = requests.post(SILICONFLOW_API_URL, json=payload, headers=headers, timeout=30)
141
+
142
+ if response.status_code == 200:
143
+ result = response.json()
144
+ content = result['choices'][0]['message']['content']
145
+
146
+ # Clean up content if it contains markdown code blocks
147
+ if "```json" in content:
148
+ content = content.replace("```json", "").replace("```", "")
149
+ elif "```" in content:
150
+ content = content.replace("```", "")
151
+
152
+ ai_data = json.loads(content.strip())
153
+
154
+ new_id = f"sk_{random.randint(1000, 9999)}"
155
+ new_skill = {
156
+ "id": new_id,
157
+ "name": ai_data.get('name', f"AI 生成: {prompt[:10]}"),
158
+ "description": ai_data.get('description', f"AI 为 '{prompt}' 生成的解决方案"),
159
+ "author": "SiliconFlow_AI",
160
+ "downloads": 0,
161
+ "price": 0.00,
162
+ "rating": 0.0,
163
+ "tags": ai_data.get('tags', ["AI生成", "Python"]),
164
+ "code": ai_data.get('code', "# 生成失败,请重试")
165
+ }
166
+
167
+ # Don't insert automatically, let user verify first (frontend logic handles display)
168
+ return jsonify(new_skill)
169
+
170
+ except Exception as e:
171
+ print(f"SiliconFlow API Error: {e}")
172
+ # Fallback to mock if API fails
173
+ time.sleep(1)
174
+ new_id = f"sk_{random.randint(100, 999)}"
175
+ new_skill = {
176
+ "id": new_id,
177
+ "name": f"模拟生成: {prompt[:10]}...",
178
+ "description": f"由于 API 连接问题,这是本地模拟生成的结果: {prompt}",
179
+ "author": "Local_Fallback",
180
+ "downloads": 0,
181
+ "price": 0.00,
182
+ "rating": 0.0,
183
+ "tags": ["模拟", "离线"],
184
+ "code": f"def task_fallback(data):\n # API Error fallback\n print('Processing {prompt}')\n return True"
185
+ }
186
+ return jsonify(new_skill)
187
+
188
+ return jsonify({"error": "Generation failed"}), 500
189
+
190
+ @app.route('/api/save', methods=['POST'])
191
+ def save_skill():
192
+ data = request.json
193
+ if not data:
194
+ return jsonify({"error": "No data provided"}), 400
195
+
196
+ # In a real app, save to DB. Here, add to memory.
197
+ # Check if exists
198
+ existing = next((s for s in SKILL_REGISTRY if s['id'] == data.get('id')), None)
199
+ if not existing:
200
+ SKILL_REGISTRY.insert(0, data)
201
+ STATS['total_skills'] += 1
202
+ return jsonify({"status": "success", "message": "技能已保存"})
203
+ else:
204
+ return jsonify({"status": "exists", "message": "技能已存在"})
205
+
206
+ @app.route('/api/execute', methods=['POST'])
207
+ def execute_skill():
208
+ data = request.json
209
+ skill_id = data.get('skill_id')
210
+
211
+ # Mock Execution Delay
212
+ time.sleep(0.5)
213
+
214
+ return jsonify({
215
+ "status": "success",
216
+ "output": f"技能 {skill_id} 执行成功。",
217
+ "result_data": {
218
+ "processed_items": random.randint(5, 50),
219
+ "efficiency_score": f"{random.randint(80, 99)}%",
220
+ "timestamp": time.time()
221
+ }
222
+ })
223
+
224
+ @app.route('/api/upload', methods=['POST'])
225
+ def upload_file():
226
+ if 'file' not in request.files:
227
+ return jsonify({'error': 'No file part'}), 400
228
+ file = request.files['file']
229
+ if file.filename == '':
230
+ return jsonify({'error': 'No selected file'}), 400
231
+
232
+ if file:
233
+ filename = secure_filename(file.filename)
234
+ file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
235
+ return jsonify({'message': f'File {filename} uploaded successfully', 'path': f"/uploads/{filename}"})
236
+
237
+ if __name__ == '__main__':
238
+ app.run(host='0.0.0.0', port=7860)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ flask
2
+ gunicorn
3
+ pandas
4
+ requests
templates/index.html ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Skill Factory - AI 自动化工具工坊</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
10
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
11
+ <style>
12
+ body { font-family: 'Inter', system-ui, -apple-system, sans-serif; background-color: #f8fafc; }
13
+ .glass-panel { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); border: 1px solid #e2e8f0; }
14
+ .code-font { font-family: 'Fira Code', monospace; }
15
+ [v-cloak] { display: none; }
16
+ </style>
17
+ </head>
18
+ <body class="text-slate-800">
19
+ <div id="app" v-cloak class="min-h-screen flex flex-col">
20
+
21
+ <!-- Navigation -->
22
+ <nav class="bg-white border-b border-slate-200 sticky top-0 z-50">
23
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
24
+ <div class="flex justify-between h-16">
25
+ <div class="flex items-center gap-3">
26
+ <div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center text-white font-bold">
27
+ <i class="fas fa-cube"></i>
28
+ </div>
29
+ <span class="font-bold text-xl tracking-tight text-slate-900">Skill Factory <span class="text-xs text-blue-600 bg-blue-50 px-2 py-0.5 rounded-full ml-1">Beta</span></span>
30
+ </div>
31
+ <div class="flex items-center gap-6 text-sm font-medium text-slate-600">
32
+ <a href="#" class="hover:text-blue-600 transition-colors">工具市场</a>
33
+ <a href="#" class="hover:text-blue-600 transition-colors">我的资产</a>
34
+ <a href="#" class="hover:text-blue-600 transition-colors">API 文档</a>
35
+ <div class="flex items-center gap-2 px-3 py-1.5 bg-slate-100 rounded-full">
36
+ <div class="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
37
+ <span class="text-xs">系统正常</span>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </nav>
43
+
44
+ <!-- Main Content -->
45
+ <main class="flex-1 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 w-full grid grid-cols-12 gap-8">
46
+
47
+ <!-- Left Column: Generator & Console (8 cols) -->
48
+ <div class="col-span-12 lg:col-span-8 flex flex-col gap-6">
49
+
50
+ <!-- AI Generator Section -->
51
+ <div class="glass-panel rounded-xl p-6 shadow-sm">
52
+ <h2 class="text-lg font-bold mb-4 flex items-center gap-2">
53
+ <i class="fas fa-magic text-purple-500"></i> AI 工具生成器
54
+ </h2>
55
+ <div class="space-y-4">
56
+ <textarea
57
+ v-model="prompt"
58
+ class="w-full p-4 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none resize-none h-32 text-sm"
59
+ placeholder="描述你想要自动化的任务... (例如:提取PDF中的表格并保存为Excel,或者监控特定网页的价格变化)"
60
+ ></textarea>
61
+ <div class="flex justify-between items-center">
62
+ <div class="flex items-center gap-4">
63
+ <div class="text-xs text-slate-500">
64
+ <i class="fas fa-info-circle mr-1"></i> 支持 Python 3.11 环境
65
+ </div>
66
+ <!-- File Upload Trigger -->
67
+ <input type="file" ref="fileInput" class="hidden" @change="handleFileUpload">
68
+ <button @click="triggerUpload" class="text-xs text-slate-500 hover:text-blue-600 flex items-center gap-1 transition-colors">
69
+ <i class="fas fa-paperclip"></i> 上传参考文件
70
+ </button>
71
+ </div>
72
+ <button
73
+ @click="generateSkill"
74
+ :disabled="isGenerating || !prompt"
75
+ class="bg-slate-900 hover:bg-slate-800 text-white px-6 py-2.5 rounded-lg text-sm font-medium transition-all flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-blue-500/20"
76
+ >
77
+ <span v-if="isGenerating"><i class="fas fa-spinner fa-spin"></i> 生成中...</span>
78
+ <span v-else><i class="fas fa-bolt"></i> 立即生成</span>
79
+ </button>
80
+ </div>
81
+ </div>
82
+ </div>
83
+
84
+ <!-- Code Preview & Test Bench -->
85
+ <div v-if="currentSkill" class="glass-panel rounded-xl shadow-sm overflow-hidden flex flex-col flex-1 min-h-[400px]">
86
+ <div class="bg-slate-50 border-b border-slate-200 px-4 py-3 flex justify-between items-center">
87
+ <div class="flex items-center gap-2">
88
+ <i class="fas fa-code text-slate-400 text-sm"></i>
89
+ <span class="font-mono text-sm font-medium text-slate-700">${ currentSkill.name }</span>
90
+ </div>
91
+ <div class="flex gap-2">
92
+ <button @click="runTest" class="text-xs bg-green-600 hover:bg-green-700 text-white px-3 py-1.5 rounded flex items-center gap-1 transition-colors">
93
+ <i class="fas fa-play"></i> 运行测试
94
+ </button>
95
+ <button @click="saveSkill" class="text-xs bg-white border border-slate-300 hover:bg-slate-50 text-slate-700 px-3 py-1.5 rounded flex items-center gap-1 transition-colors">
96
+ <i class="fas fa-save"></i> 保存到资产库
97
+ </button>
98
+ </div>
99
+ </div>
100
+ <div class="flex-1 bg-[#1e1e1e] p-4 overflow-auto">
101
+ <pre class="code-font text-sm text-gray-300"><code>${ currentSkill.code }</code></pre>
102
+ </div>
103
+ <!-- Console Output -->
104
+ <div class="bg-slate-900 border-t border-slate-700 p-4 h-48 overflow-auto code-font text-xs">
105
+ <div class="flex items-center gap-2 text-slate-400 mb-2 uppercase tracking-wider text-[10px] font-bold">
106
+ <i class="fas fa-terminal"></i> 终端输出
107
+ </div>
108
+ <div v-for="(log, idx) in logs" :key="idx" :class="log.type === 'error' ? 'text-red-400' : 'text-green-400'" class="mb-1">
109
+ <span class="text-slate-600 select-none mr-2">[${ log.time }]</span>
110
+ <span>${ log.msg }</span>
111
+ </div>
112
+ <div v-if="logs.length === 0" class="text-slate-600 italic">等待执行...</div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+
117
+ <!-- Right Column: Marketplace & Stats (4 cols) -->
118
+ <div class="col-span-12 lg:col-span-4 flex flex-col gap-6">
119
+
120
+ <!-- Stats Cards -->
121
+ <div class="grid grid-cols-2 gap-4">
122
+ <div class="glass-panel p-4 rounded-xl">
123
+ <div class="text-slate-500 text-xs font-medium mb-1">总资产价值 (预估)</div>
124
+ <div class="text-2xl font-bold text-slate-900">$${ stats.revenue.toFixed(2) }</div>
125
+ <div class="text-xs text-green-500 mt-1 flex items-center gap-1">
126
+ <i class="fas fa-arrow-up"></i> 12.5%
127
+ </div>
128
+ </div>
129
+ <div class="glass-panel p-4 rounded-xl">
130
+ <div class="text-slate-500 text-xs font-medium mb-1">技能调用次数</div>
131
+ <div class="text-2xl font-bold text-slate-900">${ stats.total_executions }</div>
132
+ <div class="text-xs text-blue-500 mt-1 flex items-center gap-1">
133
+ <i class="fas fa-chart-line"></i> +840 今日
134
+ </div>
135
+ </div>
136
+ </div>
137
+
138
+ <!-- Skill Marketplace -->
139
+ <div class="glass-panel rounded-xl shadow-sm flex-1 flex flex-col">
140
+ <div class="p-4 border-b border-slate-200 flex justify-between items-center">
141
+ <h3 class="font-bold text-slate-800">热门技能市场</h3>
142
+ <span class="text-xs bg-slate-100 text-slate-600 px-2 py-1 rounded-full">Top 5</span>
143
+ </div>
144
+ <div class="p-2 overflow-y-auto flex-1 max-h-[600px]">
145
+ <div v-for="skill in skills" :key="skill.id" class="group p-3 hover:bg-slate-50 rounded-lg transition-all cursor-pointer border border-transparent hover:border-slate-200 mb-2">
146
+ <div class="flex justify-between items-start mb-2">
147
+ <div class="font-medium text-slate-800 text-sm group-hover:text-blue-600 transition-colors">${ skill.name }</div>
148
+ <div v-if="skill.price > 0" class="text-xs font-bold text-slate-700 bg-yellow-100 px-1.5 py-0.5 rounded text-yellow-700">$${ skill.price }</div>
149
+ <div v-else class="text-xs font-bold text-slate-500 bg-slate-100 px-1.5 py-0.5 rounded">Free</div>
150
+ </div>
151
+ <p class="text-xs text-slate-500 line-clamp-2 mb-3">${ skill.description }</p>
152
+ <div class="flex justify-between items-center">
153
+ <div class="flex gap-1">
154
+ <span v-for="tag in skill.tags" class="text-[10px] bg-slate-100 text-slate-500 px-1.5 py-0.5 rounded">${ tag }</span>
155
+ </div>
156
+ <button @click="loadSkill(skill)" class="text-xs text-blue-600 hover:text-blue-700 font-medium">
157
+ 加载 <i class="fas fa-chevron-right text-[10px]"></i>
158
+ </button>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </div>
163
+
164
+ </div>
165
+ </main>
166
+ </div>
167
+
168
+ <script>
169
+ const { createApp, ref, onMounted } = Vue;
170
+
171
+ createApp({
172
+ delimiters: ['${', '}'], // Avoid Jinja2 conflict
173
+ setup() {
174
+ const prompt = ref('');
175
+ const isGenerating = ref(false);
176
+ const currentSkill = ref(null);
177
+ const skills = ref([]);
178
+ const stats = ref({ revenue: 0, total_executions: 0 });
179
+ const logs = ref([]);
180
+ const fileInput = ref(null);
181
+
182
+ const addLog = (msg, type = 'info') => {
183
+ const time = new Date().toLocaleTimeString('zh-CN', { hour12: false });
184
+ logs.value.unshift({ time, msg, type });
185
+ };
186
+
187
+ const fetchStats = async () => {
188
+ const res = await fetch('/api/stats');
189
+ stats.value = await res.json();
190
+ };
191
+
192
+ const fetchSkills = async () => {
193
+ const res = await fetch('/api/skills');
194
+ skills.value = await res.json();
195
+ };
196
+
197
+ const generateSkill = async () => {
198
+ if (!prompt.value) return;
199
+ isGenerating.value = true;
200
+ addLog(`开始生成技能任务: "${prompt.value}"...`);
201
+
202
+ try {
203
+ const res = await fetch('/api/generate', {
204
+ method: 'POST',
205
+ headers: {'Content-Type': 'application/json'},
206
+ body: JSON.stringify({ prompt: prompt.value })
207
+ });
208
+ const data = await res.json();
209
+ if (data.error) throw new Error(data.error);
210
+
211
+ currentSkill.value = data;
212
+ addLog(`生成成功: ${data.name}`, 'success');
213
+ addLog(`代码已加载到编辑器`);
214
+ // Don't refresh skills list yet, wait for save
215
+ } catch (e) {
216
+ addLog(`生成失败: ${e.message}`, 'error');
217
+ } finally {
218
+ isGenerating.value = false;
219
+ }
220
+ };
221
+
222
+ const loadSkill = (skill) => {
223
+ currentSkill.value = skill;
224
+ addLog(`已加载技能: ${skill.name}`);
225
+ };
226
+
227
+ const saveSkill = async () => {
228
+ if (!currentSkill.value) return;
229
+ addLog(`正在保存技能: ${currentSkill.value.name}...`);
230
+ try {
231
+ const res = await fetch('/api/save', {
232
+ method: 'POST',
233
+ headers: {'Content-Type': 'application/json'},
234
+ body: JSON.stringify(currentSkill.value)
235
+ });
236
+ const data = await res.json();
237
+ if (data.status === 'success') {
238
+ addLog('保存成功!', 'success');
239
+ await fetchSkills();
240
+ } else {
241
+ addLog(`保存提示: ${data.message}`);
242
+ }
243
+ } catch (e) {
244
+ addLog(`保存失败: ${e.message}`, 'error');
245
+ }
246
+ };
247
+
248
+ const runTest = async () => {
249
+ if (!currentSkill.value) return;
250
+ addLog(`正在执行测试: ${currentSkill.value.name}...`);
251
+ try {
252
+ const res = await fetch('/api/execute', {
253
+ method: 'POST',
254
+ headers: {'Content-Type': 'application/json'},
255
+ body: JSON.stringify({ skill_id: currentSkill.value.id })
256
+ });
257
+ const data = await res.json();
258
+ addLog(`执行成功! 耗时: 0.8s`, 'success');
259
+ addLog(`输出结果: ${JSON.stringify(data.result_data)}`);
260
+ await fetchStats(); // Update stats
261
+ } catch (e) {
262
+ addLog(`执行错误: ${e.message}`, 'error');
263
+ }
264
+ };
265
+
266
+ const triggerUpload = () => {
267
+ fileInput.value.click();
268
+ };
269
+
270
+ const handleFileUpload = async (event) => {
271
+ const file = event.target.files[0];
272
+ if (!file) return;
273
+
274
+ const formData = new FormData();
275
+ formData.append('file', file);
276
+
277
+ addLog(`正在上传文件: ${file.name}...`);
278
+ try {
279
+ const res = await fetch('/api/upload', {
280
+ method: 'POST',
281
+ body: formData
282
+ });
283
+ const data = await res.json();
284
+ if (data.path) {
285
+ addLog(`上传成功! 文件路径: ${data.path}`, 'success');
286
+ // Auto-fill prompt with file context if prompt is empty
287
+ if (!prompt.value) {
288
+ prompt.value = `分析上传的文件 ${file.name} 并创建一个处理它的脚本...`;
289
+ }
290
+ } else {
291
+ addLog(`上传失败: ${data.error}`, 'error');
292
+ }
293
+ } catch (e) {
294
+ addLog(`上传错误: ${e.message}`, 'error');
295
+ }
296
+ };
297
+
298
+ onMounted(() => {
299
+ fetchStats();
300
+ fetchSkills();
301
+ addLog('系统初始化完成');
302
+ addLog('连接到 Skill Registry...');
303
+ addLog('SiliconFlow API 已就绪');
304
+ });
305
+
306
+ return {
307
+ prompt,
308
+ isGenerating,
309
+ currentSkill,
310
+ skills,
311
+ stats,
312
+ logs,
313
+ fileInput,
314
+ generateSkill,
315
+ loadSkill,
316
+ saveSkill,
317
+ runTest,
318
+ triggerUpload,
319
+ handleFileUpload
320
+ };
321
+ }
322
+ }).mount('#app');
323
+ </script>
324
+ </body>
325
+ </html>