Files
ttstd-infinite-canvas/web/src/components/layout/client-root-init.tsx
QAyong cf6c78d540 feat: 完善创作工作台与用户诊断支持 (#295)
* feat(canvas): 参考素材 - 增加移除按钮并删除对应连线

* feat(canvas): 完善节点面板交互与拖拽反馈

* fix(canvas): 画布交互与小地图 - 统一浮层层级并完善节点识别

* feat(prompt-optimizer): 增加 AI 提示词优化器及宿主接入

* feat(canvas): 画布 - 增加自动整理节点按钮并补充文档

* chore(web): 前端 - 修正提示词优化器格式

* chore(backend): 后端 - 修正 Go 格式检查

* feat(workspace): 完善诊断导出与本地排障支持

* chore(web): 格式化诊断导出文件

* fix(web-test): 兼容 Bun 测试运行时

* fix(web-test): 对齐模型目录协议断言
2026-08-25 13:49:15 +08:00

79 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ReactNode } from "react";
import { useEffect, useRef } from "react";
import { App } from "antd";
import { createModelChannel, useConfigStore } from "@/stores/use-config-store";
import { navigateToSettings } from "@/lib/settings-navigation";
import { useLocalDreaminaModelBootstrap } from "@/stores/use-local-dreamina-model-store";
import { useLocalRuntimeBootstrap } from "@/stores/use-local-runtime-store";
import { initializeClientDiagnostics, setDiagnosticUserScope } from "@/services/diagnostics/client-diagnostics";
import { useUserStore } from "@/stores/use-user-store";
export function ClientRootInit({ children }: { children: ReactNode }) {
const config = useConfigStore((state) => state.config);
const userId = useUserStore((state) => state.user?.id || "");
const localRuntimeConfigured = config.channels.some((channel) => channel.transport === "local-runtime" && channel.enabled !== false);
useLocalRuntimeBootstrap(localRuntimeConfigured);
useLocalDreaminaModelBootstrap();
const { message } = App.useApp();
const handledConfigParams = useRef(false);
const updateConfig = useConfigStore((state) => state.updateConfig);
useEffect(() => {
initializeClientDiagnostics();
}, []);
useEffect(() => {
setDiagnosticUserScope(userId);
}, [userId]);
useEffect(() => {
const interactiveSelector = 'button, [role="button"], a, [class*="card"], [class*="Card"]';
const blurPointerFocus = (event: PointerEvent) => {
if (event.pointerType === "mouse" && event.button !== 0) return;
const target = event.target instanceof Element ? event.target.closest<HTMLElement>(interactiveSelector) : null;
if (!target || target.hasAttribute("disabled") || target.getAttribute("aria-disabled") === "true") return;
// 浏览器可能把鼠标点击误判为 :focus-visible;下一帧只清掉这次指针点击产生的焦点。
window.requestAnimationFrame(() => {
if (document.activeElement === target) target.blur();
});
};
document.addEventListener("pointerdown", blurPointerFocus, true);
return () => document.removeEventListener("pointerdown", blurPointerFocus, true);
}, []);
useEffect(() => {
if (handledConfigParams.current) return;
const searchParams = new URLSearchParams(window.location.search);
const baseUrl = searchParams.get("baseUrl") || searchParams.get("baseurl");
const ignoredApiKey = searchParams.has("apiKey") || searchParams.has("apikey");
if (!baseUrl && !ignoredApiKey) return;
handledConfigParams.current = true;
searchParams.delete("baseUrl");
searchParams.delete("baseurl");
searchParams.delete("apiKey");
searchParams.delete("apikey");
window.history.replaceState(null, "", `${window.location.pathname}${searchParams.size ? `?${searchParams}` : ""}${window.location.hash}`);
const firstChannel = config.channels[0];
updateConfig(
"channels",
firstChannel
? config.channels.map((channel, index) =>
index === 0
? {
...channel,
...(baseUrl ? { baseUrl } : {}),
}
: channel,
)
: [createModelChannel({ id: "default", name: "默认渠道", baseUrl: baseUrl || undefined })],
);
if (baseUrl) updateConfig("baseUrl", baseUrl);
navigateToSettings({ section: "channels" });
if (ignoredApiKey) message.warning("出于安全考虑,链接中的 API Key 已忽略,请在配置中手动填写");
else message.success("已导入本地直连地址");
}, [config.channels, message, updateConfig]);
return <>{children}</>;
}