2026-04-20 16:21:06 +08:00

869 lines
49 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标伙伴 · AI 标书助手</title>
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<link rel="stylesheet" href="/static/style.css">
<style>
[x-cloak]{display:none!important}
body{font-family:'PingFang SC','Microsoft YaHei',sans-serif;background:#f0f4f8}
</style>
</head>
<body class="min-h-screen" x-data="app()" x-init="init()">
<!-- ── 顶栏 ── -->
<header class="bg-white border-b border-gray-200 sticky top-0 z-50 shadow-sm">
<div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-blue-600 to-indigo-600 flex items-center justify-center shadow">
<svg class="w-5 h-5 text-white" 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"/>
</svg>
</div>
<div>
<span class="text-lg font-bold text-gray-900">标伙伴</span>
<span class="ml-2 text-xs text-gray-400 font-medium">AI 标书助手</span>
</div>
</div>
<div class="flex items-center gap-3">
<button @click="showConfig=true"
class="p-2 text-gray-500 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</button>
<button @click="showCreate=true"
class="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg shadow-sm transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
新建项目
</button>
</div>
</div>
</header>
<!-- ── 主内容 ── -->
<main class="max-w-7xl mx-auto px-6 py-8">
<!-- 欢迎横幅(无项目时显示) -->
<template x-if="projects.length === 0 && !loading">
<div class="text-center py-20">
<div class="w-24 h-24 mx-auto mb-6 rounded-3xl bg-gradient-to-br from-blue-100 to-indigo-100 flex items-center justify-center">
<svg class="w-12 h-12 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
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"/>
</svg>
</div>
<h2 class="text-2xl font-bold text-gray-800 mb-2">欢迎使用标伙伴</h2>
<p class="text-gray-500 mb-8 max-w-md mx-auto">AI 驱动的标书写作助手,上传招标文件,一键生成专业技术标书</p>
<button @click="showCreate=true"
class="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-xl shadow-md transition">
创建第一个项目
</button>
<!-- 功能介绍 -->
<div class="grid grid-cols-3 gap-6 mt-16 max-w-3xl mx-auto text-left">
<div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100">
<div class="w-10 h-10 bg-blue-100 rounded-xl flex items-center justify-center mb-4">
<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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
</svg>
</div>
<h3 class="font-semibold text-gray-800 mb-1">智能解析招标文件</h3>
<p class="text-sm text-gray-500">自动提取评分要求、资质条件、技术参数</p>
</div>
<div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100">
<div class="w-10 h-10 bg-green-100 rounded-xl flex items-center justify-center mb-4">
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
</div>
<h3 class="font-semibold text-gray-800 mb-1">自动生成标书大纲</h3>
<p class="text-sm text-gray-500">按评分权重生成四级章节结构,精准对标要求</p>
</div>
<div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100">
<div class="w-10 h-10 bg-purple-100 rounded-xl flex items-center justify-center mb-4">
<svg class="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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"/>
</svg>
</div>
<h3 class="font-semibold text-gray-800 mb-1">一键导出 Word 文档</h3>
<p class="text-sm text-gray-500">专业排版,直接交付使用</p>
</div>
</div>
</div>
</template>
<!-- 加载中 -->
<template x-if="loading">
<div class="flex justify-center py-20">
<div class="w-8 h-8 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div>
</div>
</template>
<!-- 项目列表 -->
<template x-if="projects.length > 0">
<div>
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-bold text-gray-800">我的项目
<span class="ml-2 text-sm font-normal text-gray-400"><span x-text="projects.length"></span></span>
</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
<template x-for="p in projects" :key="p.id">
<div class="bg-white rounded-2xl border border-gray-100 shadow-sm hover:shadow-md transition-shadow cursor-pointer group"
@click="window.location='/project/'+p.id">
<div class="p-5">
<!-- 状态徽标 -->
<div class="flex items-start justify-between mb-3">
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 group-hover:text-blue-600 transition truncate" x-text="p.name"></h3>
<p class="text-xs text-gray-400 mt-1" x-text="formatDate(p.created_at)"></p>
</div>
<span class="ml-2 flex-shrink-0 px-2 py-0.5 rounded-full text-xs font-medium"
:class="statusBadge(p.parse_status).cls" x-text="statusBadge(p.parse_status).text"></span>
</div>
<!-- 文件名 -->
<div x-show="p.file_name" class="flex items-center gap-1.5 text-xs text-gray-500 mb-3">
<svg class="w-3.5 h-3.5" 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"/>
</svg>
<span class="truncate" x-text="p.file_name"></span>
</div>
<!-- 进度条 -->
<div x-show="p.section_count > 0" class="mb-3">
<div class="flex justify-between text-xs text-gray-500 mb-1">
<span>章节生成进度</span>
<span x-text="p.done_count + '/' + p.section_count"></span>
</div>
<div class="h-1.5 bg-gray-100 rounded-full overflow-hidden">
<div class="h-full bg-blue-500 rounded-full transition-all"
:style="'width:' + (p.section_count ? p.done_count/p.section_count*100 : 0) + '%'"></div>
</div>
</div>
<!-- 操作按钮 -->
<div class="flex gap-2 pt-3 border-t border-gray-50">
<button class="flex-1 text-xs text-blue-600 hover:text-blue-700 font-medium py-1 hover:bg-blue-50 rounded-lg transition"
@click.stop="window.location='/project/'+p.id">
进入项目
</button>
<button class="text-xs text-red-400 hover:text-red-600 font-medium px-3 py-1 hover:bg-red-50 rounded-lg transition"
@click.stop="deleteProject(p.id, p.name)">
删除
</button>
</div>
</div>
</div>
</template>
</div>
</div>
</template>
</main>
<!-- ══ 新建项目弹窗 ══ -->
<div x-show="showCreate" x-cloak class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-md p-6" @click.stop>
<h2 class="text-lg font-bold text-gray-900 mb-4">新建标书项目</h2>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">项目名称</label>
<input type="text" x-model="newProjectName" @keydown.enter="createProject()"
placeholder="例如XX智慧城市信息化建设项目"
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm">
</div>
<div class="flex gap-3">
<button @click="showCreate=false" class="flex-1 px-4 py-2 border border-gray-200 text-gray-600 rounded-xl text-sm hover:bg-gray-50 transition">取消</button>
<button @click="createProject()" :disabled="creating"
class="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-xl text-sm font-medium transition disabled:opacity-60">
<span x-show="!creating">创建项目</span>
<span x-show="creating">创建中...</span>
</button>
</div>
</div>
</div>
<!-- ══ AI 配置弹窗 ══ -->
<div x-show="showConfig" x-cloak class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-lg p-6" @click.stop>
<h2 class="text-lg font-bold text-gray-900 mb-4">AI 模型配置</h2>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">选择模型提供商</label>
<div class="grid grid-cols-3 gap-2">
<label class="flex items-center gap-2 p-3 border-2 rounded-xl cursor-pointer transition"
:class="cfg.model_provider==='qwen' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300'">
<input type="radio" name="provider" value="qwen" x-model="cfg.model_provider" class="accent-blue-600">
<div>
<p class="font-medium text-sm leading-tight">通义千问</p>
<p class="text-xs text-gray-400">Qwen · 阿里云</p>
</div>
</label>
<label class="flex items-center gap-2 p-3 border-2 rounded-xl cursor-pointer transition"
:class="cfg.model_provider==='deepseek' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300'">
<input type="radio" name="provider" value="deepseek" x-model="cfg.model_provider" class="accent-blue-600">
<div>
<p class="font-medium text-sm leading-tight">DeepSeek</p>
<p class="text-xs text-gray-400">高性价比 · 云端</p>
</div>
</label>
<label class="flex items-center gap-2 p-3 border-2 rounded-xl cursor-pointer transition"
:class="cfg.model_provider==='openai' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300'">
<input type="radio" name="provider" value="openai" x-model="cfg.model_provider" class="accent-blue-600">
<div>
<p class="font-medium text-sm leading-tight">OpenAI</p>
<p class="text-xs text-gray-400">GPT-4.1 · 云端</p>
</div>
</label>
<label class="flex items-center gap-2 p-3 border-2 rounded-xl cursor-pointer transition"
:class="cfg.model_provider==='doubao' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300'">
<input type="radio" name="provider" value="doubao" x-model="cfg.model_provider" class="accent-blue-600">
<div>
<p class="font-medium text-sm leading-tight">豆包</p>
<p class="text-xs text-gray-400">字节跳动 · 云端</p>
</div>
</label>
<label class="flex items-center gap-2 p-3 border-2 rounded-xl cursor-pointer transition"
:class="cfg.model_provider==='kimi' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300'">
<input type="radio" name="provider" value="kimi" x-model="cfg.model_provider" class="accent-blue-600">
<div>
<p class="font-medium text-sm leading-tight">Kimi</p>
<p class="text-xs text-gray-400">Moonshot · 长文本</p>
</div>
</label>
<label class="flex items-center gap-2 p-3 border-2 rounded-xl cursor-pointer transition"
:class="cfg.model_provider==='ollama' ? 'border-green-500 bg-green-50' : 'border-gray-200 hover:border-gray-300'">
<input type="radio" name="provider" value="ollama" x-model="cfg.model_provider" class="accent-green-600">
<div>
<p class="font-medium text-sm leading-tight">Ollama 本地</p>
<p class="text-xs text-gray-400">免费 · 离线</p>
</div>
</label>
</div>
</div>
<template x-if="cfg.model_provider==='qwen'">
<div class="space-y-3 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Qwen API Key
<a href="https://dashscope.aliyun.com/" target="_blank" class="ml-1 text-blue-500 text-xs hover:underline font-normal">申请地址 ↗</a>
</label>
<input type="password" x-model="cfg.qwen_api_key" placeholder="sk-..."
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<p x-show="cfg.has_qwen_key" class="text-xs text-green-600 mt-1">✓ 已配置</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">模型</label>
<select x-model="cfg.qwen_model" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<optgroup label="─── 旗舰版 ───">
<option value="qwen-max">qwen-max ★ 推荐</option>
<option value="qwen-max-latest">qwen-max-latest自动追踪最新</option>
</optgroup>
<optgroup label="─── 均衡版 ───">
<option value="qwen-plus">qwen-plus</option>
<option value="qwen-plus-latest">qwen-plus-latest自动追踪最新</option>
</optgroup>
<optgroup label="─── 快速版 ───">
<option value="qwen-turbo">qwen-turbo</option>
<option value="qwen-turbo-latest">qwen-turbo-latest自动追踪最新</option>
</optgroup>
<optgroup label="─── 超长上下文 ───">
<option value="qwen-long">qwen-long1M tokens</option>
</optgroup>
<optgroup label="─── Qwen3 系列 API ───">
<option value="qwen3-235b-a22b">qwen3-235b-a22bMoE 旗舰)</option>
<option value="qwen3-32b">qwen3-32b</option>
<option value="qwen3-30b-a3b">qwen3-30b-a3bMoE 高效)</option>
<option value="qwen3-14b">qwen3-14b</option>
<option value="qwen3-8b">qwen3-8b</option>
</optgroup>
<optgroup label="─── 自定义 ───">
<option value="">手动输入模型名</option>
</optgroup>
</select>
<div x-show="!qwenPresets.includes(cfg.qwen_model)" class="mt-2">
<input type="text" x-model="cfg.qwen_model" placeholder="输入模型名,如 qwen-max-2025-01-25"
class="w-full px-3 py-2 border border-blue-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
<p class="text-xs text-gray-400 mt-1">输入任意 DashScope 兼容的模型名</p>
</div>
</div>
<!-- 自定义 API 地址(可选,供代理/中转使用) -->
<div>
<button type="button" @click="cfg._qwen_adv = !cfg._qwen_adv"
class="text-xs text-gray-400 hover:text-gray-600 flex items-center gap-1">
<span x-text="cfg._qwen_adv ? '▾' : '▸'"></span>
高级:自定义 API 地址(代理/中转)
</button>
<div x-show="cfg._qwen_adv" class="mt-2">
<input type="text" x-model="cfg.qwen_base_url"
placeholder="https://dashscope.aliyuncs.com/compatible-mode/v1"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
<p class="text-xs text-gray-400 mt-1">默认https://dashscope.aliyuncs.com/compatible-mode/v1</p>
</div>
</div>
</div>
</template>
<template x-if="cfg.model_provider==='deepseek'">
<div class="space-y-3 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">DeepSeek API Key
<a href="https://platform.deepseek.com/" target="_blank" class="ml-1 text-blue-500 text-xs hover:underline font-normal">申请地址 ↗</a>
</label>
<input type="password" x-model="cfg.deepseek_api_key" placeholder="sk-..."
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<p x-show="cfg.has_deepseek_key" class="text-xs text-green-600 mt-1">✓ 已配置</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">模型</label>
<select x-model="cfg.deepseek_model" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<optgroup label="─── 对话模型 ───">
<option value="deepseek-chat">deepseek-chat ★ 推荐V3 最新)</option>
</optgroup>
<optgroup label="─── 推理模型 ───">
<option value="deepseek-reasoner">deepseek-reasonerR1</option>
</optgroup>
<optgroup label="─── 自定义 ───">
<option value="">手动输入模型名</option>
</optgroup>
</select>
<div x-show="!deepseekPresets.includes(cfg.deepseek_model)" class="mt-2">
<input type="text" x-model="cfg.deepseek_model" placeholder="输入模型名,如 deepseek-chat-v3-0324"
class="w-full px-3 py-2 border border-blue-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
<p class="text-xs text-gray-400 mt-1">输入任意 DeepSeek 兼容的模型名</p>
</div>
</div>
<!-- 自定义 API 地址 -->
<div>
<button type="button" @click="cfg._ds_adv = !cfg._ds_adv"
class="text-xs text-gray-400 hover:text-gray-600 flex items-center gap-1">
<span x-text="cfg._ds_adv ? '▾' : '▸'"></span>
高级:自定义 API 地址(代理/中转)
</button>
<div x-show="cfg._ds_adv" class="mt-2">
<input type="text" x-model="cfg.deepseek_base_url"
placeholder="https://api.deepseek.com/v1"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
<p class="text-xs text-gray-400 mt-1">默认https://api.deepseek.com/v1</p>
</div>
</div>
<div class="flex items-start gap-2 p-3 bg-amber-50 rounded-lg border border-amber-200 text-xs text-amber-700">
<svg class="w-4 h-4 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>DeepSeek 暂不提供 Embedding API知识库功能将自动使用本地默认模型需下载约 90MB 模型)。其他功能不受影响。</span>
</div>
</div>
</template>
<!-- ── 豆包配置面板 ── -->
<template x-if="cfg.model_provider==='doubao'">
<div class="space-y-3 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">豆包 API Key
<a href="https://console.volcengine.com/ark/" target="_blank" class="ml-1 text-blue-500 text-xs hover:underline font-normal">申请地址 ↗</a>
</label>
<input type="password" x-model="cfg.doubao_api_key" placeholder="sk-..."
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<p x-show="cfg.has_doubao_key" class="text-xs text-green-600 mt-1">✓ 已配置</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">模型</label>
<select x-model="cfg.doubao_model" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<optgroup label="─── 豆包 1.5 系列2025 最新)───">
<option value="doubao-1-5-pro-32k">doubao-1-5-pro-32k ★ 推荐</option>
<option value="doubao-1-5-pro-128k">doubao-1-5-pro-128k超长上下文</option>
<option value="doubao-1-5-lite-32k">doubao-1-5-lite-32k快速低价</option>
</optgroup>
<optgroup label="─── 豆包 Pro 系列 ───">
<option value="doubao-pro-32k">doubao-pro-32k</option>
<option value="doubao-pro-128k">doubao-pro-128k</option>
<option value="doubao-pro-256k">doubao-pro-256k超长</option>
</optgroup>
<optgroup label="─── 豆包 Lite 系列 ───">
<option value="doubao-lite-32k">doubao-lite-32k</option>
<option value="doubao-lite-128k">doubao-lite-128k</option>
</optgroup>
<optgroup label="─── 自定义 ───">
<option value="">手动输入模型名</option>
</optgroup>
</select>
<div x-show="!doubaoPresets.includes(cfg.doubao_model)" class="mt-2">
<input type="text" x-model="cfg.doubao_model" placeholder="输入模型名,如 doubao-1-5-pro-32k"
class="w-full px-3 py-2 border border-blue-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
<p class="text-xs text-gray-400 mt-1">输入火山引擎方舟平台支持的任意模型名</p>
</div>
</div>
<!-- 自定义 API 地址 -->
<div>
<button type="button" @click="cfg._doubao_adv = !cfg._doubao_adv"
class="text-xs text-gray-400 hover:text-gray-600 flex items-center gap-1">
<span x-text="cfg._doubao_adv ? '▾' : '▸'"></span>
高级:自定义 API 地址(代理/中转)
</button>
<div x-show="cfg._doubao_adv" class="mt-2">
<input type="text" x-model="cfg.doubao_base_url"
placeholder="https://ark.cn-beijing.volces.com/api/v3"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
<p class="text-xs text-gray-400 mt-1">默认https://ark.cn-beijing.volces.com/api/v3</p>
</div>
</div>
<div class="flex items-start gap-2 p-3 bg-amber-50 rounded-lg border border-amber-200 text-xs text-amber-700">
<svg class="w-4 h-4 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>豆包暂不提供通用 Embedding API知识库将自动使用关键词检索模式。其他功能完全正常。</span>
</div>
</div>
</template>
<!-- ── Kimi 配置面板 ── -->
<template x-if="cfg.model_provider==='kimi'">
<div class="space-y-3 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Kimi API Key
<a href="https://platform.moonshot.cn/" target="_blank" class="ml-1 text-blue-500 text-xs hover:underline font-normal">申请地址 ↗</a>
</label>
<input type="password" x-model="cfg.kimi_api_key" placeholder="sk-..."
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<p x-show="cfg.has_kimi_key" class="text-xs text-green-600 mt-1">✓ 已配置</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">模型</label>
<select x-model="cfg.kimi_model" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<optgroup label="─── Moonshot 系列 ───">
<option value="moonshot-v1-32k">moonshot-v1-32k ★ 推荐(均衡)</option>
<option value="moonshot-v1-128k">moonshot-v1-128k超长上下文</option>
<option value="moonshot-v1-8k">moonshot-v1-8k快速低价</option>
<option value="moonshot-v1-auto">moonshot-v1-auto自动选择</option>
</optgroup>
<optgroup label="─── 自定义 ───">
<option value="">手动输入模型名</option>
</optgroup>
</select>
<div x-show="!kimiPresets.includes(cfg.kimi_model)" class="mt-2">
<input type="text" x-model="cfg.kimi_model" placeholder="输入模型名,如 moonshot-v1-128k"
class="w-full px-3 py-2 border border-blue-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
<p class="text-xs text-gray-400 mt-1">输入 Moonshot 平台支持的任意模型名</p>
</div>
</div>
<!-- 自定义 API 地址 -->
<div>
<button type="button" @click="cfg._kimi_adv = !cfg._kimi_adv"
class="text-xs text-gray-400 hover:text-gray-600 flex items-center gap-1">
<span x-text="cfg._kimi_adv ? '▾' : '▸'"></span>
高级:自定义 API 地址(代理/中转)
</button>
<div x-show="cfg._kimi_adv" class="mt-2">
<input type="text" x-model="cfg.kimi_base_url"
placeholder="https://api.moonshot.cn/v1"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
<p class="text-xs text-gray-400 mt-1">默认https://api.moonshot.cn/v1</p>
</div>
</div>
<div class="flex items-start gap-2 p-3 bg-teal-50 rounded-lg border border-teal-200 text-xs text-teal-700">
<svg class="w-4 h-4 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>Kimi 支持 Embedding APImoonshot-v1-embedding知识库将使用语义向量检索效果更佳。</span>
</div>
</div>
</template>
<template x-if="cfg.model_provider==='openai'">
<div class="space-y-3 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">OpenAI API Key
<a href="https://platform.openai.com/" target="_blank" class="ml-1 text-blue-500 text-xs hover:underline font-normal">申请地址 ↗</a>
</label>
<input type="password" x-model="cfg.openai_api_key" placeholder="sk-..."
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<p x-show="cfg.has_openai_key" class="text-xs text-green-600 mt-1">✓ 已配置</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">模型</label>
<select x-model="cfg.openai_model" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<optgroup label="─── GPT-4.1 系列2025───">
<option value="gpt-4.1">gpt-4.1 ★ 推荐旗舰1M 上下文)</option>
<option value="gpt-4.1-mini">gpt-4.1-mini快速均衡</option>
<option value="gpt-4.1-nano">gpt-4.1-nano最轻量低价</option>
</optgroup>
<optgroup label="─── o 推理系列(深度推理,适合复杂标书)───">
<option value="o4-mini">o4-mini推理高性价比</option>
<option value="o3">o3最强推理较慢</option>
<option value="o3-mini">o3-mini快速推理</option>
<option value="o1">o1深度推理</option>
<option value="o1-mini">o1-mini推理入门</option>
<option value="o1-pro">o1-pro最高质量推理</option>
</optgroup>
<optgroup label="─── GPT-4o 系列 ───">
<option value="gpt-4o">gpt-4o</option>
<option value="gpt-4o-mini">gpt-4o-mini</option>
</optgroup>
<optgroup label="─── 旧版 ───">
<option value="gpt-4-turbo">gpt-4-turbo</option>
</optgroup>
<optgroup label="─── 自定义 ───">
<option value="">手动输入模型名</option>
</optgroup>
</select>
<div x-show="!openaiPresets.includes(cfg.openai_model)" class="mt-2">
<input type="text" x-model="cfg.openai_model" placeholder="输入模型名,如 gpt-5、gpt-4.1-2025-04-14"
class="w-full px-3 py-2 border border-blue-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
<p class="text-xs text-gray-400 mt-1">输入 OpenAI 平台支持的任意模型名</p>
</div>
</div>
<!-- 自定义 API 地址 -->
<div>
<button type="button" @click="cfg._oai_adv = !cfg._oai_adv"
class="text-xs text-gray-400 hover:text-gray-600 flex items-center gap-1">
<span x-text="cfg._oai_adv ? '▾' : '▸'"></span>
高级:自定义 API 地址Azure / 代理 / 中转)
</button>
<div x-show="cfg._oai_adv" class="mt-2">
<input type="text" x-model="cfg.openai_base_url"
placeholder="https://api.openai.com/v1"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
<p class="text-xs text-gray-400 mt-1">默认https://api.openai.com/v1 &nbsp;|&nbsp; Azure 示例https://YOUR.openai.azure.com/openai/deployments/MODEL</p>
</div>
</div>
</div>
</template>
<template x-if="cfg.model_provider==='ollama'">
<div class="space-y-3 mb-4">
<!-- 状态检测 -->
<div class="flex items-center justify-between p-3 bg-green-50 rounded-lg border border-green-200">
<div class="flex items-center gap-2 text-xs text-green-700">
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>本地运行,数据不上传云端,完全免费</span>
</div>
<button type="button" @click="testOllama()"
class="text-xs px-2 py-1 bg-green-600 hover:bg-green-700 text-white rounded-lg transition flex-shrink-0">
检测连接
</button>
</div>
<!-- Ollama 服务地址 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
Ollama 服务地址
<span class="ml-1 text-xs text-gray-400 font-normal">(默认本机)</span>
</label>
<input type="text" x-model="cfg.ollama_base_url" placeholder="http://localhost:11434/v1"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500 font-mono">
</div>
<!-- 模型选择 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">选择模型</label>
<select x-model="cfg.ollama_model"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500">
<optgroup label="★ 推荐:标书写作首选">
<option value="qwen3:8b">qwen3:8b ★ 推荐入门(约 5 GB</option>
<option value="qwen3:14b">qwen3:14b ★ 推荐均衡(约 9 GB</option>
<option value="qwen3:32b">qwen3:32b ★ 推荐高质量(约 20 GB</option>
<option value="deepseek-r1:14b">deepseek-r1:14b ★ 推荐推理(约 9 GB</option>
<option value="deepseek-r1:32b">deepseek-r1:32b ★ 推荐高质量推理(约 20 GB</option>
</optgroup>
<optgroup label="─── Qwen3 系列阿里2025最新───">
<option value="qwen3:0.6b">qwen3:0.6b(最轻量,约 0.5 GB</option>
<option value="qwen3:1.7b">qwen3:1.7b(约 1 GB</option>
<option value="qwen3:4b">qwen3:4b约 2.5 GB</option>
<option value="qwen3:8b">qwen3:8b约 5 GB</option>
<option value="qwen3:14b">qwen3:14b约 9 GB</option>
<option value="qwen3:32b">qwen3:32b约 20 GB</option>
<option value="qwen3:30b-a3b">qwen3:30b-a3bMoE 高效,约 19 GB</option>
<option value="qwen3:235b-a22b">qwen3:235b-a22bMoE 旗舰,约 142 GB</option>
</optgroup>
<optgroup label="─── Qwen2.5 系列(阿里)───">
<option value="qwen2.5:0.5b">qwen2.5:0.5b(约 0.4 GB</option>
<option value="qwen2.5:1.5b">qwen2.5:1.5b(约 1 GB</option>
<option value="qwen2.5:3b">qwen2.5:3b约 2 GB</option>
<option value="qwen2.5:7b">qwen2.5:7b约 4.7 GB</option>
<option value="qwen2.5:14b">qwen2.5:14b约 9 GB</option>
<option value="qwen2.5:32b">qwen2.5:32b约 20 GB</option>
<option value="qwen2.5:72b">qwen2.5:72b约 47 GB</option>
</optgroup>
<optgroup label="─── Qwen2.5-Coder 系列(代码增强)───">
<option value="qwen2.5-coder:1.5b">qwen2.5-coder:1.5b(约 1 GB</option>
<option value="qwen2.5-coder:3b">qwen2.5-coder:3b约 2 GB</option>
<option value="qwen2.5-coder:7b">qwen2.5-coder:7b约 4.7 GB</option>
<option value="qwen2.5-coder:14b">qwen2.5-coder:14b约 9 GB</option>
<option value="qwen2.5-coder:32b">qwen2.5-coder:32b约 20 GB</option>
</optgroup>
<optgroup label="─── QwQ 系列(阿里深度推理)───">
<option value="qwq:32b">qwq:32b深度推理约 20 GB</option>
</optgroup>
<optgroup label="─── DeepSeek R1 系列(推理增强)───">
<option value="deepseek-r1:1.5b">deepseek-r1:1.5b(约 1 GB</option>
<option value="deepseek-r1:7b">deepseek-r1:7b约 4.7 GB</option>
<option value="deepseek-r1:8b">deepseek-r1:8b约 5 GB</option>
<option value="deepseek-r1:14b">deepseek-r1:14b约 9 GB</option>
<option value="deepseek-r1:32b">deepseek-r1:32b约 20 GB</option>
<option value="deepseek-r1:70b">deepseek-r1:70b约 43 GB</option>
<option value="deepseek-r1:671b">deepseek-r1:671b原版需超大显存</option>
</optgroup>
<optgroup label="─── DeepSeek V2 系列 ───">
<option value="deepseek-v2:16b">deepseek-v2:16bLite约 10 GB</option>
<option value="deepseek-v2:236b">deepseek-v2:236b全量约 150 GB</option>
</optgroup>
<optgroup label="─── DeepSeek V3 系列 ───">
<option value="deepseek-v3:7b">deepseek-v3:7b约 4.7 GB</option>
<option value="deepseek-v3:671b">deepseek-v3:671b完整版需超大显存</option>
</optgroup>
<optgroup label="─── 自定义 ───">
<option value="">手动输入模型名</option>
</optgroup>
</select>
</div>
<!-- 自定义模型名(选中"手动输入"或填入了预设外的值时显示) -->
<div x-show="!ollamaPresets.includes(cfg.ollama_model)">
<input type="text" x-model="cfg.ollama_model" placeholder="例如qwen3:latest 或 deepseek-r1:latest"
class="w-full px-3 py-2 border border-green-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500 font-mono">
<p class="text-xs text-gray-400 mt-1">请输入已通过 <code class="bg-gray-100 px-1 rounded">ollama pull &lt;模型名&gt;</code> 下载的模型名</p>
</div>
<div class="flex items-start gap-2 p-3 bg-amber-50 rounded-lg border border-amber-200 text-xs text-amber-700">
<svg class="w-4 h-4 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>使用前请先安装 <a href="https://ollama.com/" target="_blank" class="underline">Ollama</a> 并下载模型,例如:<br>
<code class="bg-amber-100 px-1 rounded">ollama pull qwen3:14b</code>Qwen3 推荐)&nbsp;
<code class="bg-amber-100 px-1 rounded">ollama pull deepseek-r1:14b</code>DeepSeek R1 推荐)<br>
Ollama 本地不支持知识库 Embedding该功能将自动回退到本地模型。推理模型R1/QwQ可能输出 <code class="bg-amber-100 px-1 rounded">&lt;think&gt;</code> 标签,不影响正文使用。</span>
</div>
</div>
</template>
<!-- 标书篇幅设置 -->
<div class="mb-4 pt-3 border-t border-gray-100">
<label class="block text-sm font-medium text-gray-700 mb-2">
标书篇幅预期
<span class="ml-1 text-xs text-gray-400 font-normal">(控制每个章节生成内容的字数)</span>
</label>
<div class="grid grid-cols-2 gap-2">
<label class="flex items-center gap-2.5 p-3 border-2 rounded-xl cursor-pointer transition"
:class="cfg.content_volume==='concise' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300'">
<input type="radio" name="volume" value="concise" x-model="cfg.content_volume" class="accent-blue-600">
<div>
<p class="font-medium text-sm leading-tight">精简版</p>
<p class="text-xs text-gray-400">每节约 1200 字</p>
</div>
</label>
<label class="flex items-center gap-2.5 p-3 border-2 rounded-xl cursor-pointer transition"
:class="cfg.content_volume==='standard' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300'">
<input type="radio" name="volume" value="standard" x-model="cfg.content_volume" class="accent-blue-600">
<div>
<p class="font-medium text-sm leading-tight">标准版(推荐)</p>
<p class="text-xs text-gray-400">每节约 2000 字</p>
</div>
</label>
<label class="flex items-center gap-2.5 p-3 border-2 rounded-xl cursor-pointer transition"
:class="cfg.content_volume==='detailed' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300'">
<input type="radio" name="volume" value="detailed" x-model="cfg.content_volume" class="accent-blue-600">
<div>
<p class="font-medium text-sm leading-tight">详细版</p>
<p class="text-xs text-gray-400">每节约 3000 字</p>
</div>
</label>
<label class="flex items-center gap-2.5 p-3 border-2 rounded-xl cursor-pointer transition"
:class="cfg.content_volume==='full' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300'">
<input type="radio" name="volume" value="full" x-model="cfg.content_volume" class="accent-blue-600">
<div>
<p class="font-medium text-sm leading-tight">充实版</p>
<p class="text-xs text-gray-400">每节约 4000 字</p>
</div>
</label>
</div>
</div>
<!-- 并发生成设置 -->
<div class="mb-4 pt-3 border-t border-gray-100">
<label class="block text-sm font-medium text-gray-700 mb-2">
并发生成章节数
<span class="ml-1 text-xs text-gray-400 font-normal">(同时调用 AI 的线程数,越大越快但需注意 API 限流)</span>
</label>
<div class="flex items-center gap-3">
<input type="range" min="1" max="10" step="1" x-model.number="cfg.max_concurrent"
class="flex-1 accent-blue-600">
<span class="w-12 text-center text-sm font-bold text-blue-600 bg-blue-50 rounded-lg py-1"
x-text="cfg.max_concurrent + ' 路'"></span>
</div>
<div class="flex justify-between text-xs text-gray-400 mt-1 px-0.5">
<span>保守1路</span>
<span>推荐3-5路</span>
<span>激进10路</span>
</div>
</div>
<div class="flex gap-3 mt-2">
<button @click="showConfig=false" class="flex-1 px-4 py-2 border border-gray-200 text-gray-600 rounded-xl text-sm hover:bg-gray-50 transition">取消</button>
<button @click="saveConfig()" class="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-xl text-sm font-medium transition">保存配置</button>
</div>
</div>
</div>
<script>
function app() {
return {
projects: [],
loading: true,
showCreate: false,
showConfig: false,
newProjectName: '',
creating: false,
cfg: {
model_provider: 'qwen',
qwen_api_key: '', qwen_model: 'qwen-max', qwen_base_url: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
openai_api_key: '', openai_model: 'gpt-4.1', openai_base_url: 'https://api.openai.com/v1',
deepseek_api_key: '', deepseek_model: 'deepseek-chat', deepseek_base_url: 'https://api.deepseek.com/v1',
ollama_base_url: 'http://localhost:11434/v1', ollama_model: 'qwen3:8b',
doubao_api_key: '', doubao_model: 'doubao-1-5-pro-32k', doubao_base_url: 'https://ark.cn-beijing.volces.com/api/v3',
kimi_api_key: '', kimi_model: 'moonshot-v1-32k', kimi_base_url: 'https://api.moonshot.cn/v1',
max_concurrent: 5, content_volume: 'standard',
_qwen_adv: false, _ds_adv: false, _oai_adv: false, _doubao_adv: false, _kimi_adv: false,
},
qwenPresets: [
'qwen-max','qwen-max-latest','qwen-plus','qwen-plus-latest',
'qwen-turbo','qwen-turbo-latest','qwen-long',
'qwen3-235b-a22b','qwen3-32b','qwen3-30b-a3b','qwen3-14b','qwen3-8b',
],
deepseekPresets: ['deepseek-chat','deepseek-reasoner'],
openaiPresets: [
'gpt-4.1','gpt-4.1-mini','gpt-4.1-nano',
'o4-mini','o3','o3-mini','o1','o1-mini','o1-pro',
'gpt-4o','gpt-4o-mini','gpt-4-turbo',
],
doubaoPresets: [
'doubao-1-5-pro-32k', 'doubao-1-5-pro-128k', 'doubao-1-5-lite-32k',
'doubao-pro-32k', 'doubao-pro-128k', 'doubao-pro-256k',
'doubao-lite-32k', 'doubao-lite-128k',
],
kimiPresets: [
'moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k', 'moonshot-v1-auto',
],
ollamaPresets: [
// 推荐
'qwen3:8b','qwen3:14b','qwen3:32b','deepseek-r1:14b','deepseek-r1:32b',
// Qwen3
'qwen3:0.6b','qwen3:1.7b','qwen3:4b','qwen3:30b-a3b','qwen3:235b-a22b',
// Qwen2.5
'qwen2.5:0.5b','qwen2.5:1.5b','qwen2.5:3b','qwen2.5:7b','qwen2.5:14b','qwen2.5:32b','qwen2.5:72b',
// Qwen2.5-Coder
'qwen2.5-coder:1.5b','qwen2.5-coder:3b','qwen2.5-coder:7b','qwen2.5-coder:14b','qwen2.5-coder:32b',
// QwQ
'qwq:32b',
// DeepSeek R1
'deepseek-r1:1.5b','deepseek-r1:7b','deepseek-r1:8b','deepseek-r1:70b','deepseek-r1:671b',
// DeepSeek V2
'deepseek-v2:16b','deepseek-v2:236b',
// DeepSeek V3
'deepseek-v3:7b','deepseek-v3:671b',
],
async init() {
await Promise.all([this.loadProjects(), this.loadConfig()])
this.loading = false
},
async loadProjects() {
const res = await fetch('/api/projects')
const data = await res.json()
this.projects = data.projects || []
},
async loadConfig() {
const res = await fetch('/api/config')
const data = await res.json()
this.cfg = { ...this.cfg, ...data }
},
async createProject() {
if (!this.newProjectName.trim()) return
this.creating = true
const res = await fetch('/api/projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: this.newProjectName.trim() })
})
const data = await res.json()
this.creating = false
this.showCreate = false
this.newProjectName = ''
if (data.id) {
window.location = '/project/' + data.id
}
},
async deleteProject(id, name) {
if (!confirm(`确定要删除项目"${name}"吗?此操作不可恢复。`)) return
await fetch('/api/projects/' + id, { method: 'DELETE' })
this.projects = this.projects.filter(p => p.id !== id)
},
async saveConfig() {
await fetch('/api/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.cfg)
})
this.showConfig = false
alert('配置已保存')
},
async testOllama() {
const baseUrl = (this.cfg.ollama_base_url || 'http://localhost:11434/v1').replace(/\/v1\/?$/, '')
try {
const res = await fetch(baseUrl + '/api/tags', { signal: AbortSignal.timeout(3000) })
if (res.ok) {
const data = await res.json()
const models = (data.models || []).map(m => m.name).join('、') || '(暂无已下载模型)'
alert('✅ Ollama 连接成功!\n已下载模型' + models)
} else {
alert('⚠️ Ollama 已启动,但返回状态异常:' + res.status)
}
} catch (e) {
alert('❌ 无法连接到 Ollama' + (this.cfg.ollama_base_url || 'http://localhost:11434/v1') + '\n\n请确认\n1. 已安装 Ollamahttps://ollama.com\n2. Ollama 服务正在运行\n3. 服务地址填写正确')
}
},
formatDate(dt) {
if (!dt) return ''
const d = new Date(dt)
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
},
statusBadge(status) {
const map = {
'none': { text: '未上传', cls: 'bg-gray-100 text-gray-500' },
'uploaded': { text: '待解析', cls: 'bg-yellow-100 text-yellow-700' },
'parsing': { text: '解析中', cls: 'bg-blue-100 text-blue-700' },
'done': { text: '已解析', cls: 'bg-green-100 text-green-700' },
'error': { text: '解析失败', cls: 'bg-red-100 text-red-600' },
}
return map[status] || map['none']
}
}
}
</script>
<!-- 页脚版权声明 -->
<footer class="mt-auto py-4 px-6 border-t border-gray-200 bg-white text-center text-xs text-gray-500 space-y-1">
<p class="font-medium text-gray-600">© 标书老崔</p>
<p>本工具仅限学习交流免费使用,生成的技术方案请人工核对。本工具不会在任何平台售卖,请注意甄别。</p>
</footer>
</body>
</html>