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): 对齐模型目录协议断言
This commit is contained in:
QAyong authored and GitHub committed 2026-08-25 13:49:15 +08:00
1 parent 625a97560b
commit cf6c78d540
70 files changed
+6863 -1708

No files matched your search

+20 -4
View File
@@ -1,4 +1,4 @@
import { useEffect, useId, useMemo, useRef, useState, type KeyboardEvent } from "react";
import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent } from "react";
import { Check, ChevronDown, Coins } from "lucide-react";
import { Popover } from "antd";
@@ -19,6 +19,7 @@ type ModelPickerProps = {
onChange: (model: string) => void;
capability?: ModelCapability;
className?: string;
popoverClassName?: string;
fullWidth?: boolean;
placeholder?: string;
onMissingConfig?: () => void;
@@ -28,13 +29,14 @@ type ModelPickerProps = {
showConfiguredModelName?: boolean;
};
export function ModelPicker({ config, value, onChange, capability, className, fullWidth = false, placeholder = "选择模型", onMissingConfig, showSelectedPrice = true, variant = "default", requirements, showConfiguredModelName = false }: ModelPickerProps) {
export function ModelPicker({ config, value, onChange, capability, className, popoverClassName, fullWidth = false, placeholder = "选择模型", onMissingConfig, showSelectedPrice = true, variant = "default", requirements, showConfiguredModelName = false }: ModelPickerProps) {
const creditsEnabled = useUserStore((state) => state.features.creditsEnabled);
const pickerId = useId();
// 双保险:即使 store merge 写出非法 theme,这里也兜底到 dark,避免 "reading 'node'" 崩溃
const rawTheme = useThemeStore((state) => state.theme);
const theme = (canvasThemes[rawTheme as keyof typeof canvasThemes] ?? canvasThemes.dark) as CanvasTheme;
const [open, setOpen] = useState(false);
const [triggerWidth, setTriggerWidth] = useState<number | null>(null);
const menuRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLButtonElement>(null);
const options = useMemo(() => Array.from(new Set(selectableModelsByCapability(config, capability).filter(Boolean))), [capability, config]);
@@ -65,6 +67,16 @@ export function ModelPicker({ config, value, onChange, capability, className, fu
const [routeQuote, setRouteQuote] = useState<LogicalModelQuote | undefined>();
const creationVariant = variant === "creation";
useLayoutEffect(() => {
const trigger = triggerRef.current;
if (!trigger) return;
const updateTriggerWidth = () => setTriggerWidth(Math.ceil(trigger.getBoundingClientRect().width));
updateTriggerWidth();
const observer = new ResizeObserver(updateTriggerWidth);
observer.observe(trigger);
return () => observer.disconnect();
}, [className, fullWidth, showSelectedPrice, variant, value]);
useEffect(() => {
if (!showSelectedPrice || !creditsEnabled || !quoteRequest) {
setRouteQuote(undefined);
@@ -139,7 +151,11 @@ export function ModelPicker({ config, value, onChange, capability, className, fu
ref={menuRef}
data-canvas-no-zoom
className={cn("canvas-model-picker-menu max-w-[calc(100vw-24px)]", creationVariant ? "creation-model-picker-menu w-[360px]" : "w-[var(--panel-width-compact)]")}
style={{ background: theme.node.panel, color: theme.node.text }}
style={{
background: theme.node.panel,
color: theme.node.text,
"--canvas-model-picker-trigger-width": triggerWidth ? String(triggerWidth) + "px" : undefined,
} as CSSProperties}
role="listbox"
aria-label={placeholder}
onKeyDown={handleMenuKeyDown}
@@ -209,7 +225,7 @@ export function ModelPicker({ config, value, onChange, capability, className, fu
arrow={false}
content={content}
classNames={{
root: cn("canvas-model-picker-popover", creationVariant && "creation-model-picker-popover"),
root: cn("canvas-model-picker-popover", creationVariant && "creation-model-picker-popover", popoverClassName),
container: cn("canvas-composer-popover-surface", creationVariant && "creation-model-picker-surface"),
content: "canvas-composer-popover-content",
}}