AI 小说工坊:主编 + 作家多模型协作写作应用
- 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>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
import { ROLE_LABEL } from '#shared/types'
|
||||
|
||||
const props = defineProps<{ agent: AgentItem, builtin: string }>()
|
||||
const store = useProjectStore()
|
||||
const api = useApi()
|
||||
|
||||
const form = reactive({
|
||||
name: props.agent.name,
|
||||
modelId: props.agent.modelId,
|
||||
systemPrompt: props.agent.systemPrompt,
|
||||
styleNotes: props.agent.styleNotes,
|
||||
enabled: props.agent.enabled
|
||||
})
|
||||
watch(() => props.agent, (a) => {
|
||||
Object.assign(form, { name: a.name, modelId: a.modelId, systemPrompt: a.systemPrompt, styleNotes: a.styleNotes, enabled: a.enabled })
|
||||
})
|
||||
|
||||
const dirty = computed(() => form.name !== props.agent.name || form.modelId !== props.agent.modelId || form.systemPrompt !== props.agent.systemPrompt || form.styleNotes !== props.agent.styleNotes || form.enabled !== props.agent.enabled)
|
||||
const saving = ref(false)
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
await api.put(`/api/agents/${props.agent.id}`, { ...form })
|
||||
await store.refreshAgents()
|
||||
api.toast.add({ title: `${form.name} 已保存`, color: 'success' })
|
||||
}
|
||||
finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
async function remove() {
|
||||
if (!window.confirm(`删除作家「${props.agent.name}」?`)) return
|
||||
await api.del(`/api/agents/${props.agent.id}`)
|
||||
await store.refreshAgents()
|
||||
}
|
||||
|
||||
const modelItems = computed(() => store.models.value.map(m => ({ label: `${m.providerName} / ${m.label}`, value: m.id })))
|
||||
const showBuiltin = ref(false)
|
||||
|
||||
const ICON = { editor: 'i-lucide-crown', writer: 'i-lucide-pen-tool', summarizer: 'i-lucide-notebook-pen' } as const
|
||||
const DESC = {
|
||||
editor: '负责规划章节大纲、为每章撰写简报、选派作家、审稿。建议使用推理能力强的模型(如 Claude)。',
|
||||
writer: '根据主编的简报写出章节正文。可以创建多位风格不同的作家,使用不同模型。',
|
||||
summarizer: '章节确认后提炼摘要并更新人物状态、伏笔等故事记忆。可以用便宜快速的模型;未配置时使用主编的模型。'
|
||||
} as const
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard :class="form.enabled ? '' : 'opacity-60'">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-3">
|
||||
<UIcon :name="ICON[agent.role]" class="size-5 text-primary" />
|
||||
<UInput v-model="form.name" variant="ghost" class="font-semibold max-w-56" />
|
||||
<UBadge variant="subtle" size="sm">
|
||||
{{ ROLE_LABEL[agent.role] }}
|
||||
</UBadge>
|
||||
<UBadge v-if="agent.createdBy === 'editor'" color="info" variant="subtle" size="sm">
|
||||
主编创建
|
||||
</UBadge>
|
||||
<div class="ms-auto flex items-center gap-2">
|
||||
<USwitch v-if="agent.role === 'writer'" v-model="form.enabled" size="sm" label="启用" />
|
||||
<UButton v-if="agent.role === 'writer'" icon="i-lucide-trash-2" size="xs" color="error" variant="ghost" @click="remove" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="space-y-4">
|
||||
<p class="text-xs text-muted">
|
||||
{{ DESC[agent.role] }}
|
||||
</p>
|
||||
<div class="grid sm:grid-cols-2 gap-4">
|
||||
<UFormField label="模型" required>
|
||||
<USelect
|
||||
:model-value="form.modelId ?? undefined"
|
||||
:items="modelItems"
|
||||
placeholder="选择模型"
|
||||
@update:model-value="(v: string) => (form.modelId = v)"
|
||||
class="w-full"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField v-if="agent.role === 'writer'" label="擅长风格" hint="主编选派作家时参考">
|
||||
<UInput v-model="form.styleNotes" placeholder="例如:细腻的情感描写、快节奏动作戏" class="w-full" />
|
||||
</UFormField>
|
||||
</div>
|
||||
<UFormField label="自定义系统提示词" hint="最高优先级,与内置提示词冲突时以此为准">
|
||||
<UTextarea
|
||||
v-model="form.systemPrompt"
|
||||
autoresize
|
||||
:rows="4"
|
||||
:maxrows="20"
|
||||
class="w-full"
|
||||
:placeholder="agent.role === 'writer' ? '例如:你的文字冷峻克制,擅长用白描和短句营造张力;对话不加多余的动作提示……' : agent.role === 'editor' ? '例如:每章必须有一个明确的小高潮;感情线推进要克制……' : '例如:摘要要保留关键对白原句……'"
|
||||
/>
|
||||
</UFormField>
|
||||
<div>
|
||||
<UButton :label="showBuiltin ? '收起内置提示词' : '查看内置提示词'" :icon="showBuiltin ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'" size="xs" color="neutral" variant="link" class="px-0" @click="showBuiltin = !showBuiltin" />
|
||||
<pre v-if="showBuiltin" class="mt-2 text-xs whitespace-pre-wrap rounded-md bg-elevated p-3 text-muted">{{ builtin }}</pre>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<UButton label="保存" icon="i-lucide-save" size="sm" :loading="saving" :disabled="!dirty" @click="save" />
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
Reference in New Issue
Block a user