|
|
|
|
@ -7,53 +7,122 @@
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<el-form :model="ocForm" label-width="120px" class="creation-form">
|
|
|
|
|
<el-form-item label="OC名称" required>
|
|
|
|
|
<!-- 1. 基础信息 -->
|
|
|
|
|
<el-divider>基础信息</el-divider>
|
|
|
|
|
<el-form-item label="角色名称" required>
|
|
|
|
|
<el-input v-model="ocForm.name" placeholder="请输入OC名称" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="性格描述">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="ocForm.personality"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:rows="3"
|
|
|
|
|
placeholder="描述一下这个角色的性格特点"
|
|
|
|
|
/>
|
|
|
|
|
<el-form-item label="性别">
|
|
|
|
|
<el-radio-group v-model="ocForm.gender">
|
|
|
|
|
<el-radio label="男">男</el-radio>
|
|
|
|
|
<el-radio label="女">女</el-radio>
|
|
|
|
|
<el-radio label="其他">其他</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
<el-input v-if="ocForm.gender==='其他'" v-model="ocForm.genderOther" placeholder="自定义性别" style="width:120px;margin-left:8px;" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="生日">
|
|
|
|
|
<el-input v-model="ocForm.birthday" placeholder="如:08-15 (月-日)" maxlength="5" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="年龄">
|
|
|
|
|
<el-input v-model="ocForm.age" placeholder="请输入年龄" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="种族">
|
|
|
|
|
<el-input v-model="ocForm.race" placeholder="如:人类、精灵、机器人等" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="职业">
|
|
|
|
|
<el-input v-model="ocForm.occupation" placeholder="请输入职业" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="生日">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="ocForm.birthday"
|
|
|
|
|
type="date"
|
|
|
|
|
placeholder="选择生日"
|
|
|
|
|
format="YYYY-MM-DD"
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="爱好">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="ocForm.hobbies"
|
|
|
|
|
placeholder="请输入爱好,用逗号分隔"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="个人简介">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="ocForm.bio"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:rows="3"
|
|
|
|
|
placeholder="介绍一下这个角色"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="头像">
|
|
|
|
|
|
|
|
|
|
<!-- 2. 外表形象 -->
|
|
|
|
|
<el-divider>外表形象</el-divider>
|
|
|
|
|
<el-form-item label="发型与发色">
|
|
|
|
|
<el-input v-model="ocForm.hair" placeholder="如:银色短发" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="瞳色">
|
|
|
|
|
<el-input v-model="ocForm.eyeColor" placeholder="如:琥珀色" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="大致身高">
|
|
|
|
|
<el-input v-model="ocForm.height" placeholder="如:170cm" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="着装风格">
|
|
|
|
|
<el-input v-model="ocForm.clothes" placeholder="如:休闲运动、哥特礼服等" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="标志性特征">
|
|
|
|
|
<el-input v-model="ocForm.feature" placeholder="如:猫耳、机械臂等" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<!-- 3. 性格与内心 -->
|
|
|
|
|
<el-divider>性格与内心</el-divider>
|
|
|
|
|
<el-form-item label="核心性格">
|
|
|
|
|
<el-checkbox-group v-model="ocForm.personality">
|
|
|
|
|
<el-checkbox label="开朗外向"/><el-checkbox label="安静内向"/><el-checkbox label="成熟稳重"/><el-checkbox label="天真烂漫"/><el-checkbox label="理性冷静"/><el-checkbox label="感性冲动"/><el-checkbox label="温柔体贴"/><el-checkbox label="傲娇冷淡"/><el-checkbox label="幽默搞怪"/><el-checkbox label="认真固执"/><el-checkbox label="悲观消极"/><el-checkbox label="乐观积极"/><el-checkbox label="腹黑"/><el-checkbox label="忠犬"/><el-checkbox label="其他"/>
|
|
|
|
|
</el-checkbox-group>
|
|
|
|
|
<el-input v-if="ocForm.personality?.includes('其他')" v-model="ocForm.personalityOther" placeholder="自定义性格" style="width:120px;margin-left:8px;" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="MBTI人格类型">
|
|
|
|
|
<el-input v-model="ocForm.mbti" placeholder="如:INFP" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="最大优点">
|
|
|
|
|
<el-input v-model="ocForm.advantage" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="最大缺点">
|
|
|
|
|
<el-input v-model="ocForm.shortcoming" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="习惯性小动作">
|
|
|
|
|
<el-input v-model="ocForm.habit" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="口头禅">
|
|
|
|
|
<el-input v-model="ocForm.catchphrase" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<!-- 4. 背景故事与关系 -->
|
|
|
|
|
<el-divider>背景故事与关系</el-divider>
|
|
|
|
|
<el-form-item label="成长环境">
|
|
|
|
|
<el-input v-model="ocForm.growup" placeholder="如:都市、乡村等" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="背景故事">
|
|
|
|
|
<el-input v-model="ocForm.story" type="textarea" :rows="2" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="想要隐藏的秘密">
|
|
|
|
|
<el-input v-model="ocForm.secret" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<!-- 5. 喜好与日常 -->
|
|
|
|
|
<el-divider>喜好与日常</el-divider>
|
|
|
|
|
<el-form-item label="兴趣爱好">
|
|
|
|
|
<el-input v-model="ocForm.hobbies" placeholder="如:阅读、健身等" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="喜欢的食物">
|
|
|
|
|
<el-input v-model="ocForm.foodLike" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="讨厌的东西">
|
|
|
|
|
<el-input v-model="ocForm.hate" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="理想生活方式">
|
|
|
|
|
<el-input v-model="ocForm.life" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="周末日常">
|
|
|
|
|
<el-input v-model="ocForm.weekend" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<!-- 6. 社区角色期待 -->
|
|
|
|
|
<el-divider>社区角色期待</el-divider>
|
|
|
|
|
<el-form-item label="社区主要活动">
|
|
|
|
|
<el-input v-model="ocForm.communityActivity" placeholder="如:在花店打工" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="关系期待">
|
|
|
|
|
<el-checkbox-group v-model="ocForm.relation">
|
|
|
|
|
<el-checkbox label="亲密无间的朋友"/><el-checkbox label="互相扶持的伙伴"/><el-checkbox label="需要照顾的后辈"/><el-checkbox label="值得信赖的前辈"/><el-checkbox label="略带距离感的熟人"/><el-checkbox label="其他"/>
|
|
|
|
|
</el-checkbox-group>
|
|
|
|
|
<el-input v-if="ocForm.relation?.includes('其他')" v-model="ocForm.relationOther" placeholder="自定义关系" style="width:120px;margin-left:8px;" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<!-- 头像 -->
|
|
|
|
|
<el-divider>头像</el-divider>
|
|
|
|
|
<el-form-item label="头像URL">
|
|
|
|
|
<el-input v-model="ocForm.avatar" placeholder="头像URL(可选)" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button
|
|
|
|
|
type="primary"
|
|
|
|
|
@ -89,32 +158,109 @@ import { useRouter } from 'vue-router'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import { addOC } from '../stores/ocStore.js'
|
|
|
|
|
import { currentUser } from '../stores/userStore.js'
|
|
|
|
|
import axios from 'axios'
|
|
|
|
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
|
|
|
|
const ocForm = reactive({
|
|
|
|
|
name: '',
|
|
|
|
|
personality: '',
|
|
|
|
|
occupation: '',
|
|
|
|
|
birthday: '',
|
|
|
|
|
hobbies: '',
|
|
|
|
|
bio: '',
|
|
|
|
|
name: '', gender: '', genderOther: '', birthday: '', age: '', race: '', occupation: '',
|
|
|
|
|
hair: '', eyeColor: '', height: '', clothes: '', feature: '',
|
|
|
|
|
personality: [], personalityOther: '', mbti: '', advantage: '', shortcoming: '', habit: '', catchphrase: '',
|
|
|
|
|
growup: '', story: '', secret: '',
|
|
|
|
|
hobbies: '', foodLike: '', hate: '', life: '', weekend: '',
|
|
|
|
|
communityActivity: '', relation: [], relationOther: '',
|
|
|
|
|
avatar: ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const handleAIGenerate = () => {
|
|
|
|
|
console.log('AI生成OC')
|
|
|
|
|
// 模拟AI生成数据
|
|
|
|
|
ocForm.name = '小' + Math.random().toString(36).substr(2, 4)
|
|
|
|
|
ocForm.personality = '活泼开朗,喜欢交朋友'
|
|
|
|
|
ocForm.occupation = '学生'
|
|
|
|
|
ocForm.birthday = '2000-01-01'
|
|
|
|
|
ocForm.hobbies = '音乐、绘画、运动'
|
|
|
|
|
ocForm.bio = '这是一个由AI生成的OC角色'
|
|
|
|
|
ocForm.avatar = `https://i.pravatar.cc/100?img=${Math.floor(Math.random() * 50) + 1}`
|
|
|
|
|
|
|
|
|
|
ElMessage.success('AI生成完成!')
|
|
|
|
|
const handleAIGenerate = async () => {
|
|
|
|
|
ElMessage.info('AI生成中...')
|
|
|
|
|
const API_KEY = import.meta.env.VITE_ZHIPU_API_KEY
|
|
|
|
|
const prompt = `生成一个OC角色的JSON对象,字段有:
|
|
|
|
|
name, gender, genderOther, birthday, age, race, occupation, hair, eyeColor, height, clothes, feature, personality(数组), personalityOther, mbti, advantage, shortcoming, habit, catchphrase, growup, story, secret, hobbies(数组), foodLike, hate, life, weekend, communityActivity, relation(数组), relationOther, avatar。
|
|
|
|
|
每个字段都要有合理虚构内容,必须为中文,不能留空、不能为[]、不能为null。
|
|
|
|
|
只输出JSON对象本身,不要任何推理、解释、reasoning、注释、代码块、markdown标记或多余内容。`;
|
|
|
|
|
try {
|
|
|
|
|
const res = await axios.post(
|
|
|
|
|
'https://open.bigmodel.cn/api/paas/v4/chat/completions',
|
|
|
|
|
{
|
|
|
|
|
model: 'glm-4-flash',
|
|
|
|
|
messages: [
|
|
|
|
|
{ role: 'user', content: prompt }
|
|
|
|
|
],
|
|
|
|
|
temperature: 0.9,
|
|
|
|
|
max_tokens: 1024,
|
|
|
|
|
thinking: { type: 'enabled' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
headers: {
|
|
|
|
|
'Authorization': `Bearer ${API_KEY}`,
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
console.log('AI接口完整响应:', res.data)
|
|
|
|
|
let jsonStr = res.data.choices?.[0]?.message?.content || ''
|
|
|
|
|
// 兼容AI输出前后有markdown、代码块或多余解释
|
|
|
|
|
jsonStr = jsonStr.replace(/^```json|```$/g, '').trim()
|
|
|
|
|
// 尝试只提取第一个合法JSON对象
|
|
|
|
|
let data = {}
|
|
|
|
|
let parsed = false
|
|
|
|
|
try {
|
|
|
|
|
data = JSON.parse(jsonStr)
|
|
|
|
|
parsed = true
|
|
|
|
|
} catch {}
|
|
|
|
|
if (!parsed) {
|
|
|
|
|
// 尝试用正则提取第一个 {...} 内容
|
|
|
|
|
const match = jsonStr.match(/\{[\s\S]*\}/)
|
|
|
|
|
if (match) {
|
|
|
|
|
try {
|
|
|
|
|
data = JSON.parse(match[0])
|
|
|
|
|
parsed = true
|
|
|
|
|
} catch {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// B方案:如content为空且reasoning_content存在,尝试从reasoning_content中提取JSON
|
|
|
|
|
if (!parsed && res.data.choices?.[0]?.message?.reasoning_content) {
|
|
|
|
|
const reasoning = res.data.choices[0].message.reasoning_content
|
|
|
|
|
const match = reasoning.match(/\{[\s\S]*\}/)
|
|
|
|
|
if (match) {
|
|
|
|
|
try {
|
|
|
|
|
data = JSON.parse(match[0])
|
|
|
|
|
parsed = true
|
|
|
|
|
} catch {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!parsed) {
|
|
|
|
|
ElMessage.error('AI生成内容解析失败,原始内容已打印到控制台')
|
|
|
|
|
console.error('AI原始内容:', jsonStr)
|
|
|
|
|
if (res.data.choices?.[0]?.message?.reasoning_content) {
|
|
|
|
|
console.error('AI reasoning_content:', res.data.choices[0].message.reasoning_content)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// 兼容AI输出的personality/relation为字符串或数组,增强类型兼容
|
|
|
|
|
Object.keys(ocForm).forEach(k => {
|
|
|
|
|
if (data[k] !== undefined) {
|
|
|
|
|
if (Array.isArray(ocForm[k]) && typeof data[k] === 'string') {
|
|
|
|
|
ocForm[k] = data[k].split(/[,,]/).map(s => s.trim()).filter(Boolean)
|
|
|
|
|
} else if (typeof ocForm[k] === 'string' && typeof data[k] !== 'string') {
|
|
|
|
|
ocForm[k] = String(data[k])
|
|
|
|
|
} else if (Array.isArray(ocForm[k]) && Array.isArray(data[k])) {
|
|
|
|
|
ocForm[k] = data[k].map(x => typeof x === 'string' ? x.trim() : String(x))
|
|
|
|
|
} else {
|
|
|
|
|
ocForm[k] = data[k]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
console.log('AI同步到表单后的ocForm:', JSON.parse(JSON.stringify(ocForm)))
|
|
|
|
|
ElMessage.success('AI生成完成!')
|
|
|
|
|
} catch (err) {
|
|
|
|
|
let msg = 'AI生成失败'
|
|
|
|
|
if (err?.response?.data?.msg) msg += ': ' + err.response.data.msg
|
|
|
|
|
else if (err?.message) msg += ': ' + err.message
|
|
|
|
|
ElMessage.error(msg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
|