100 lines
4.8 KiB
Vue
100 lines
4.8 KiB
Vue
<script setup lang="ts">
|
||
import AppForm from '../../../components/ui/AppForm.vue'
|
||
import { NFormItem } from 'naive-ui'
|
||
import DetailDisclosure from '../../../components/ui/DetailDisclosure.vue'
|
||
import { NButton, NCheckbox, NInput } from 'naive-ui'
|
||
import { computed, reactive, ref, watch } from 'vue'
|
||
import type { SaveIdentityInput, SubjectIdentity } from '../types'
|
||
|
||
/** 身份文本草稿不随查询覆盖;父页面切换主体或保存成功后重建编辑器。 */
|
||
const props = defineProps<{ identity: SubjectIdentity | null; disabled: boolean; module: string }>()
|
||
const emit = defineEmits<{ save: [input: SaveIdentityInput]; dirty: [value: boolean] }>()
|
||
const form = reactive({ description: '', generationPrompt: '', isLocked: false })
|
||
const baseline = ref('')
|
||
const dirty = computed(() => JSON.stringify(form) !== baseline.value)
|
||
watch(
|
||
() => props.identity,
|
||
value => {
|
||
if (baseline.value && dirty.value) return
|
||
Object.assign(form, {
|
||
description: value?.description ?? '',
|
||
generationPrompt: value?.generationPrompt ?? '',
|
||
isLocked: value?.isLocked ?? false
|
||
})
|
||
baseline.value = JSON.stringify(form)
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
watch(dirty, value => emit('dirty', value), { immediate: true })
|
||
|
||
/** 保存用户输入;空文本允许保存,但生图入口仍会检查必要 Prompt。 */
|
||
function save() {
|
||
if (!props.disabled) emit('save', { ...form })
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<AppForm :model="form" :disabled="disabled" class="mt-5" @submit="save">
|
||
<fieldset :disabled="disabled" class="space-y-4">
|
||
<NFormItem
|
||
class="block"
|
||
path="description"
|
||
label="稳定身份描述"
|
||
:label-props="{ for: 'identity-description' }"
|
||
><NInput
|
||
:disabled="disabled"
|
||
placeholder="只描述跨形态不变的面部、结构、材质等特征"
|
||
:input-props="{ id: 'identity-description' }"
|
||
class="min-h-28"
|
||
v-model:value="form.description"
|
||
type="textarea"
|
||
:autosize="{ minRows: 3, maxRows: 12 }"
|
||
></NInput>
|
||
</NFormItem>
|
||
<NFormItem
|
||
class="block"
|
||
path="generationPrompt"
|
||
label="身份核心提示词(稳定事实)"
|
||
:label-props="{ for: 'identity-prompt' }"
|
||
><NInput
|
||
:disabled="disabled"
|
||
placeholder="不固定某一形态的服装、伤势或临时状态"
|
||
:input-props="{ id: 'identity-prompt' }"
|
||
class="min-h-36"
|
||
v-model:value="form.generationPrompt"
|
||
type="textarea"
|
||
:autosize="{ minRows: 3, maxRows: 12 }"
|
||
></NInput>
|
||
</NFormItem>
|
||
<DetailDisclosure title="身份填写与锁定规则"
|
||
><p class="text-xs leading-6 text-muted">
|
||
{{
|
||
module === 'prop'
|
||
? '保留道具固定文字、编号、部件位置、结构与材质组合;不要泛化原有辨识细节,也不要写临时损伤或摆放状态。'
|
||
: module === 'scene'
|
||
? '保留建筑骨架、入口、窗户、楼梯与固定布局,不写临时天气、人群或灯光。'
|
||
: '保留有依据的五官、骨相、发际线与体型,不固定某套服装、临时表情或伤势。'
|
||
}}
|
||
这里保存身份事实;背景、视角和参考图约束由后端另行编译,不是最终整段生图 Prompt。
|
||
</p>
|
||
<p class="mt-2 text-xs text-muted">
|
||
人物需要同时确认母版并锁定身份,首帧才能就绪。锁定后仍可人工编辑、生成图片及切换母版。
|
||
</p></DetailDisclosure
|
||
>
|
||
<NCheckbox
|
||
:disabled="disabled"
|
||
id="identity-lock"
|
||
v-model:checked="form.isLocked"
|
||
class="flex items-start gap-2 text-xs leading-6"
|
||
>{{ module === 'character' ? '确认并锁定演员身份' : '确认并锁定身份文本' }}</NCheckbox
|
||
>
|
||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||
<span class="text-xs text-muted">{{
|
||
dirty ? '有未保存修改,刷新不会覆盖草稿。' : '图片以已保存的身份提示词为准。'
|
||
}}</span
|
||
><NButton :disabled="disabled" type="primary" attr-type="submit">保存主体身份</NButton>
|
||
</div>
|
||
</fieldset>
|
||
</AppForm>
|
||
</template>
|