Files
ai-write/index.html.backup
empty 9fb3600a6a feat: 初始化AI写作工坊项目
- 实现基于Vue 3 + Vite的模块化架构
- 集成DeepSeek API进行内容生成
- 支持写作范式分析功能
- 添加环境变量配置支持
- 完整的项目结构和文档
2026-01-08 10:04:59 +08:00

562 lines
29 KiB
Plaintext
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://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
/* 自定义滚动条样式,适配深色主题 */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: #1e293b; }
::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #64748b; }
/* Markdown 内容样式覆盖 */
.prose h1, .prose h2, .prose h3 { color: #e2e8f0; margin-top: 1.5em; margin-bottom: 0.8em; }
.prose p { margin-bottom: 1.2em; line-height: 1.75; color: #cbd5e1; }
.prose ul { list-style-type: disc; padding-left: 1.5em; margin-bottom: 1.2em; color: #cbd5e1; }
.prose strong { color: #60a5fa; font-weight: 600; }
.prose blockquote { border-left-color: #3b82f6; background: #1e293b; padding: 0.5rem 1rem; font-style: italic; }
.prose code { background: #334155; padding: 0.2em 0.4em; border-radius: 4px; font-size: 0.9em; color: #f8fafc; }
[v-cloak] { display: none; }
</style>
</head>
<body class="bg-slate-900 text-slate-200 h-screen overflow-hidden font-sans">
<div id="app" v-cloak class="flex h-full">
<aside class="w-[400px] flex flex-col border-r border-slate-700 bg-slate-800 shrink-0">
<div class="p-4 border-b border-slate-700 flex items-center justify-between">
<div class="flex items-center gap-4">
<h1 class="font-bold text-lg text-white flex items-center gap-2">
<span class="text-2xl">✍️</span>
<span v-if="currentPage === 'writer'">AI 写作工坊</span>
<span v-else>写作范式分析</span>
</h1>
<button
@click="currentPage = currentPage === 'writer' ? 'analysis' : 'writer'"
class="text-xs px-2 py-1 rounded bg-slate-700 text-slate-300 hover:bg-slate-600 transition"
>
{{ currentPage === 'writer' ? '写作范式' : '返回写作' }}
</button>
</div>
<span class="text-xs px-2 py-1 rounded bg-blue-900 text-blue-300 border border-blue-700">Pro版</span>
</div>
<div class="flex-1 overflow-y-auto p-4 space-y-6" v-if="currentPage === 'writer'">
<section>
<label class="block text-sm font-medium text-slate-400 mb-2 flex justify-between">
1. 写作任务 (User Input)
<span class="text-xs text-slate-500">{{ inputTask.length }} 字</span>
</label>
<textarea
v-model="inputTask"
class="w-full h-32 bg-slate-900 border border-slate-700 rounded-lg p-3 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition placeholder-slate-600 resize-none"
placeholder="请输入具体的写作要求、主题、核心观点..."
></textarea>
</section>
<section>
<div class="flex justify-between items-center mb-2">
<label class="text-sm font-medium text-slate-400">2. 参考案例 (Style Ref)</label>
<button @click="showRefInput = !showRefInput" class="text-xs text-blue-400 hover:text-blue-300">
{{ showRefInput ? '取消' : '+ 添加案例' }}
</button>
</div>
<div v-if="showRefInput" class="mb-3 p-3 bg-slate-900 rounded-lg border border-blue-500/30">
<input v-model="newRefTitle" placeholder="案例标题 (如: 乔布斯演讲)" class="w-full mb-2 bg-slate-800 border border-slate-700 rounded px-2 py-1 text-xs outline-none">
<textarea v-model="newRefContent" placeholder="粘贴优秀的参考文本..." class="w-full h-24 bg-slate-800 border border-slate-700 rounded px-2 py-1 text-xs outline-none resize-none mb-2"></textarea>
<button @click="addReference" class="w-full bg-blue-600 hover:bg-blue-500 text-xs py-1.5 rounded text-white">确认添加</button>
</div>
<div class="space-y-2">
<div v-for="(ref, index) in references" :key="index" class="group flex items-center justify-between bg-slate-700/50 p-2 rounded border border-slate-700 hover:border-slate-600">
<div class="flex items-center gap-2 overflow-hidden">
<span class="text-lg">📄</span>
<div class="flex flex-col min-w-0">
<span class="text-xs font-medium text-slate-200 truncate">{{ ref.title }}</span>
<span class="text-[10px] text-slate-500 truncate">{{ ref.content.substring(0, 20) }}...</span>
</div>
</div>
<button @click="removeReference(index)" class="text-slate-500 hover:text-red-400 opacity-0 group-hover:opacity-100 transition px-2">×</button>
</div>
<div v-if="references.length === 0" class="text-xs text-slate-600 text-center py-4 border border-dashed border-slate-700 rounded">
暂无参考案例AI 将自由发挥
</div>
</div>
</section>
<section>
<label class="block text-sm font-medium text-slate-400 mb-2">3. 输出规范 (Constraints)</label>
<div class="flex flex-wrap gap-2 mb-3">
<button
v-for="tag in presetTags"
:key="tag"
@click="toggleTag(tag)"
:class="['px-2 py-1 rounded text-xs border transition',
selectedTags.includes(tag)
? 'bg-blue-600/20 border-blue-500 text-blue-300'
: 'bg-slate-900 border-slate-700 text-slate-500 hover:border-slate-500']"
>
{{ tag }}
</button>
</div>
<input
v-model="customConstraint"
type="text"
class="w-full bg-slate-900 border border-slate-700 rounded px-3 py-2 text-xs focus:border-blue-500 outline-none"
placeholder="补充其他要求 (例如: 严禁使用'综上所述')"
>
</section>
</div>
<div class="p-4 bg-slate-800 border-t border-slate-700 space-y-3 z-10">
<div class="flex items-center justify-between">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="showPromptDebug" class="hidden">
<div class="w-8 h-4 bg-slate-900 rounded-full border border-slate-600 relative transition-colors" :class="{'bg-blue-900 border-blue-500': showPromptDebug}">
<div class="w-2 h-2 bg-slate-400 rounded-full absolute top-1 left-1 transition-transform" :class="{'translate-x-4 bg-blue-400': showPromptDebug}"></div>
</div>
<span class="text-xs text-slate-500 select-none">预览构建的 Prompt</span>
</label>
<span class="text-xs text-slate-600">deepseek</span>
</div>
<!-- API 配置 -->
<div class="space-y-2">
<input
v-model="apiUrl"
type="text"
class="w-full bg-slate-900 border border-slate-700 rounded px-3 py-2 text-xs focus:border-blue-500 outline-none"
placeholder="API 地址: https://your-kong-gateway.com/v1/chat/completions"
>
<input
v-model="apiKey"
type="password"
class="w-full bg-slate-900 border border-slate-700 rounded px-3 py-2 text-xs focus:border-blue-500 outline-none"
placeholder="API Key: Bearer YOUR_KEY"
>
</div>
<button
@click="generateContent"
:disabled="isGenerating || !inputTask"
class="w-full py-3 rounded-lg font-bold text-white shadow-lg shadow-blue-900/20 flex items-center justify-center gap-2 transition-all transform active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed"
:class="isGenerating ? 'bg-slate-700' : 'bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500'"
>
<span v-if="isGenerating" class="animate-spin text-lg">↻</span>
{{ isGenerating ? '正在思考与撰写...' : '开始生成文稿' }}
</button>
</div>
</aside>
<!-- 写作范式分析页面 -->
<aside v-if="currentPage === 'analysis'" class="w-[400px] flex flex-col border-r border-slate-700 bg-slate-800 shrink-0">
<div class="flex-1 overflow-y-auto p-4 space-y-6">
<section>
<h3 class="text-sm font-medium text-slate-400 mb-4">📚 写作范式库</h3>
<div class="space-y-3">
<div class="bg-slate-700/50 rounded-lg p-4 border border-slate-600 hover:border-blue-500 transition cursor-pointer">
<h4 class="font-medium text-white mb-2">📝 技术博客范式</h4>
<p class="text-xs text-slate-400 mb-2">适用于技术分享、教程类文章</p>
<div class="flex flex-wrap gap-1">
<span class="text-xs px-2 py-1 bg-blue-900/30 text-blue-300 rounded">问题引入</span>
<span class="text-xs px-2 py-1 bg-blue-900/30 text-blue-300 rounded">解决方案</span>
<span class="text-xs px-2 py-1 bg-blue-900/30 text-blue-300 rounded">代码示例</span>
<span class="text-xs px-2 py-1 bg-blue-900/30 text-blue-300 rounded">总结</span>
</div>
</div>
<div class="bg-slate-700/50 rounded-lg p-4 border border-slate-600 hover:border-blue-500 transition cursor-pointer">
<h4 class="font-medium text-white mb-2">📊 商业分析范式</h4>
<p class="text-xs text-slate-400 mb-2">适用于行业分析、市场报告</p>
<div class="flex flex-wrap gap-1">
<span class="text-xs px-2 py-1 bg-green-900/30 text-green-300 rounded">背景介绍</span>
<span class="text-xs px-2 py-1 bg-green-900/30 text-green-300 rounded">数据支撑</span>
<span class="text-xs px-2 py-1 bg-green-900/30 text-green-300 rounded">趋势分析</span>
<span class="text-xs px-2 py-1 bg-green-900/30 text-green-300 rounded">建议</span>
</div>
</div>
<div class="bg-slate-700/50 rounded-lg p-4 border border-slate-600 hover:border-blue-500 transition cursor-pointer">
<h4 class="font-medium text-white mb-2">🎯 产品文案范式</h4>
<p class="text-xs text-slate-400 mb-2">适用于产品介绍、营销文案</p>
<div class="flex flex-wrap gap-1">
<span class="text-xs px-2 py-1 bg-purple-900/30 text-purple-300 rounded">痛点切入</span>
<span class="text-xs px-2 py-1 bg-purple-900/30 text-purple-300 rounded">价值主张</span>
<span class="text-xs px-2 py-1 bg-purple-900/30 text-purple-300 rounded">功能亮点</span>
<span class="text-xs px-2 py-1 bg-purple-900/30 text-purple-300 rounded">行动号召</span>
</div>
</div>
<div class="bg-slate-700/50 rounded-lg p-4 border border-slate-600 hover:border-blue-500 transition cursor-pointer">
<h4 class="font-medium text-white mb-2">📖 学术论文范式</h4>
<p class="text-xs text-slate-400 mb-2">适用于学术写作、研究报告</p>
<div class="flex flex-wrap gap-1">
<span class="text-xs px-2 py-1 bg-orange-900/30 text-orange-300 rounded">摘要</span>
<span class="text-xs px-2 py-1 bg-orange-900/30 text-orange-300 rounded">引言</span>
<span class="text-xs px-2 py-1 bg-orange-900/30 text-orange-300 rounded">文献综述</span>
<span class="text-xs px-2 py-1 bg-orange-900/30 text-orange-300 rounded">方法论</span>
<span class="text-xs px-2 py-1 bg-orange-900/30 text-orange-300 rounded">结果</span>
</div>
</div>
</div>
</section>
<section>
<h3 class="text-sm font-medium text-slate-400 mb-4">🔍 范式分析工具</h3>
<textarea
v-model="analysisText"
class="w-full h-32 bg-slate-900 border border-slate-700 rounded-lg p-3 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition placeholder-slate-600 resize-none"
placeholder="粘贴文章内容,分析其写作范式..."
></textarea>
<button
@click="analyzeArticleParadigm"
:disabled="isAnalyzing || !analysisText"
class="mt-3 w-full bg-blue-600 hover:bg-blue-500 text-white py-2 rounded-lg text-sm font-medium transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{{ isAnalyzing ? '正在分析...' : '分析文章范式' }}
</button>
</section>
</div>
</aside>
<main class="flex-1 flex flex-col bg-slate-950 relative">
<div v-if="showPromptDebug" class="absolute inset-0 bg-slate-950/95 z-20 p-8 overflow-auto backdrop-blur-sm">
<div class="max-w-3xl mx-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-bold text-yellow-500">🔧 构建的 Prompt 结构预览</h3>
<button @click="showPromptDebug = false" class="text-sm text-slate-400 hover:text-white">关闭预览</button>
</div>
<div class="bg-black/50 p-6 rounded-lg border border-slate-700 font-mono text-sm text-green-400 whitespace-pre-wrap leading-relaxed shadow-inner">
{{ constructedPrompt }}
</div>
<p class="mt-4 text-xs text-slate-500 text-center">此内容将直接发送给 API 接口</p>
</div>
</div>
<div class="h-14 border-b border-slate-800 flex items-center justify-between px-6 bg-slate-950">
<span class="text-sm font-medium text-slate-400">
{{ currentPage === 'writer' ? '输出预览' : '范式分析结果' }}
</span>
<div class="flex items-center gap-4" v-if="currentPage === 'writer'">
<span class="text-xs text-slate-600">
字数: {{ generatedContent.length }} {{ isGenerating ? '(生成中...)' : '' }}
</span>
<div class="flex gap-3">
<button @click="copyContent" class="text-xs text-slate-400 hover:text-white flex items-center gap-1 transition">
📋 复制 Markdown
</button>
<button @click="generatedContent = ''" class="text-xs text-slate-400 hover:text-red-400 flex items-center gap-1 transition">
🗑️ 清空
</button>
</div>
</div>
</div>
<div class="flex-1 overflow-y-auto p-8 md:p-12">
<div class="max-w-3xl mx-auto min-h-[500px]">
<!-- 写作页面内容 -->
<div v-if="currentPage === 'writer'">
<div v-if="!generatedContent && !isGenerating" class="h-full flex flex-col items-center justify-center text-slate-700 mt-20">
<span class="text-6xl mb-4 opacity-20">⌨️</span>
<p>在左侧配置参数,点击生成开始写作</p>
</div>
<div v-else class="prose prose-invert max-w-none">
<div v-html="renderedMarkdown"></div>
<span v-if="isGenerating" class="inline-block w-2 h-5 bg-blue-500 ml-1 animate-pulse align-middle"></span>
</div>
</div>
<!-- 范式分析页面内容 -->
<div v-else class="space-y-8">
<div v-if="!analysisResult" class="h-full flex flex-col items-center justify-center text-slate-700 mt-20">
<span class="text-6xl mb-4 opacity-20">📊</span>
<p>选择左侧的写作范式或使用分析工具</p>
</div>
<div v-else class="space-y-6">
<div v-if="analysisResult.error" class="bg-red-900/20 rounded-lg p-6 border border-red-700">
<h3 class="text-lg font-bold text-red-400 mb-4">分析失败</h3>
<p class="text-red-300">{{ analysisResult.message }}</p>
</div>
<div v-else class="bg-slate-900 rounded-lg p-6 border border-slate-700">
<h3 class="text-lg font-bold text-white mb-4">分析结果</h3>
<div class="prose prose-invert max-w-none">
<div v-html="marked.parse(analysisResult.analysis)"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<script>
const { createApp, computed, ref } = Vue;
createApp({
setup() {
// --- 状态数据 ---
const inputTask = ref('请帮我写一篇关于“AI 辅助编程如何改变软件开发流程”的博客文章,面向中级程序员。');
// 参考案例相关
const references = ref([
{ title: '示例:技术博客风格.txt', content: '本文深入探讨了...此处省略2000字这是为了让AI模仿这种干练的技术风格...' }
]);
const showRefInput = ref(false);
const newRefTitle = ref('');
const newRefContent = ref('');
// 规范相关
const presetTags = ['Markdown格式', '总分总结构', '数据支撑', '语气幽默', '严禁被动语态', '引用权威来源'];
const selectedTags = ref(['Markdown格式', '总分总结构']);
const customConstraint = ref('');
// 生成相关
const isGenerating = ref(false);
const generatedContent = ref('');
const showPromptDebug = ref(false);
// 页面切换
const currentPage = ref('writer'); // 'writer' 或 'analysis'
// 范式分析相关
const analysisText = ref('');
const analysisResult = ref(null);
const isAnalyzing = ref(false);
// API 配置
const apiUrl = ref('https://api.deepseek.com/chat/completions');
const apiKey = ref('YOUR_KEY');
// --- 核心逻辑Meta-Prompt 组装器 ---
const constructedPrompt = computed(() => {
let prompt = `# Role\n你是一个资深的专业写作助手请严格按照以下要求进行创作。\n\n`;
// 1. 注入规范
prompt += `# System Constraints (必须遵守)\n`;
selectedTags.value.forEach(tag => prompt += `- ${tag}\n`);
if (customConstraint.value) prompt += `- ${customConstraint.value}\n`;
prompt += `\n`;
// 2. 注入参考案例 (Few-Shot)
if (references.value.length > 0) {
prompt += `# Reference Cases (请模仿以下风格)\n`;
references.value.forEach((ref, idx) => {
prompt += `<case_${idx + 1} title="${ref.title}">\n${ref.content}\n</case_${idx + 1}>\n\n`;
});
}
// 3. 注入用户任务
prompt += `# Current Task (User Input)\n${inputTask.value}`;
return prompt;
});
const renderedMarkdown = computed(() => {
return marked.parse(generatedContent.value);
});
// --- 方法 ---
const addReference = () => {
if (!newRefTitle.value || !newRefContent.value) return;
references.value.push({
title: newRefTitle.value,
content: newRefContent.value
});
newRefTitle.value = '';
newRefContent.value = '';
showRefInput.value = false;
};
const removeReference = (index) => {
references.value.splice(index, 1);
};
const toggleTag = (tag) => {
if (selectedTags.value.includes(tag)) {
selectedTags.value = selectedTags.value.filter(t => t !== tag);
} else {
selectedTags.value.push(tag);
}
};
const generateContent = async () => {
if (!apiUrl.value || !apiKey.value || apiKey.value === 'YOUR_KEY') {
alert('请先配置 API 地址和 API Key');
return;
}
isGenerating.value = true;
generatedContent.value = '';
try {
const response = await fetch(apiUrl.value, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey.value}`
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: '你是一个资深的专业写作助手,请严格按照用户的要求进行创作。'
},
{
role: 'user',
content: constructedPrompt.value
}
],
stream: true,
temperature: 0.7
})
});
if (!response.ok) {
const errorText = await response.text();
console.error('API Error Response:', errorText);
throw new Error(`API 请求失败: ${response.status} ${response.statusText}\n\n详细信息: ${errorText}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(line => line.trim());
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') continue;
try {
const parsed = JSON.parse(data);
const content = parsed.choices?.[0]?.delta?.content || '';
generatedContent.value += content;
} catch (e) {
// 忽略解析错误
}
}
}
}
} catch (error) {
generatedContent.value = `## 错误\n\n${error.message}\n\n请检查 API 配置是否正确。`;
} finally {
isGenerating.value = false;
}
};
const copyContent = () => {
navigator.clipboard.writeText(generatedContent.value);
alert('已复制到剪贴板');
};
const analyzeArticleParadigm = async () => {
if (!analysisText.value.trim()) {
alert('请输入要分析的文章内容');
return;
}
isAnalyzing.value = true;
analysisResult.value = null;
try {
const response = await fetch(apiUrl.value, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey.value}`
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: '你是一个专业的写作分析师擅长分析文章的写作范式、结构和特点。请从以下几个方面分析文章1. 主要写作范式类型 2. 文章结构特点 3. 语言风格特征 4. 目标读者群体 5. 写作技巧总结。请用简洁明了的语言回答。'
},
{
role: 'user',
content: `请分析以下文章的写作范式:\n\n${analysisText.value}`
}
],
stream: false,
temperature: 0.3
})
});
if (!response.ok) {
throw new Error(`分析请求失败: ${response.status} ${response.statusText}`);
}
const data = await response.json();
analysisResult.value = {
paradigm: '技术博客范式',
features: [
'采用问题-解决方案的叙述结构',
'包含大量代码示例',
'语言简洁,逻辑清晰'
],
analysis: data.choices[0].message.content
};
} catch (error) {
analysisResult.value = {
error: true,
message: error.message
};
} finally {
isAnalyzing.value = false;
}
};
return {
inputTask,
references,
showRefInput,
newRefTitle,
newRefContent,
presetTags,
selectedTags,
customConstraint,
isGenerating,
generatedContent,
showPromptDebug,
currentPage,
analysisText,
analysisResult,
isAnalyzing,
apiUrl,
apiKey,
constructedPrompt,
renderedMarkdown,
addReference,
removeReference,
toggleTag,
generateContent,
copyContent,
analyzeArticleParadigm
};
}
}).mount('#app');
</script>
</body>
</html>