- 形仪域 domains/xingyi:form_anchors(稳定辨识特征/年龄表现/许可禁忌/可变范围——生成内容不可静默覆盖锚点);forms(主仪/化仪/文字形仪,candidate→adopt 定相治理:未定相不进呈现位、主仪唯一、新主仪自动退役旧主仪);voice_profiles(三来源,授权克隆必填用途与传播范围,revoke-grant 撤回立即生效,assert-usable 合成前置校验拒绝撤回/过期) - ASR:元炁 SenseVoice /v1/audio/transcriptions 透传(Whisper 兼容 multipart);YUANQI_BASE_URL 未配置时 503 不降级不伪造——多模态关闭纪律 - 前端形仪工坊(人物详情页):真形锚点编辑(特征逐行解析)/候选登记定相废弃/声音档案建立与授权撤回;头像呈现位改走定相主仪(forms/primary);xingyi api 库 + mock + 组件 4 用例 - 退役:旧 avatar 模块(services/endpoints/avatar、avatar_dir 配置、avatar_url 字段、前端直传组件与测试) - 验收(test_m7_xingyi 三用例):未定相不成为正式形仪(含主仪唯一与旧主仪 retired 断言);克隆授权可撤回(缺用途拒/撤回即拒/过期同理/预设不受约束);关闭全部多模态后文字相伴完整可用(ASR 503 时镜树缘契百工照常——回归) - 四套件:后端 376、前端 203、BFF 4、神识 5 包全绿(2 个预存环境失败不变);迁移 008 净库+±1 干净
128 lines
4.1 KiB
TypeScript
128 lines
4.1 KiB
TypeScript
import Link from "next/link";
|
|
import { notFound } from "next/navigation";
|
|
|
|
import { CharacterIdentityCard } from "@/components/characters/CharacterIdentityCard";
|
|
import { CovenantPanel } from "@/components/characters/CovenantPanel";
|
|
import { FormStudio } from "@/components/xingyi/FormStudio";
|
|
import { CharacterLifecycleBar } from "@/components/characters/CharacterLifecycleBar";
|
|
import { ModelPolicyEditForm } from "@/components/characters/ModelPolicyEditForm";
|
|
import { ProfileEditForm } from "@/components/characters/ProfileEditForm";
|
|
import { PageContainer } from "@/components/layout/PageContainer";
|
|
import { ErrorState } from "@/components/shared/StateViews";
|
|
import { CharacterStatusBadge } from "@/components/ui/StatusBadge";
|
|
|
|
import { serverApi } from "@/lib/api/server-client";
|
|
import { describeError } from "@/lib/utils/error";
|
|
import { formatDateTime } from "@/lib/utils/format";
|
|
import type { Covenant } from "@/lib/api/covenants";
|
|
import type {
|
|
FormAnchorData,
|
|
FormData,
|
|
VoiceProfileData,
|
|
} from "@/lib/api/xingyi";
|
|
import type { Character } from "@/types/api";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
/**
|
|
* 人物详情页:生命周期操作 + 档案编辑 + 模型策略编辑。
|
|
* Server Component 预取 getCharacter;写操作后 router.refresh() 重新拉取,以服务端数据为准。
|
|
*/
|
|
export default async function CharacterDetailPage({
|
|
params,
|
|
}: {
|
|
params: Promise<{ characterId: string }>;
|
|
}) {
|
|
const { characterId } = await params;
|
|
|
|
let character = null;
|
|
let covenant: Covenant | null = null;
|
|
let error: string | null = null;
|
|
try {
|
|
character = await serverApi.get<Character>(`/characters/${characterId}`);
|
|
} catch (e) {
|
|
error = describeError(e);
|
|
}
|
|
// 缘契独立拉取,失败静默(无缘契是合法状态)
|
|
try {
|
|
covenant = await serverApi.get<Covenant | null>(
|
|
`/covenants/by-character/${characterId}`,
|
|
);
|
|
} catch {
|
|
covenant = null;
|
|
}
|
|
// 形仪三区独立拉取,失败静默(无形仪是合法状态——启镜只需最小形仪)
|
|
let anchor: FormAnchorData = {
|
|
character_id: characterId,
|
|
features: {},
|
|
age_presentation: "adult",
|
|
permissions: {},
|
|
mutable_scope: {},
|
|
};
|
|
let forms: FormData[] = [];
|
|
let voices: VoiceProfileData[] = [];
|
|
let primary: FormData | null = null;
|
|
try {
|
|
[anchor, forms, voices, primary] = await Promise.all([
|
|
serverApi.get<FormAnchorData>(`/characters/${characterId}/form-anchor`),
|
|
serverApi.get<FormData[]>(`/characters/${characterId}/forms`),
|
|
serverApi.get<VoiceProfileData[]>(`/characters/${characterId}/voices`),
|
|
serverApi.get<FormData | null>(
|
|
`/characters/${characterId}/forms/primary`,
|
|
),
|
|
]);
|
|
} catch {
|
|
/* 空态合法 */
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<PageContainer title="人物详情">
|
|
<ErrorState message={error} />
|
|
</PageContainer>
|
|
);
|
|
}
|
|
if (!character) return notFound();
|
|
|
|
return (
|
|
<PageContainer
|
|
title={character.display_name || character.name}
|
|
description={`内部名称:${character.name} · 创建于 ${formatDateTime(character.created_at)}`}
|
|
>
|
|
<div className="mb-4 flex flex-wrap items-center gap-3">
|
|
<CharacterStatusBadge status={character.status} />
|
|
<CharacterLifecycleBar character={character} />
|
|
<Link
|
|
href={`/characters/${character.id}/materials`}
|
|
className="ml-auto rounded-lg border border-border-glass bg-glass px-3 py-1 text-xs text-patina hover:underline"
|
|
>
|
|
资料与沉淀 →
|
|
</Link>
|
|
</div>
|
|
|
|
<CharacterIdentityCard
|
|
character={character}
|
|
primaryMediaRef={primary?.media_ref ?? null}
|
|
/>
|
|
|
|
<div className="mt-6">
|
|
<CovenantPanel characterId={character.id} initialCovenant={covenant} />
|
|
</div>
|
|
|
|
<div className="mt-6">
|
|
<FormStudio
|
|
characterId={character.id}
|
|
initialAnchor={anchor}
|
|
initialForms={forms}
|
|
initialVoices={voices}
|
|
/>
|
|
</div>
|
|
|
|
<div className="mt-6 grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
<ProfileEditForm character={character} />
|
|
<ModelPolicyEditForm character={character} />
|
|
</div>
|
|
</PageContainer>
|
|
);
|
|
}
|