feat: 实现剧本创作与拆解前端工作台

This commit is contained in:
GouJ
2026-08-27 19:33:51 +08:00
parent edc003443f
commit 2ee6f049f6
56 changed files with 7597 additions and 2 deletions
@@ -0,0 +1,105 @@
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { DialogTrigger } from 'reka-ui'
import { ArrowRight, LoaderCircle, Plus } from '@lucide/vue'
import { AppDialog } from '../../../components/ui'
import { projectsApi } from '../api'
import { errorMessage } from '../../../lib/http'
/** 新建剧本表单,202 成功后交由父级跳转,不在前端模拟生成。 */
const emit = defineEmits<{ created: [projectId: string] }>()
const open = ref(false)
const busy = ref(false)
const error = ref('')
const form = reactive({ topic: '', style: '爽文反转', episodeCount: 3 })
/** 校验正整数集数和主题,禁止双击产生重复项目。 */
async function submit() {
if (busy.value) return
error.value = ''
if (!form.topic.trim() || !Number.isSafeInteger(form.episodeCount) || form.episodeCount <= 0) {
error.value = '请填写故事主题,并输入大于 0 的整数集数。'
return
}
busy.value = true
try {
const result = await projectsApi.create({
...form,
topic: form.topic.trim(),
style: form.style.trim() || '爽文反转'
})
open.value = false
form.topic = ''
emit('created', result.projectId)
} catch (cause) {
error.value = errorMessage(cause)
} finally {
busy.value = false
}
}
</script>
<template>
<AppDialog
v-model:open="open"
title="新建剧本"
description="从一个故事想法开始。提交后,工作流将依次生成角色、世界观和剧集,并进行审核。"
:busy="busy"
>
<template #trigger
><DialogTrigger class="button button-primary"><Plus :size="16" />新建剧本</DialogTrigger></template
>
<form class="mt-7 space-y-5" @submit.prevent="submit">
<div>
<label class="field-label" for="topic">故事主题 <span class="text-accent">*</span></label
><textarea
id="topic"
v-model="form.topic"
class="input min-h-32 resize-y"
placeholder="描述主角、故事背景,以及你希望展开的核心冲突……"
required
:disabled="busy"
></textarea>
</div>
<div class="grid grid-cols-[1fr_110px] gap-4">
<div>
<label class="field-label" for="style">剧本风格</label
><input
id="style"
v-model="form.style"
class="input"
placeholder="如:都市悬疑、爽文反转"
:disabled="busy"
/>
</div>
<div>
<label class="field-label" for="episode-count">计划集数</label
><input
id="episode-count"
v-model.number="form.episodeCount"
class="input"
type="number"
min="1"
step="1"
required
:disabled="busy"
/>
</div>
</div>
<p class="text-xs leading-5 text-muted">
建议先用 3 集验证生成效果生成会实际调用后端模型产生相应费用
</p>
<p v-if="error" class="alert alert-error" role="alert">{{ error }}</p>
<div class="dialog-footer">
<button type="button" class="button button-secondary" :disabled="busy" @click="open = false">
取消</button
><button type="submit" class="button button-primary" :disabled="busy">
<LoaderCircle v-if="busy" :size="16" class="animate-spin" />开始生成<ArrowRight
v-if="!busy"
:size="16"
/>
</button>
</div>
</form>
</AppDialog>
</template>