Trae Assistant
Initial commit with SiliconFlow integration
038fa15
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>知识提炼与资产化智能体 | Knowledge Refinery Agent</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; background-color: #f8fafc; color: #1e293b; }
.card { background: white; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border: 1px solid #e2e8f0; }
.btn-primary { background-color: #0f172a; color: white; transition: all 0.2s; }
.btn-primary:hover { background-color: #334155; }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.tag { @apply px-2 py-1 rounded-full text-xs font-medium; }
</style>
</head>
<body>
<div id="app" class="min-h-screen flex flex-col">
<!-- Header -->
<header class="bg-white border-b border-gray-200 sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-slate-900 rounded-lg flex items-center justify-center text-white font-bold">K</div>
<h1 class="text-xl font-semibold text-slate-900 tracking-tight">知识提炼与资产化智能体</h1>
</div>
<div class="text-sm text-slate-500 hidden sm:block">v1.0.0 | Powered by Mock AI</div>
</div>
</header>
<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">
<!-- Left Column: Input & Controls -->
<div class="lg:col-span-4 space-y-6">
<!-- Input Card -->
<div class="card p-6">
<h2 class="text-lg font-medium mb-4 flex items-center gap-2">
<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>
输入源挖掘
</h2>
<div class="flex gap-2 mb-4 bg-slate-100 p-1 rounded-lg">
<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>
<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>
<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>
</div>
<div class="space-y-4">
<div v-if="inputType === 'text'">
<label class="block text-sm font-medium text-slate-700 mb-1">原始文本</label>
<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>
</div>
<div v-else-if="inputType === 'url'">
<label class="block text-sm font-medium text-slate-700 mb-1">目标 URL</label>
<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">
</div>
<div v-else>
<label class="block text-sm font-medium text-slate-700 mb-1">上传文件 (支持 .txt, .md)</label>
<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">
<input type="file" ref="fileInput" @change="handleFileChange" class="hidden">
<div v-if="selectedFile" class="text-sm text-slate-900 font-medium flex items-center justify-center gap-2">
<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>
${ selectedFile.name }
</div>
<div v-else class="text-slate-500 text-sm">
<p>点击选择文件或拖拽至此处</p>
<p class="text-xs mt-1 text-slate-400">最大支持 50MB</p>
</div>
</div>
</div>
<button @click="startMining" :disabled="isProcessing" class="w-full btn-primary py-2.5 rounded-lg font-medium flex items-center justify-center gap-2">
<span v-if="isProcessing">
<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>
深度挖掘中...
</span>
<span v-else>开始提炼资产</span>
</button>
</div>
</div>
<!-- History / Vault Preview -->
<div class="card p-6">
<h2 class="text-lg font-medium mb-4">资产库</h2>
<div v-if="vault.length === 0" class="text-center text-slate-400 py-8 text-sm">
暂无提炼资产
</div>
<ul v-else class="space-y-3">
<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">
<div class="flex justify-between items-start mb-1">
<span class="text-xs font-mono text-slate-400">${ item.timestamp.split(' ')[1] }</span>
<span class="tag bg-blue-100 text-blue-700">${ item.metrics.commercial_value }分</span>
</div>
<div class="text-sm text-slate-700 line-clamp-2">${ item.summary }</div>
</li>
</ul>
</div>
</div>
<!-- Right Column: Results & Visualization -->
<div class="lg:col-span-8 space-y-6">
<!-- Welcome State -->
<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">
<div class="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mb-4">
<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>
</div>
<h3 class="text-lg font-medium text-slate-900">准备就绪</h3>
<p class="text-slate-500 max-w-md mt-2">输入文本或 URL,智能体将自动提炼关键信息、评估商业价值并生成结构化知识资产。</p>
</div>
<!-- Analysis Result -->
<div v-else class="space-y-6">
<!-- Metrics & Summary -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="card p-4 flex flex-col items-center justify-center text-center">
<div class="text-sm text-slate-500 mb-1">商业价值评分</div>
<div class="text-3xl font-bold text-slate-900">${ currentAnalysis.metrics.commercial_value }</div>
<div class="w-full bg-gray-200 rounded-full h-1.5 mt-3">
<div class="bg-slate-900 h-1.5 rounded-full" :style="{ width: currentAnalysis.metrics.commercial_value + '%' }"></div>
</div>
</div>
<div class="card p-4 flex flex-col items-center justify-center text-center">
<div class="text-sm text-slate-500 mb-1">信息复杂度</div>
<div class="text-3xl font-bold text-slate-900">${ currentAnalysis.metrics.complexity }</div>
<div class="text-xs text-slate-400 mt-1">Mock Analysis</div>
</div>
<div class="card p-4 flex flex-col items-center justify-center text-center">
<div class="text-sm text-slate-500 mb-1">情感倾向</div>
<div class="text-3xl font-bold text-green-600" v-if="currentAnalysis.metrics.sentiment > 0.6">积极</div>
<div class="text-3xl font-bold text-yellow-600" v-else-if="currentAnalysis.metrics.sentiment > 0.4">中性</div>
<div class="text-3xl font-bold text-red-600" v-else>消极</div>
</div>
</div>
<!-- Main Report -->
<div class="card p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-bold text-slate-900">提炼报告</h2>
<div class="flex gap-2">
<button @click="generateAsset('report')" class="text-sm border border-slate-300 px-3 py-1.5 rounded hover:bg-slate-50">下载 PDF</button>
<button @click="generateAsset('mindmap')" class="text-sm border border-slate-300 px-3 py-1.5 rounded hover:bg-slate-50">导出脑图</button>
</div>
</div>
<div class="bg-slate-50 p-4 rounded-lg border border-slate-100 mb-6">
<h3 class="text-sm font-semibold text-slate-700 uppercase tracking-wider mb-2">摘要</h3>
<p class="text-slate-600 leading-relaxed">${ currentAnalysis.summary }</p>
</div>
<div class="mb-6">
<h3 class="text-sm font-semibold text-slate-700 uppercase tracking-wider mb-3">关键洞察</h3>
<ul class="space-y-2">
<li v-for="(insight, index) in currentAnalysis.insights" :key="index" class="flex items-start gap-3">
<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>
<span class="text-slate-700">${ insight }</span>
</li>
</ul>
</div>
<div>
<h3 class="text-sm font-semibold text-slate-700 uppercase tracking-wider mb-3">关联标签</h3>
<div class="flex flex-wrap gap-2">
<span v-for="tag in currentAnalysis.tags" :key="tag" class="tag bg-slate-100 text-slate-600 border border-slate-200">#${ tag }</span>
</div>
</div>
</div>
<!-- Chart -->
<div class="card p-6">
<h2 class="text-lg font-medium mb-4">趋势雷达</h2>
<div ref="radarChart" class="w-full h-64"></div>
</div>
</div>
</div>
</main>
</div>
<script>
const { createApp, ref, onMounted, nextTick } = Vue;
createApp({
delimiters: ['${', '}'],
setup() {
const inputType = ref('text');
const inputText = ref('2024年 AI 代理(AI Agents)市场正迎来爆发式增长。企业不再仅仅满足于聊天机器人,而是寻求能够执行复杂任务、自主决策的智能体。从软件开发到市场营销,Agent 正在重塑工作流程。然而,数据安全和幻觉问题依然是主要挑战。预计到 2025 年,Agent 市场规模将突破 500 亿美元。');
const inputUrl = ref('');
const isProcessing = ref(false);
const currentAnalysis = ref(null);
const vault = ref([]);
const radarChart = ref(null);
const fileInput = ref(null);
const selectedFile = ref(null);
let chartInstance = null;
const triggerUpload = () => {
fileInput.value.click();
};
const handleFileChange = (event) => {
const file = event.target.files[0];
if (file) {
if (file.size > 50 * 1024 * 1024) {
alert('文件大小超过 50MB 限制');
event.target.value = '';
return;
}
selectedFile.value = file;
}
};
const startMining = async () => {
isProcessing.value = true;
try {
let response;
if (inputType.value === 'file') {
if (!selectedFile.value) {
alert('请先选择文件');
isProcessing.value = false;
return;
}
const formData = new FormData();
formData.append('file', selectedFile.value);
formData.append('type', 'file');
response = await fetch('/api/mine', {
method: 'POST',
body: formData
});
} else {
const content = inputType.value === 'text' ? inputText.value : inputUrl.value;
if (!content) {
alert('请输入内容');
isProcessing.value = false;
return;
}
response = await fetch('/api/mine', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content, type: inputType.value })
});
}
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Request failed');
}
const data = await response.json();
currentAnalysis.value = data;
vault.value.unshift(data);
await nextTick();
initChart();
} catch (error) {
console.error("Mining failed", error);
alert("分析失败: " + error.message);
} finally {
isProcessing.value = false;
}
};
const loadItem = async (item) => {
currentAnalysis.value = item;
await nextTick();
initChart();
};
const generateAsset = async (type) => {
alert(`正在生成 ${type === 'report' ? 'PDF 报告' : '思维导图'}... \n(Mock Function: Asset Generated)`);
};
const initChart = () => {
if (!radarChart.value) return;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(radarChart.value);
const metrics = currentAnalysis.value.metrics;
const option = {
radar: {
indicator: [
{ name: '商业价值', max: 100 },
{ name: '技术可行性', max: 100 },
{ name: '市场热度', max: 100 },
{ name: '合规风险', max: 100 },
{ name: '创新度', max: 100 }
],
radius: '70%'
},
series: [{
name: 'Asset Analysis',
type: 'radar',
data: [{
value: [
metrics.commercial_value,
Math.random() * 40 + 60,
Math.random() * 40 + 60,
100 - metrics.complexity,
Math.random() * 50 + 50
],
name: '当前项目',
itemStyle: { color: '#0f172a' },
areaStyle: { opacity: 0.2 }
}]
}]
};
chartInstance.setOption(option);
window.addEventListener('resize', () => chartInstance.resize());
};
onMounted(() => {
// Optional: Fetch initial history
// fetch('/api/vault').then(r => r.json()).then(d => vault.value = d);
});
return {
inputType,
inputText,
inputUrl,
isProcessing,
currentAnalysis,
vault,
radarChart,
fileInput,
selectedFile,
triggerUpload,
handleFileChange,
startMining,
loadItem,
generateAsset
};
}
}).mount('#app');
</script>
</body>
</html>