130 lines
5.2 KiB
Vue
130 lines
5.2 KiB
Vue
<script setup lang="ts">
|
||
import AppForm from '../../../components/ui/AppForm.vue'
|
||
import { NFormItem } from 'naive-ui'
|
||
import { requiredTextRule, integerRule } from '../../../lib/form-rules'
|
||
import { NAlert, NButton, NInput, NInputNumber } from 'naive-ui'
|
||
import { reactive, ref } from 'vue'
|
||
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('')
|
||
/** 与后端 POST /projects 入参一致的创建表单。 */
|
||
const form = reactive({ title: '', topic: '', style: '', episodeCount: 1 })
|
||
|
||
/** 校验项目标题、主题与正整数集数,禁止双击产生重复项目。 */
|
||
async function submit() {
|
||
if (busy.value) return
|
||
error.value = ''
|
||
busy.value = true
|
||
try {
|
||
const result = await projectsApi.create({
|
||
...form,
|
||
title: form.title.trim(),
|
||
topic: form.topic.trim(),
|
||
style: form.style.trim()
|
||
})
|
||
open.value = false
|
||
form.title = ''
|
||
form.topic = ''
|
||
emit('created', result.projectId)
|
||
} catch (cause) {
|
||
error.value = errorMessage(cause)
|
||
} finally {
|
||
busy.value = false
|
||
}
|
||
}
|
||
/** 创建项目表单校验规则。 */
|
||
const rules = {
|
||
title: requiredTextRule('项目标题'),
|
||
topic: requiredTextRule('故事主题'),
|
||
episodeCount: integerRule('计划集数')
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<AppDialog
|
||
v-model:open="open"
|
||
title="新建剧本"
|
||
description="从一个故事想法开始。提交后,工作流将依次生成角色、世界观和剧集,并进行审核。"
|
||
:busy="busy"
|
||
>
|
||
<template #trigger
|
||
><NButton @click="open = true" type="primary"><Plus :size="16" />新建剧本</NButton></template
|
||
>
|
||
<AppForm
|
||
:model="form"
|
||
:rules="rules"
|
||
:disabled="busy"
|
||
:reset-key="open"
|
||
class="mt-7 space-y-4"
|
||
@submit="submit"
|
||
>
|
||
<!-- 以下是项目基本信息模块 -->
|
||
<NFormItem path="title" label="项目标题" :label-props="{ for: 'title' }"
|
||
><NInput
|
||
v-model:value="form.title"
|
||
placeholder="例如:记忆当铺"
|
||
:disabled="busy"
|
||
:input-props="{ id: 'title', autocomplete: 'off' }"
|
||
></NInput
|
||
></NFormItem>
|
||
<NFormItem path="topic" label="故事主题" :label-props="{ for: 'topic' }"
|
||
><NInput
|
||
placeholder="描述主角、故事背景,以及你希望展开的核心冲突……"
|
||
:disabled="busy"
|
||
:input-props="{ id: 'topic' }"
|
||
class="min-h-32 resize-y"
|
||
v-model:value="form.topic"
|
||
type="textarea"
|
||
:autosize="{ minRows: 3, maxRows: 12 }"
|
||
></NInput
|
||
></NFormItem>
|
||
<!-- 以下是生成参数模块 -->
|
||
<div class="grid grid-cols-1 gap-4">
|
||
<NFormItem path="style" label="剧本风格" :label-props="{ for: 'style' }"
|
||
><NInput
|
||
placeholder="如:都市悬疑、爽文反转"
|
||
:disabled="busy"
|
||
:input-props="{ id: 'style' }"
|
||
class="min-h-32 resize-y"
|
||
v-model:value="form.style"
|
||
type="textarea"
|
||
:autosize="{ minRows: 3, maxRows: 12 }"
|
||
></NInput
|
||
></NFormItem>
|
||
<NFormItem path="episodeCount" label="计划集数" :label-props="{ for: 'episode-count' }"
|
||
><NInputNumber
|
||
:disabled="busy"
|
||
:input-props="{ id: 'episode-count' }"
|
||
:value="typeof form.episodeCount === 'number' ? form.episodeCount : null"
|
||
@update:value="form.episodeCount = $event ?? 0"
|
||
:min="1"
|
||
:step="1"
|
||
></NInputNumber
|
||
></NFormItem>
|
||
</div>
|
||
<p class="text-xs leading-5 text-muted">
|
||
建议先用 1 集验证生成效果。生成会实际调用后端模型,产生相应费用。
|
||
</p>
|
||
<NAlert v-if="error" role="alert" type="error" :show-icon="false" class=" ">{{ error }}</NAlert>
|
||
<div class="dialog-footer">
|
||
<NButton :disabled="busy" @click="open = false"> 取消</NButton
|
||
><NButton :disabled="busy" type="primary" attr-type="submit"
|
||
><LoaderCircle v-if="busy" :size="16" class="animate-spin" />开始生成<ArrowRight
|
||
v-if="!busy"
|
||
:size="16"
|
||
/></NButton>
|
||
</div>
|
||
</AppForm>
|
||
</AppDialog>
|
||
</template>
|