869 lines
49 KiB
HTML
869 lines
49 KiB
HTML
<!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-long(1M tokens)</option>
|
||
</optgroup>
|
||
<optgroup label="─── Qwen3 系列 API ───">
|
||
<option value="qwen3-235b-a22b">qwen3-235b-a22b(MoE 旗舰)</option>
|
||
<option value="qwen3-32b">qwen3-32b</option>
|
||
<option value="qwen3-30b-a3b">qwen3-30b-a3b(MoE 高效)</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-reasoner(R1)</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 API(moonshot-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 | 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-a3b(MoE 高效,约 19 GB)</option>
|
||
<option value="qwen3:235b-a22b">qwen3:235b-a22b(MoE 旗舰,约 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:16b(Lite,约 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 <模型名></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 推荐)
|
||
<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"><think></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. 已安装 Ollama(https://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>
|