Trae Assistant
Update for Hugging Face Spaces: Add export, templates, and optimizations
75a237a
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>利润逻辑工作室 | Profit Logic Studio</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Vue 3 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- FontAwesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f3f4f6;
}
.var-card {
transition: all 0.2s;
}
.var-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>
</head>
<body class="h-screen overflow-hidden flex flex-col">
<div id="app" class="flex-1 flex flex-col h-full">
<!-- Header -->
<header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-6 shadow-sm z-10">
<div class="flex items-center gap-3">
<div class="bg-indigo-600 text-white p-2 rounded-lg">
<i class="fa-solid fa-chart-line"></i>
</div>
<h1 class="text-xl font-bold text-gray-800">利润逻辑工作室 <span class="text-xs text-gray-500 font-normal ml-2">商业研判与模拟系统</span></h1>
</div>
<div class="flex items-center gap-4">
<button @click="saveModel" class="text-gray-600 hover:text-indigo-600 transition" title="保存模型到本地">
<i class="fa-solid fa-save mr-1"></i> 保存
</button>
<button @click="loadModel" class="text-gray-600 hover:text-indigo-600 transition" title="加载本地模型">
<i class="fa-solid fa-folder-open mr-1"></i> 加载
</button>
<!-- Templates Dropdown -->
<div class="relative group">
<button class="text-gray-600 hover:text-indigo-600 transition flex items-center gap-1" title="加载预设场景">
<i class="fa-solid fa-shapes mr-1"></i> 模板
</button>
<div class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-20 hidden group-hover:block border border-gray-100">
<a href="#" @click.prevent="loadTemplate('ecommerce')" class="block px-4 py-2 text-sm text-gray-700 hover:bg-indigo-50">电商利润模型</a>
<a href="#" @click.prevent="loadTemplate('saas')" class="block px-4 py-2 text-sm text-gray-700 hover:bg-indigo-50">SaaS MRR模型</a>
</div>
</div>
<button @click="showHelp = true" class="text-gray-600 hover:text-indigo-600 transition">
<i class="fa-solid fa-circle-question"></i>
</button>
</div>
</header>
<!-- Main Content -->
<main class="flex-1 flex overflow-hidden">
<!-- Sidebar: Model Builder -->
<aside class="w-1/3 bg-white border-r border-gray-200 flex flex-col shadow-lg z-0">
<div class="p-4 border-b border-gray-100 bg-gray-50 flex justify-between items-center">
<h2 class="font-semibold text-gray-700"><i class="fa-solid fa-cubes mr-2"></i>商业变量构建</h2>
<button @click="addVariable" class="bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1 rounded text-sm transition shadow-sm">
<i class="fa-solid fa-plus mr-1"></i> 新增变量
</button>
</div>
<div class="flex-1 overflow-y-auto p-4 space-y-3 bg-gray-50/50">
<div v-for="(v, index) in variables" :key="index" class="var-card bg-white p-4 rounded-lg border border-gray-200 relative group">
<!-- Delete Button -->
<button @click="removeVariable(index)" class="absolute top-2 right-2 text-gray-300 hover:text-red-500 opacity-0 group-hover:opacity-100 transition">
<i class="fa-solid fa-times"></i>
</button>
<!-- Variable Name -->
<div class="mb-3">
<label class="block text-xs font-medium text-gray-500 mb-1 uppercase tracking-wide">变量名称 (ID)</label>
<input v-model="v.name" type="text" class="w-full border-b border-gray-300 focus:border-indigo-500 outline-none py-1 text-gray-800 font-medium bg-transparent" placeholder="例如: 流量">
</div>
<!-- Variable Type -->
<div class="mb-3">
<label class="block text-xs font-medium text-gray-500 mb-1">类型</label>
<div class="flex rounded-md shadow-sm" role="group">
<button type="button" @click="v.type = 'constant'" :class="{'bg-indigo-100 text-indigo-700 border-indigo-200': v.type === 'constant', 'bg-white text-gray-600 border-gray-300 hover:bg-gray-50': v.type !== 'constant'}" class="flex-1 px-2 py-1 text-xs font-medium border rounded-l-md focus:z-10 focus:ring-1 focus:ring-indigo-500">
常量
</button>
<button type="button" @click="v.type = 'distribution'" :class="{'bg-indigo-100 text-indigo-700 border-indigo-200': v.type === 'distribution', 'bg-white text-gray-600 border-gray-300 hover:bg-gray-50': v.type !== 'distribution'}" class="flex-1 px-2 py-1 text-xs font-medium border-t border-b border-gray-300 focus:z-10 focus:ring-1 focus:ring-indigo-500">
分布(概率)
</button>
<button type="button" @click="v.type = 'formula'" :class="{'bg-indigo-100 text-indigo-700 border-indigo-200': v.type === 'formula', 'bg-white text-gray-600 border-gray-300 hover:bg-gray-50': v.type !== 'formula'}" class="flex-1 px-2 py-1 text-xs font-medium border rounded-r-md focus:z-10 focus:ring-1 focus:ring-indigo-500">
公式
</button>
</div>
</div>
<!-- Type Specific Inputs -->
<!-- Constant -->
<div v-if="v.type === 'constant'" class="animate-fade-in">
<label class="block text-xs font-medium text-gray-500 mb-1">数值</label>
<input v-model="v.value" type="number" class="w-full border rounded px-2 py-1 text-sm focus:ring-1 focus:ring-indigo-500 border-gray-300">
</div>
<!-- Distribution -->
<div v-if="v.type === 'distribution'" class="space-y-2 animate-fade-in">
<div class="flex gap-2">
<select v-model="v.dist" class="w-1/2 border rounded px-2 py-1 text-sm bg-white border-gray-300">
<option value="normal">正态分布 (Normal)</option>
<option value="uniform">均匀分布 (Uniform)</option>
<option value="triangular">三角分布 (Triangular)</option>
</select>
</div>
<div class="flex gap-2 items-center">
<div v-if="v.dist === 'normal'" class="flex gap-2 w-full">
<div class="flex-1">
<label class="text-[10px] text-gray-500">均值 (Mean)</label>
<input v-model="v.params[0]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300">
</div>
<div class="flex-1">
<label class="text-[10px] text-gray-500">标准差 (Std)</label>
<input v-model="v.params[1]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300">
</div>
</div>
<div v-if="v.dist === 'uniform'" class="flex gap-2 w-full">
<div class="flex-1">
<label class="text-[10px] text-gray-500">最小值 (Min)</label>
<input v-model="v.params[0]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300">
</div>
<div class="flex-1">
<label class="text-[10px] text-gray-500">最大值 (Max)</label>
<input v-model="v.params[1]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300">
</div>
</div>
<div v-if="v.dist === 'triangular'" class="flex gap-2 w-full">
<div class="flex-1">
<label class="text-[10px] text-gray-500">Min</label>
<input v-model="v.params[0]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300">
</div>
<div class="flex-1">
<label class="text-[10px] text-gray-500">Mode</label>
<input v-model="v.params[1]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300">
</div>
<div class="flex-1">
<label class="text-[10px] text-gray-500">Max</label>
<input v-model="v.params[2]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300">
</div>
</div>
</div>
</div>
<!-- Formula -->
<div v-if="v.type === 'formula'" class="animate-fade-in">
<label class="block text-xs font-medium text-gray-500 mb-1">计算公式 (使用变量名)</label>
<div class="relative">
<input v-model="v.formula" type="text" class="w-full border rounded px-2 py-1 text-sm pl-6 border-gray-300 font-mono text-indigo-600 bg-indigo-50" placeholder="例如: 流量 * 转化率">
<i class="fa-solid fa-calculator absolute left-2 top-2 text-indigo-400 text-xs"></i>
</div>
<p class="text-[10px] text-gray-400 mt-1">支持 +, -, *, /, (, ) 及 min(), max()</p>
</div>
</div>
<!-- Empty State -->
<div v-if="variables.length === 0" class="text-center py-10 text-gray-400 border-2 border-dashed border-gray-200 rounded-lg">
<i class="fa-solid fa-arrow-up mb-2"></i>
<p>点击上方添加第一个变量</p>
</div>
</div>
</aside>
<!-- Main: Simulation Dashboard -->
<section class="w-2/3 bg-gray-100 flex flex-col p-6 overflow-y-auto">
<!-- Control Panel -->
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
<div class="flex justify-between items-end mb-4">
<div>
<h2 class="text-lg font-bold text-gray-800">模拟控制台</h2>
<p class="text-sm text-gray-500">配置模拟参数并运行分析</p>
</div>
<div class="flex gap-4 items-center">
<div>
<label class="block text-xs font-medium text-gray-500 mb-1">模拟次数 (Iterations)</label>
<input v-model.number="iterations" type="number" class="border rounded px-2 py-1 text-sm w-32 border-gray-300">
</div>
<div>
<label class="block text-xs font-medium text-gray-500 mb-1">目标分析变量</label>
<select v-model="targetVariable" class="border rounded px-2 py-1 text-sm w-40 border-gray-300">
<option v-for="v in variables" :value="v.name">{{ v.name }}</option>
</select>
</div>
<button @click="runSimulation" :disabled="loading" class="bg-indigo-600 hover:bg-indigo-700 disabled:bg-indigo-300 text-white px-6 py-2 rounded-lg font-medium shadow-md transition flex items-center gap-2">
<i v-if="loading" class="fa-solid fa-circle-notch fa-spin"></i>
<span v-else><i class="fa-solid fa-play"></i></span>
运行模拟
</button>
</div>
</div>
</div>
<!-- Results -->
<div v-if="results" class="animate-fade-in space-y-6">
<!-- KPI Cards -->
<div class="grid grid-cols-4 gap-4">
<div class="bg-white p-4 rounded-xl shadow-sm border-l-4 border-blue-500">
<div class="text-xs text-gray-500 uppercase">平均值 (Mean)</div>
<div class="text-xl font-bold text-gray-800">{{ formatNumber(results.stats.mean) }}</div>
</div>
<div class="bg-white p-4 rounded-xl shadow-sm border-l-4 border-green-500">
<div class="text-xs text-gray-500 uppercase">中位数 (Median)</div>
<div class="text-xl font-bold text-gray-800">{{ formatNumber(results.stats.median) }}</div>
</div>
<div class="bg-white p-4 rounded-xl shadow-sm border-l-4 border-yellow-500">
<div class="text-xs text-gray-500 uppercase" title="90% 的概率会高于此值">保守估计 (P10)</div>
<div class="text-xl font-bold text-gray-800">{{ formatNumber(results.stats.p10) }}</div>
</div>
<div class="bg-white p-4 rounded-xl shadow-sm border-l-4 border-purple-500">
<div class="text-xs text-gray-500 uppercase" title="10% 的概率会高于此值">乐观估计 (P90)</div>
<div class="text-xl font-bold text-gray-800">{{ formatNumber(results.stats.p90) }}</div>
</div>
</div>
<!-- Chart -->
<div class="bg-white p-6 rounded-xl shadow-sm">
<div class="flex justify-between items-center mb-4">
<h3 class="text-md font-bold text-gray-800">概率分布图 - {{ targetVariable }}</h3>
<button @click="exportCSV" class="text-xs bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 px-3 py-1 rounded transition flex items-center gap-1" title="下载模拟结果数据">
<i class="fa-solid fa-download"></i> 导出 CSV
</button>
</div>
<div class="h-64">
<canvas id="resultChart"></canvas>
</div>
<div class="mt-4 text-sm text-gray-500 text-center">
X 轴: {{ targetVariable }} 数值区间 | Y 轴: 出现频次 (模拟 {{ iterations }} 次)
</div>
</div>
<!-- Risk Analysis -->
<div class="bg-white p-6 rounded-xl shadow-sm">
<h3 class="text-md font-bold text-gray-800 mb-2">研判分析结论</h3>
<div class="text-sm text-gray-600 space-y-2">
<p><i class="fa-solid fa-circle-info text-blue-500 mr-2"></i>在当前模型假设下,<b>{{ targetVariable }}</b> 的预期平均值为 <b>{{ formatNumber(results.stats.mean) }}</b></p>
<p><i class="fa-solid fa-triangle-exclamation text-yellow-500 mr-2"></i><b>风险提示</b>:有 10% 的可能性,结果会低于 <b>{{ formatNumber(results.stats.p10) }}</b>(P10 下限)。</p>
<p><i class="fa-solid fa-rocket text-purple-500 mr-2"></i><b>潜力评估</b>:如果运气好(Top 10%),结果可能达到 <b>{{ formatNumber(results.stats.p90) }}</b>(P90 上限)。</p>
</div>
</div>
</div>
<div v-else class="flex-1 flex flex-col items-center justify-center text-gray-400">
<i class="fa-solid fa-chart-simple text-6xl mb-4 text-gray-200"></i>
<p>配置左侧变量并点击“运行模拟”查看结果</p>
</div>
</section>
</main>
<!-- Help Modal -->
<div v-if="showHelp" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 max-w-lg w-full shadow-2xl">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold">使用指南</h3>
<button @click="showHelp = false" class="text-gray-500 hover:text-gray-800"><i class="fa-solid fa-times"></i></button>
</div>
<div class="space-y-4 text-sm text-gray-600">
<p><b>1. 定义变量:</b> 在左侧添加商业变量。可以是固定值(常量),也可以是带有不确定性的范围(分布),或者是依赖其他变量的计算公式。</p>
<p><b>2. 设置公式:</b> 公式支持基本的数学运算。请确保公式中引用的变量名与定义的一致。</p>
<p><b>3. 运行模拟:</b> 选择你最关心的结果指标(如“利润”),设置模拟次数(建议 1000 次以上),点击运行。</p>
<p><b>4. 分析结果:</b> 系统会通过蒙特卡洛方法(Monte Carlo Simulation)计算出成千上万种可能的情境,并告诉你“平均情况”、“最坏情况”和“最好情况”。</p>
<div class="bg-indigo-50 p-3 rounded text-indigo-700">
<i class="fa-solid fa-lightbulb mr-1"></i> <b>提示:</b> 这是一个帮助你做“模糊决策”的工具。不要只看平均值,更要关注 P10(下限风险)。
</div>
</div>
</div>
</div>
</div>
<script src="static/js/app.js"></script>
</body>
</html>