2026-04-23 14:36:26 +08:00

1227 lines
68 KiB
HTML
Raw Permalink 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="showStyleSettings = true"
class="flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-gray-700 hover:text-indigo-600 hover:bg-indigo-50 rounded-lg transition border border-transparent hover:border-indigo-200">
<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="M7 21a4 4 0 01-4-4V5a2 2 0 012-2 2 2 0 01-2-2 2 2 0 012-2 2 2 0 01-2-2 2 2 0 012-2 2 2 0 01-2-2 2 2 0 012-2zM17 21a4 4 0 01-4-4V5a2 2 0 012-2 2 2 0 01-2-2 2 2 0 012-2 2 2 0 01-2-2 2 2 0 012-2z"/>
</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">
<!-- 篇幅目标仅存在于「标书项目 → 步骤1 解析」;本页不重复控件 -->
<div x-show="!loading" x-cloak class="mb-6 p-3.5 bg-slate-50 border border-slate-200 rounded-xl text-sm text-slate-600 leading-relaxed">
<p><strong>篇幅目标(按页数粗略换算)</strong>请进入某标书项目,在 <strong>步骤1「解析」</strong> 中设置100/150/200/250/300 页、自定义、保存页数设置、使用原档位、当前页等,保存后用于后续章节生成。</p>
</div>
<!-- 欢迎横幅(无项目时显示) -->
<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="─── Qwen3.6(本项默认:生成+解析)───">
<option value="qwen3.6-plus">qwen3.6-plus ★ 默认</option>
</optgroup>
<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">
<p class="text-xs text-gray-500 mb-3 p-2 bg-slate-50 rounded-lg">
全稿「目标总页数」在<strong>已打开的标书项目</strong>中,到 <strong>步骤1「解析」</strong> 里设置,与下方「每节字数档」是两项不同设置。
</p>
<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="20" 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>推荐8-12路</span>
<span>极速20路</span>
</div>
<p class="text-[10px] text-amber-600 mt-1">全局LLM信号量上限20已集成速率保护。Qwen云建议≤15避免限流。</p>
</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>
<!-- 文件样式设置 Modal (full replica of the provided image UI) -->
<div x-show="showStyleSettings" class="fixed inset-0 bg-black/60 flex items-center justify-center z-[9999] p-4" @click.self="showStyleSettings = false">
<div class="bg-white rounded-3xl shadow-2xl max-w-6xl w-full max-h-[92vh] overflow-hidden flex flex-col" @click.stop>
<!-- Header -->
<div class="px-8 py-5 border-b flex items-center justify-between bg-gradient-to-r from-indigo-50 to-blue-50">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-indigo-600 text-white rounded-2xl flex items-center justify-center text-xl">📐</div>
<div>
<h2 class="text-2xl font-bold text-gray-900">文件样式设置</h2>
<p class="text-sm text-gray-500">定义标书导出Word的完整格式规范支持方案保存与复用</p>
</div>
</div>
<div class="flex items-center gap-4">
<select x-model="stylePreset" class="px-4 py-2 border border-gray-300 rounded-2xl text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="standard">标准投标格式 (100页, 宋体小四, 黑体标题)</option>
<option value="detailed">详细技术方案 (200页, 宋体小四, 更多图表)</option>
<option value="concise">精简版 (50页, 紧凑布局)</option>
<option value="engineering">工程施工组织设计 (标准工程标)</option>
<option value="service">服务实施方案 (服务类专用)</option>
<option value="goods">货物技术规格书 (货物类专用)</option>
<option value="long">超长版 (300页, 详细目录)</option>
<option value="custom">自定义方案</option>
</select>
<button @click="saveStylePreset()" class="px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium rounded-2xl transition flex items-center gap-2">
<span>保存方案</span>
</button>
<button @click="showStyleSettings = false" class="text-gray-400 hover:text-gray-600"></button>
</div>
</div>
<!-- Body - Replica of image layout -->
<div class="flex-1 overflow-auto p-8 bg-slate-50">
<div class="grid grid-cols-12 gap-8">
<!-- Left column: Type and presets -->
<div class="col-span-5 space-y-8">
<!-- 正文类型 -->
<div>
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-lg">正文类型</h3>
<div class="flex gap-6 text-sm">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" :checked="bodyMode==='text_only'" @change="bodyMode='text_only'" class="accent-indigo-600">
<span>正文生成</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" :checked="bodyMode==='with_figures_tables'" @change="bodyMode='with_figures_tables'" class="accent-indigo-600">
<span>图文并茂</span>
</label>
</div>
</div>
<div class="grid grid-cols-4 gap-3">
<div @click="figureEnabled = !figureEnabled" class="bg-white border-2 rounded-2xl p-3 cursor-pointer transition" :class="figureEnabled ? 'border-indigo-500 shadow' : 'border-gray-200'">
<img src="https://via.placeholder.com/80x110/4f46e5/ffffff?text=图文" class="mx-auto mb-2 rounded">
<p class="text-center text-xs font-medium">图文</p>
</div>
<div class="bg-white border-2 border-indigo-500 rounded-2xl p-3 shadow ring-2 ring-indigo-200">
<img src="https://via.placeholder.com/80x110/10b981/ffffff?text=纯文本" class="mx-auto mb-2 rounded">
<p class="text-center text-xs font-medium text-indigo-600">纯文本</p>
</div>
<!-- More layout presets from image -->
<div class="bg-white border border-gray-200 rounded-2xl p-3 cursor-pointer hover:border-gray-300 transition">
<img src="https://via.placeholder.com/80x110/64748b/ffffff?text=左右" class="mx-auto mb-2 rounded">
<p class="text-center text-xs">左右栏</p>
</div>
<div class="bg-white border border-gray-200 rounded-2xl p-3 cursor-pointer hover:border-gray-300 transition">
<img src="https://via.placeholder.com/80x110/64748b/ffffff?text=上下" class="mx-auto mb-2 rounded">
<p class="text-center text-xs">上下栏</p>
</div>
</div>
</div>
<!-- 序号样式 (Word目录格式 presets) -->
<div>
<h3 class="font-semibold text-lg mb-4">序号样式</h3>
<div class="grid grid-cols-5 gap-3">
<div class="bg-white border-2 border-emerald-500 rounded-2xl p-2 cursor-pointer shadow">
<div class="h-20 bg-emerald-100 rounded flex items-center justify-center text-xs font-mono">一、</div>
<p class="text-center text-[10px] mt-1 text-emerald-700">标准</p>
</div>
<!-- Additional presets from image (1.1, (一), etc.) -->
<div class="bg-white border border-gray-200 rounded-2xl p-2 cursor-pointer hover:border-gray-300">
<div class="h-20 bg-gray-100 rounded flex items-center justify-center text-xs font-mono">1.1</div>
<p class="text-center text-[10px] mt-1">数字</p>
</div>
<div class="bg-white border border-gray-200 rounded-2xl p-2 cursor-pointer hover:border-gray-300">
<div class="h-20 bg-gray-100 rounded flex items-center justify-center text-xs font-mono">(一)</div>
<p class="text-center text-[10px] mt-1">括号</p>
</div>
</div>
</div>
<!-- 封面样式 presets -->
<div>
<h3 class="font-semibold text-lg mb-4">封面样式</h3>
<div class="grid grid-cols-5 gap-3">
<div class="border-2 border-blue-500 rounded-2xl overflow-hidden cursor-pointer shadow">
<img src="https://via.placeholder.com/80x110/1e40af/ffffff?text=投标文件" class="w-full">
<p class="text-center text-xs py-1 bg-blue-50 text-blue-700">投标文件</p>
</div>
<div class="border border-gray-200 rounded-2xl overflow-hidden cursor-pointer hover:border-gray-300">
<img src="https://via.placeholder.com/80x110/334155/ffffff?text=投标书" class="w-full">
<p class="text-center text-xs py-1">投标书</p>
</div>
<!-- More from image: 投标文件, 投标技术文件, etc. -->
</div>
</div>
</div>
<!-- Right column: Detailed controls (正文文字, 标题, 落款, 目录, 页眉页脚, 页数) -->
<div class="col-span-7 space-y-8 bg-white p-6 rounded-3xl border">
<!-- 正文文字 -->
<div class="border-b pb-6">
<h4 class="font-semibold mb-4 flex items-center gap-2"><span class="text-emerald-500">正文文字</span></h4>
<div class="grid grid-cols-2 gap-x-8 gap-y-4 text-sm">
<div>
<label class="block text-xs text-gray-500 mb-1">字体</label>
<select x-model="bodyFont" class="w-full border border-gray-300 rounded-xl px-3 py-2">
<option>宋体</option>
<option>黑体</option>
<option>仿宋_GB2312</option>
<option>楷体_GB2312</option>
<option>隶书</option>
<option>微软雅黑</option>
<option>等线</option>
<option>方正小标宋</option>
<option>华文细黑</option>
<option>Arial</option>
<option>Times New Roman</option>
<option>Calibri</option>
<option>Georgia</option>
<option>Verdana</option>
<option>Courier New</option>
</select>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">字号</label>
<select x-model="bodySize" class="w-full border border-gray-300 rounded-xl px-3 py-2">
<option>初号 (42pt)</option>
<option>一号 (26pt)</option>
<option>二号 (22pt)</option>
<option>三号 (16pt)</option>
<option>小三 (15pt)</option>
<option>四号 (14pt)</option>
<option>小四 (12pt)</option>
<option>五号 (10.5pt)</option>
<option>小五 (9pt)</option>
<option>六号 (7.5pt)</option>
<option>小六 (6.5pt)</option>
<option>七号 (5.5pt)</option>
<option>八号 (5pt)</option>
</select>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">行距</label>
<select x-model="bodyLineSpacing" class="w-full border border-gray-300 rounded-xl px-3 py-2">
<option value="1.0">1.0倍 (单倍)</option>
<option value="1.15">1.15倍 (Office默认)</option>
<option value="1.5" selected>1.5倍 (标准标书)</option>
<option value="2.0">2.0倍 (宽松)</option>
<option value="18">固定 18pt</option>
<option value="20">固定 20pt</option>
<option value="22">固定 22pt</option>
<option value="24">固定 24pt (推荐)</option>
<option value="26">固定 26pt</option>
<option value="28">固定 28pt</option>
<option value="30">固定 30pt</option>
</select>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">首行缩进</label>
<select x-model="bodyIndent" class="w-full border border-gray-300 rounded-xl px-3 py-2">
<option value="0">无缩进</option>
<option value="1">1字符</option>
<option value="2" selected>2字符 (标准)</option>
<option value="3">3字符</option>
<option value="24">24磅</option>
<option value="28">28磅</option>
<option value="32">32磅</option>
<option value="36">36磅 (宽松)</option>
</select>
</div>
</div>
</div>
<!-- 标题文字, 落款, 目录, 页眉页脚 (abbreviated for space but full in real) -->
<div class="grid grid-cols-2 gap-8">
<div>
<h4 class="font-semibold mb-3">标题文字</h4>
<div class="grid grid-cols-2 gap-4 text-xs">
<div>
<label class="block text-[10px] text-gray-500">一级标题</label>
<select x-model="heading1Font" class="w-full border border-gray-300 rounded-xl px-2 py-1 text-[10px]">
<option>黑体</option>
<option>宋体</option>
<option>仿宋</option>
<option>楷体</option>
<option>隶书</option>
<option>微软雅黑</option>
</select>
<select x-model="heading1Size" class="w-full border border-gray-300 rounded-xl px-2 py-1 text-[10px] mt-1">
<option>一号 (26pt)</option>
<option>二号 (22pt)</option>
<option selected>三号 (16pt)</option>
<option>小三 (15pt)</option>
<option>四号 (14pt)</option>
</select>
</div>
<div>
<label class="block text-[10px] text-gray-500">二级标题</label>
<select x-model="heading2Font" class="w-full border border-gray-300 rounded-xl px-2 py-1 text-[10px]">
<option>黑体</option>
<option>宋体</option>
<option selected>仿宋</option>
<option>楷体</option>
</select>
<select x-model="heading2Size" class="w-full border border-gray-300 rounded-xl px-2 py-1 text-[10px] mt-1">
<option>三号 (16pt)</option>
<option selected>四号 (14pt)</option>
<option>小四 (12pt)</option>
<option>五号 (10.5pt)</option>
</select>
</div>
</div>
</div>
<div>
<h4 class="font-semibold mb-3">页眉页脚</h4>
<div class="bg-slate-50 p-3 rounded-2xl text-xs space-y-2">
<div>页眉: <span class="font-medium" x-text="headerText"></span></div>
<input x-model="headerText" class="w-full text-xs border rounded-xl px-3 py-1">
<div>页脚: <span class="font-medium" x-text="footerText"></span></div>
<input x-model="footerText" class="w-full text-xs border rounded-xl px-3 py-1">
</div>
</div>
</div>
<!-- 方案页数 slider (bottom of image) -->
<div class="pt-4 border-t">
<div class="flex justify-between text-sm mb-2">
<span class="font-medium">方案页数</span>
<span class="font-mono text-indigo-600" x-text="pageCountTarget + ' 页'"></span>
</div>
<input type="range" min="5" max="5000" step="5" x-model.number="pageCountTarget" class="w-full accent-indigo-600">
<div class="flex justify-between text-[10px] text-gray-400 mt-1">
<span>5</span>
<span>5000</span>
</div>
<p class="text-xs text-gray-500 mt-3">选择页数范围后系统将根据设置自动调整字数、行距、页眉等参数生成符合文件样式规范的Word文档。</p>
</div>
</div>
</div>
</div>
<!-- Footer actions -->
<div class="px-8 py-4 border-t bg-white flex justify-end gap-3">
<button @click="showStyleSettings = false" class="px-6 py-2.5 text-gray-600 hover:bg-gray-100 rounded-2xl">取消</button>
<button @click="saveStylePreset(); showStyleSettings = false" class="px-8 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-2xl font-medium">应用到当前项目并保存</button>
</div>
</div>
</div>
<script>
function app() {
return {
projects: [],
loading: true,
showCreate: false,
showConfig: false,
showStyleSettings: false,
newProjectName: '',
creating: false,
// Style settings state (full replica of image UI with many dropdown options)
stylePreset: 'standard',
bodyMode: 'text_only',
figureEnabled: true,
tableEnabled: true,
bodyFont: '宋体',
bodySize: '小四',
bodyLineSpacing: 1.5,
bodyIndent: 2,
heading1Font: '黑体',
heading1Size: '三号',
heading2Font: '黑体',
heading2Size: '四号',
footerFont: '宋体',
footerSize: '五号',
pageCountTarget: 100,
margins: { top: 2.54, bottom: 2.54, left: 3.18, right: 3.18 },
headerText: '标桥AI编标',
footerText: '第 X 页 / 共 Y 页',
alignment: '两端对齐',
cfg: {
model_provider: 'qwen',
target_pages: 0,
qwen_api_key: '', qwen_model: 'qwen3.6-plus', 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: 12, content_volume: 'standard', // 默认提升至12支持极速20并发
_qwen_adv: false, _ds_adv: false, _oai_adv: false, _doubao_adv: false, _kimi_adv: false,
},
qwenPresets: [
'qwen3.6-plus',
'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.loadDefaultStyle() // Load style settings for the new homepage menu
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('配置已保存')
},
// Style settings methods for homepage menu
async saveStylePreset() {
const presetData = {
name: this.stylePreset,
config: {
bodyMode: this.bodyMode,
figureEnabled: this.figureEnabled,
tableEnabled: this.tableEnabled,
bodyFont: this.bodyFont,
bodySize: this.bodySize,
bodyLineSpacing: this.bodyLineSpacing,
headingFont: this.headingFont,
headingSize: this.headingSize,
footerFont: this.footerFont,
footerSize: this.footerSize,
pageCountTarget: this.pageCountTarget,
margins: this.margins,
headerText: this.headerText,
footerText: this.footerText,
}
}
const res = await fetch('/api/styles', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(presetData)
})
const data = await res.json()
if (data.success) {
alert('样式方案已保存!可在导出时选择使用。')
} else {
alert('保存失败: ' + (data.error || '未知错误'))
}
},
loadDefaultStyle() {
// Load from API or default
this.stylePreset = 'standard'
this.bodyMode = 'text_only'
this.figureEnabled = true
this.tableEnabled = true
this.bodyFont = '宋体'
this.bodySize = '小四'
this.bodyLineSpacing = 1.5
this.headingFont = '黑体'
this.headingSize = '三号'
this.footerFont = '宋体'
this.footerSize = '五号'
this.pageCountTarget = 100
this.margins = { top: 2.54, bottom: 2.54, left: 3.18, right: 3.18 }
this.headerText = '标桥AI编标'
this.footerText = '第 X 页 / 共 Y 页'
},
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>