3c9b65d982
- Nuxt 4 + Nuxt UI 4 + AI SDK 7 + Drizzle/SQLite - 主编规划大纲与章节简报、选派作家;作家流式写入章节文件并实时推送前端 - 逐章确认模式(默认)与自动模式;摘要与故事记忆 - 酒馆角色卡(PNG/JSON,V1-V3)与世界书导入 - 自定义最高优先级系统提示词、审稿/修改/多版本、任务日志与提示词检视 - 模拟 LLM 服务与端到端测试脚本 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
156 lines
7.1 KiB
Vue
156 lines
7.1 KiB
Vue
<script setup lang="ts">
|
||
import type { ProjectRequirements, ProjectSettings } from '#shared/types'
|
||
|
||
const store = useProjectStore()
|
||
const api = useApi()
|
||
const p = store.project
|
||
|
||
const draft = reactive({
|
||
title: p.value!.title,
|
||
description: p.value!.description,
|
||
worldSetting: p.value!.worldSetting,
|
||
storyOutline: p.value!.storyOutline,
|
||
userName: p.value!.userName,
|
||
requirements: { ...p.value!.requirements } as ProjectRequirements,
|
||
settings: { ...p.value!.settings } as ProjectSettings
|
||
})
|
||
|
||
const snapshot = ref(JSON.stringify(draft))
|
||
const dirty = computed(() => JSON.stringify(draft) !== snapshot.value)
|
||
const saving = ref(false)
|
||
|
||
async function save() {
|
||
saving.value = true
|
||
try {
|
||
await store.saveProject(JSON.parse(JSON.stringify(draft)))
|
||
snapshot.value = JSON.stringify(draft)
|
||
api.toast.add({ title: '设定已保存', color: 'success', icon: 'i-lucide-save' })
|
||
}
|
||
finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
// Ctrl+S 保存
|
||
defineShortcuts({ meta_s: { usingInput: true, handler: () => { if (dirty.value) save() } } })
|
||
|
||
onBeforeRouteLeave(() => {
|
||
if (dirty.value && !window.confirm('有未保存的修改,确定离开吗?')) return false
|
||
})
|
||
|
||
const POV_OPTIONS = ['第一人称', '第三人称有限视角', '第三人称全知视角', '第二人称', '多视角轮换']
|
||
</script>
|
||
|
||
<template>
|
||
<UDashboardPanel id="project-setting">
|
||
<template #header>
|
||
<UDashboardNavbar title="故事设定">
|
||
<template #right>
|
||
<span v-if="dirty" class="text-xs text-warning">有未保存的修改</span>
|
||
<UButton icon="i-lucide-save" label="保存" :loading="saving" :disabled="!dirty" @click="save" />
|
||
</template>
|
||
</UDashboardNavbar>
|
||
</template>
|
||
|
||
<template #body>
|
||
<div class="max-w-4xl mx-auto w-full space-y-8 pb-16">
|
||
<section class="space-y-4">
|
||
<h2 class="text-base font-semibold flex items-center gap-2">
|
||
<UIcon name="i-lucide-book" class="text-primary" />基本信息
|
||
</h2>
|
||
<div class="grid sm:grid-cols-2 gap-4">
|
||
<UFormField label="作品名" required>
|
||
<UInput v-model="draft.title" class="w-full" />
|
||
</UFormField>
|
||
<UFormField label="主角名 / {{user}}" hint="导入角色卡时用于替换 {{user}}">
|
||
<UInput v-model="draft.userName" placeholder="例如:陈默" class="w-full" />
|
||
</UFormField>
|
||
</div>
|
||
<UFormField label="简介">
|
||
<UTextarea v-model="draft.description" autoresize :rows="2" class="w-full" />
|
||
</UFormField>
|
||
</section>
|
||
|
||
<section class="space-y-4">
|
||
<h2 class="text-base font-semibold flex items-center gap-2">
|
||
<UIcon name="i-lucide-globe" class="text-primary" />故事背景 / 世界观
|
||
</h2>
|
||
<UTextarea
|
||
v-model="draft.worldSetting"
|
||
autoresize
|
||
:rows="8"
|
||
:maxrows="30"
|
||
class="w-full"
|
||
placeholder="时代、地点、社会环境、力量体系、重要组织、基调……越具体,AI 写得越贴合。导入角色卡时,卡片的场景描述会追加到这里。"
|
||
/>
|
||
</section>
|
||
|
||
<section class="space-y-4">
|
||
<h2 class="text-base font-semibold flex items-center gap-2">
|
||
<UIcon name="i-lucide-route" class="text-primary" />故事大纲
|
||
</h2>
|
||
<UTextarea
|
||
v-model="draft.storyOutline"
|
||
autoresize
|
||
:rows="10"
|
||
:maxrows="40"
|
||
class="w-full"
|
||
placeholder="整个故事的主线:起因、发展、高潮、结局,以及主要的转折点和人物弧光。主编会据此拆分章节。"
|
||
/>
|
||
</section>
|
||
|
||
<section class="space-y-4">
|
||
<h2 class="text-base font-semibold flex items-center gap-2">
|
||
<UIcon name="i-lucide-pen-tool" class="text-primary" />写作要求 <span class="text-xs font-normal text-muted">(可选)</span>
|
||
</h2>
|
||
<div class="grid sm:grid-cols-3 gap-4">
|
||
<UFormField label="语言">
|
||
<UInput v-model="draft.requirements.language" class="w-full" />
|
||
</UFormField>
|
||
<UFormField label="叙事视角">
|
||
<UInputMenu v-model="draft.requirements.pov" :items="POV_OPTIONS" create-item class="w-full" @create="(v: string) => (draft.requirements.pov = v)" />
|
||
</UFormField>
|
||
<UFormField label="每章字数">
|
||
<UInputNumber v-model="draft.requirements.wordsPerChapter" :min="500" :max="50000" :step="500" class="w-full" />
|
||
</UFormField>
|
||
</div>
|
||
<UFormField label="文风">
|
||
<UInput v-model="draft.requirements.style" placeholder="例如:细腻克制的现实主义,带一点潮湿的南方气息" class="w-full" />
|
||
</UFormField>
|
||
<UFormField label="叙述方式">
|
||
<UTextarea v-model="draft.requirements.narration" autoresize :rows="2" placeholder="例如:过去时;对白占比约三成;多用环境烘托情绪;少用心理独白" class="w-full" />
|
||
</UFormField>
|
||
<UFormField label="其他要求">
|
||
<UTextarea v-model="draft.requirements.extra" autoresize :rows="2" placeholder="例如:避免现代网络用语;不要出现“仿佛”“不禁”等滥用词" class="w-full" />
|
||
</UFormField>
|
||
</section>
|
||
|
||
<section class="space-y-4">
|
||
<h2 class="text-base font-semibold flex items-center gap-2">
|
||
<UIcon name="i-lucide-settings-2" class="text-primary" />写作流程
|
||
</h2>
|
||
<div class="grid sm:grid-cols-2 gap-4">
|
||
<USwitch v-model="draft.settings.autoMode" label="自动模式" description="章节写完后自动确认并继续下一章;关闭时每章写完都会停下来等你确认" />
|
||
<USwitch v-model="draft.settings.pauseAfterBrief" label="简报后暂停" description="主编写完简报后先暂停,你确认或修改简报后再开始写作(仅逐章模式)" />
|
||
<USwitch v-model="draft.settings.allowEditorCreateWriters" label="允许主编创建作家" description="主编可以用已配置的模型创建新的作家来写特定风格的章节" />
|
||
</div>
|
||
<div class="grid sm:grid-cols-4 gap-4">
|
||
<UFormField label="上一章衔接字数" hint="注入作家">
|
||
<UInputNumber v-model="draft.settings.prevTailChars" :min="0" :max="20000" :step="500" class="w-full" />
|
||
</UFormField>
|
||
<UFormField label="详细前情章数">
|
||
<UInputNumber v-model="draft.settings.recentSummaryCount" :min="0" :max="50" class="w-full" />
|
||
</UFormField>
|
||
<UFormField label="截断自动续写次数">
|
||
<UInputNumber v-model="draft.settings.maxContinuations" :min="0" :max="10" class="w-full" />
|
||
</UFormField>
|
||
<UFormField label="自动模式章节上限">
|
||
<UInputNumber v-model="draft.settings.autoChapterLimit" :min="1" :max="200" class="w-full" />
|
||
</UFormField>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</template>
|
||
</UDashboardPanel>
|
||
</template>
|