fix(workbench): 模型目录与助手形象 - 修正 Live2D 首帧闪现和模型默认分组 (#773)
* fix(workbench): 模型目录与助手形象 - 修正 Live2D 首帧闪现和模型默认分组
进入画布时形象配置与 Live2D 首帧未就绪不再显示默认球,改为加载提示,失败才回退。
新导入模型一级目录默认未分类、二级渠道展示名默认跟随上游 ID;历史非空分组(含与模型 ID 同名)保留。
拉取模型弹窗默认只勾选已存在模型并移除逐项已存在字样;一级分组下拉点击即展开全部选项。
* style(web): 画布与模型导入 - 按 Prettier 规范格式化改动文件
修复 PR 中 Web checks 的格式化检查失败:Live2D 启动器 JSX、模型导入选择工具函数与 Live2D 加载测试按项目 Prettier 配置重新排版,不改变行为。
* test(web): Live2D 生命周期 - 修复 bun 1.3.9 下 text 导入与模块导入冲突
agent-live2d-lifecycle 曾用 with { type: text } 导入 live2d-avatar.tsx,bun 1.3.9 会按路径复用该缓存,导致新增的模块导入拿不到 Live2DAvatar 具名导出并报 SyntaxError。改为 readFileSync 读取源码文本,本地以 bun 1.3.9 复现并验证通过。
This commit is contained in:
@@ -34,10 +34,12 @@ import { markdownPlainText } from "@/lib/markdown-plain-text";
|
||||
import type { ApprovalState } from "./canvas-cloud-agent-events";
|
||||
|
||||
// hidden 为 true 时入口保持挂载但不显示:Live2D 模型加载开销大,面板开合不能卸载它,
|
||||
// 否则每次重新挂载都要重新下载模型并重建渲染上下文,期间只能显示默认形象。
|
||||
// 否则每次重新挂载都要重新下载模型并重建渲染上下文。
|
||||
export function AgentLauncher({ theme, statusColor, approvalPending, reducedMotion, hidden = false, onOpen }: { theme: CanvasTheme; statusColor: string; approvalPending: boolean; reducedMotion: boolean; hidden?: boolean; onOpen: () => void }) {
|
||||
const appearance = useAppearanceStore((state) => state.appearance.canvas) || DEFAULT_CANVAS_APPEARANCE;
|
||||
const appearanceResolved = useAppearanceStore((state) => state.resolved);
|
||||
const live = appearance.avatarType === "live2d" && Boolean(appearance.live2dResourceId && appearance.live2dEntry);
|
||||
const [live2DError, setLive2DError] = useState("");
|
||||
const [viewport, setViewport] = useState(() => ({ width: window.innerWidth, height: window.innerHeight }));
|
||||
useEffect(() => {
|
||||
const resize = () => setViewport({ width: window.innerWidth, height: window.innerHeight });
|
||||
@@ -51,7 +53,7 @@ export function AgentLauncher({ theme, statusColor, approvalPending, reducedMoti
|
||||
<motion.button
|
||||
type="button"
|
||||
aria-label={`打开${appearance.agentName}`}
|
||||
title={`${approvalPending ? "Agent 等待你的审批" : "打开 Agent 助手"} · 拖动可调整位置,聚焦后可用方向键移动`}
|
||||
title={live && live2DError ? live2DError : `${approvalPending ? "Agent 等待你的审批" : "打开 Agent 助手"} · 拖动可调整位置,聚焦后可用方向键移动`}
|
||||
className={cn("canvas-agent-launcher fixed z-[calc(var(--z-toast)+1)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/35", dragging && "is-dragging", live && "canvas-agent-launcher-live2d")}
|
||||
style={{ ...position, width, height, color: theme.node.text, "--canvas-agent-launcher-shadow": theme.spatial.shadow, ...(hidden ? { display: "none" } : null) } as CSSProperties}
|
||||
aria-hidden={hidden || undefined}
|
||||
@@ -63,11 +65,29 @@ export function AgentLauncher({ theme, statusColor, approvalPending, reducedMoti
|
||||
transition={{ duration: reducedMotion ? 0 : 0.18 }}
|
||||
>
|
||||
{live ? (
|
||||
<Live2DAvatar url={live2DModelURL(appearance.live2dResourceId, appearance.live2dEntry)} width={width} height={height} reducedMotion={reducedMotion} fallback={<FluidOrb size={60} color="#7164f6" />} />
|
||||
) : (
|
||||
<Live2DAvatar
|
||||
url={live2DModelURL(appearance.live2dResourceId, appearance.live2dEntry)}
|
||||
width={width}
|
||||
height={height}
|
||||
reducedMotion={reducedMotion}
|
||||
fallback={<FluidOrb size={60} color="#7164f6" />}
|
||||
onReady={() => setLive2DError("")}
|
||||
onError={setLive2DError}
|
||||
/>
|
||||
) : appearanceResolved ? (
|
||||
<FluidOrb size={60} color="#7164f6" />
|
||||
) : (
|
||||
<span role="status" aria-busy="true">
|
||||
<LoaderCircle className={reducedMotion ? "size-6" : "size-6 motion-safe:animate-spin"} aria-hidden="true" />
|
||||
<span className="sr-only">正在加载助手形象</span>
|
||||
</span>
|
||||
)}
|
||||
{appearance.launcherLabel ? <span className="canvas-agent-launcher-label">{appearance.launcherLabel}</span> : null}
|
||||
{live && live2DError ? (
|
||||
<span className="sr-only" role="status">
|
||||
{live2DError}
|
||||
</span>
|
||||
) : null}
|
||||
<span className={cn("canvas-agent-launcher-status", approvalPending && "is-pending")} style={{ "--canvas-agent-status-color": statusColor } as CSSProperties} />
|
||||
{approvalPending ? <span className="canvas-agent-launcher-badge">待审批</span> : null}
|
||||
</motion.button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||
import { LoaderCircle } from "lucide-react";
|
||||
import { installLive2DLoader } from "@/services/live2d-loader";
|
||||
import { adaptLive2DRenderOrders } from "@/lib/canvas/live2d-core-adapter";
|
||||
|
||||
@@ -95,8 +96,9 @@ export function Live2DAvatar({
|
||||
model.update(Math.min(now - elapsed, 100));
|
||||
elapsed = now;
|
||||
app.render();
|
||||
} catch {
|
||||
failRendering("Live2D 渲染失败,已回退默认形象");
|
||||
} catch (error) {
|
||||
const detail = error instanceof Error && error.message ? `:${error.message}` : "";
|
||||
failRendering(`Live2D 渲染失败${detail},已回退默认形象`);
|
||||
}
|
||||
};
|
||||
const sync = () => {
|
||||
@@ -188,9 +190,16 @@ export function Live2DAvatar({
|
||||
};
|
||||
}, [url, width, height, reducedMotion]);
|
||||
return (
|
||||
<span style={{ display: "grid", placeItems: "center", width, height, pointerEvents: "none" }}>
|
||||
<span aria-busy={!failed && !ready} style={{ display: "grid", placeItems: "center", width, height, pointerEvents: "none" }}>
|
||||
<span ref={host} style={{ gridArea: "1 / 1", visibility: failed || !ready ? "hidden" : "visible", width, height }} />
|
||||
{failed || !ready ? <span style={{ gridArea: "1 / 1" }}>{fallback}</span> : null}
|
||||
{failed ? (
|
||||
<span style={{ gridArea: "1 / 1" }}>{fallback}</span>
|
||||
) : !ready ? (
|
||||
<span role="status" style={{ gridArea: "1 / 1" }}>
|
||||
<LoaderCircle className={reducedMotion ? "size-6" : "size-6 motion-safe:animate-spin"} aria-hidden="true" />
|
||||
<span className="sr-only">正在加载 Live2D 模型</span>
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user