import { useRef, useState } from "react"; import { Alert, App, Button, Form, Input, InputNumber, Radio } from "antd"; import { ExternalLink, Upload } from "lucide-react"; import { useReducedMotion } from "motion/react"; import { Live2DAvatar } from "@/components/canvas/live2d-avatar"; import { FluidOrb } from "@/components/ui/fluid-orb"; import { agentCopy, type CanvasAppearance } from "@/lib/canvas/agent-appearance"; import { live2DModelURL, uploadLive2D } from "@/services/api/appearance"; export function CanvasAppearanceEditor({ value, onChange, disabled, onUploading }: { value: CanvasAppearance; onChange: (value: CanvasAppearance) => void; disabled: boolean; onUploading: (value: boolean) => void }) { const { message } = App.useApp(); const reducedMotion = useReducedMotion() ?? false; const input = useRef(null); const [uploading, setUploading] = useState(false); const [readyURL, setReadyURL] = useState(""); const [error, setError] = useState(""); const [attempt, setAttempt] = useState(0); const url = value.live2dResourceId ? live2DModelURL(value.live2dResourceId, value.live2dEntry, true) : ""; const change = (patch: Partial) => onChange({ ...value, ...patch }); async function upload(file?: File) { if (!file) return; if (!file.name.toLowerCase().endsWith(".zip") || file.size > 128 * 1024 * 1024) { message.error("请选择不超过 128 MiB 的 Live2D ZIP"); return; } setUploading(true); onUploading(true); setError(""); try { const model = await uploadLive2D(file); setReadyURL(""); change({ live2dResourceId: model.resourceId, live2dEntry: model.entry, avatarType: "orb" }); message.success("导入成功,请预览后选择 Live2D,并点击页面顶部保存"); } catch (cause) { message.error(cause instanceof Error ? cause.message : "模型导入失败"); } finally { setUploading(false); onUploading(false); } } return (

Agent 身份与文案

独立于站点品牌。文案可使用 {"{agentName}"} 引用助手名称;这里只配置展示文字,不修改工具权限。

{( [ ["agentName", "助手名称", 24], ["launcherLabel", "悬浮入口文字(可留空)", 24], ["panelTitle", "对话面板标题", 40], ["welcomeTitle", "欢迎标题", 100], ["welcomeDescription", "欢迎说明", 200], ["inputPlaceholder", "输入框提示", 160], ] as const ).map(([key, label, max]) => ( change({ [key]: event.target.value })} /> ))}

Agent 形象

change({ avatarType: event.target.value })} options={[ { label: "默认动态球", value: "orb" }, { label: "Live2D", value: "live2d", disabled: !url || readyURL !== url }, ]} />

上传有授权的 Cubism 3/4 运行时 ZIP:一个 model3.json、moc3、PNG 纹理及可选动作/表情 JSON。首期不支持音频、旧版模型和编辑工程。模型会发送到浏览器,无法保证防下载。

下载前请确认素材授权;官方原始包需按上述要求整理为运行时 ZIP,不能直接整包导入。

{ void upload(event.target.files?.[0]); event.currentTarget.value = ""; }} />
{url ? ( ) : null}
{url ? ( } onReady={() => { setReadyURL(url); setError(""); }} onError={(reason) => { setReadyURL(""); setError(reason); }} /> ) : ( )} {agentCopy(value.welcomeTitle, value.agentName)}

{agentCopy(value.welcomeDescription, value.agentName)}

{url ? {readyURL === url ? "预览已就绪,可选择 Live2D 并保存启用" : "等待模型预览就绪"} : null}
{error ? ( { setError(""); setAttempt((n) => n + 1); }} > 重试 } /> ) : null}

项目已内置 Cubism Core,无需另行安装。使用前仍需确认 Live2D SDK 及模型素材的适用授权。未启用的导入包可在资源管理中清理。

); }