Files
Genarrative/src/components/image-editor/useImageCanvasEditorChrome.ts
T
kdletters 8dad52f040 实现画布Agent侧边栏聊天
新增画布Agent会话契约、领域规则、SpacetimeDB元数据表和生成绑定。
新增api-server画布Agent会话CRUD、OSS消息文档读写、SSE消息流和生成工具编排。
新增前端右侧Agent对话面板、会话状态hook、SSE客户端、BFF客户端和相关测试。
调整画布侧栏、任务栏、小地图默认状态和面板互斥交互。
补齐画布Agent、OSS消息存储、SSE收口、后端契约和项目记忆文档。
2026-07-04 12:03:58 +08:00

175 lines
5.4 KiB
TypeScript

import { useCallback, useState } from 'react';
import { ApiClientError } from '../../services/apiClient';
import { renameEditorProject } from '../../services/image-editor/editorProjectClient';
import {
DEFAULT_CANVAS_BACKGROUND_COLOR,
normalizeCanvasBackgroundHex,
} from './ImageCanvasEditorModel';
import type { CanvasTool, SidebarPanel } from './ImageCanvasEditorTypes';
type UseImageCanvasEditorChromeOptions = {
openEditorLoginModal: (postLoginAction?: (() => void) | null) => void;
};
function isEditorAuthError(error: unknown) {
return (
error instanceof ApiClientError &&
(error.status === 401 || error.status === 403)
);
}
export function useImageCanvasEditorChrome({
openEditorLoginModal,
}: UseImageCanvasEditorChromeOptions) {
const [projectTitle, setProjectTitle] = useState('未命名画布');
const [projectRenameValue, setProjectRenameValue] = useState('未命名画布');
const [isRenamingProject, setIsRenamingProject] = useState(false);
const [isProjectRenameSaving, setIsProjectRenameSaving] = useState(false);
const [projectRenameError, setProjectRenameError] = useState<string | null>(
null,
);
const [activeSidebarPanel, setActiveSidebarPanel] =
useState<SidebarPanel | null>(null);
const [activeTool, setActiveTool] = useState<CanvasTool>('select');
const [isZoomMenuOpen, setIsZoomMenuOpen] = useState(false);
const [isBackgroundSettingsOpen, setIsBackgroundSettingsOpen] =
useState(false);
const [isMinimapOpen, setIsMinimapOpen] = useState(false);
const [canvasBackgroundColor, setCanvasBackgroundColor] = useState(
DEFAULT_CANVAS_BACKGROUND_COLOR,
);
const [canvasBackgroundHexValue, setCanvasBackgroundHexValue] = useState(
DEFAULT_CANVAS_BACKGROUND_COLOR,
);
const applyCanvasBackgroundColor = useCallback((color: string) => {
const normalizedColor = normalizeCanvasBackgroundHex(color);
if (!normalizedColor) {
return false;
}
setCanvasBackgroundColor(normalizedColor);
setCanvasBackgroundHexValue(normalizedColor);
return true;
}, []);
const handleCanvasBackgroundHexChange = useCallback((nextValue: string) => {
setCanvasBackgroundHexValue(nextValue);
const normalizedColor = normalizeCanvasBackgroundHex(nextValue);
if (normalizedColor) {
setCanvasBackgroundColor(normalizedColor);
setCanvasBackgroundHexValue(normalizedColor);
}
}, []);
const startProjectRename = useCallback(() => {
setProjectRenameValue(projectTitle);
setProjectRenameError(null);
setIsRenamingProject(true);
}, [projectTitle]);
const cancelProjectRename = useCallback(() => {
setProjectRenameValue(projectTitle);
setProjectRenameError(null);
setIsRenamingProject(false);
}, [projectTitle]);
const submitProjectRename = useCallback(
(projectId: string | null) => {
const nextTitle = projectRenameValue.trim();
if (!nextTitle) {
setProjectRenameError('项目名称不能为空');
return;
}
if (!projectId || nextTitle === projectTitle) {
setProjectRenameValue(projectTitle);
setProjectRenameError(null);
setIsRenamingProject(false);
return;
}
setIsProjectRenameSaving(true);
setProjectRenameError(null);
renameEditorProject(projectId, nextTitle)
.then((project: Awaited<ReturnType<typeof renameEditorProject>>) => {
const savedTitle = project.title?.trim() || nextTitle;
setProjectTitle(savedTitle);
setProjectRenameValue(savedTitle);
setIsRenamingProject(false);
})
.catch((error: unknown) => {
if (isEditorAuthError(error)) {
openEditorLoginModal();
}
setProjectRenameError(
error instanceof Error ? error.message : '重命名项目失败',
);
})
.finally(() => setIsProjectRenameSaving(false));
},
[openEditorLoginModal, projectRenameValue, projectTitle],
);
const resetProjectRenameError = useCallback(() => {
setProjectRenameError(null);
}, []);
const closeEditorChromePanels = useCallback(() => {
setActiveSidebarPanel(null);
setIsZoomMenuOpen(false);
setIsBackgroundSettingsOpen(false);
}, []);
const toggleSidebarPanel = useCallback((panel: SidebarPanel) => {
setActiveSidebarPanel((currentPanel) =>
currentPanel === panel ? null : panel,
);
}, []);
const toggleZoomMenu = useCallback(() => {
setIsZoomMenuOpen((open) => !open);
}, []);
const closeZoomMenu = useCallback(() => {
setIsZoomMenuOpen(false);
}, []);
const toggleBackgroundSettings = useCallback(() => {
setIsBackgroundSettingsOpen((isOpen) => !isOpen);
}, []);
const toggleMinimap = useCallback(() => {
setIsMinimapOpen((open) => !open);
}, []);
return {
projectTitle,
setProjectTitle,
projectRenameValue,
setProjectRenameValue,
isRenamingProject,
isProjectRenameSaving,
projectRenameError,
activeSidebarPanel,
setActiveSidebarPanel,
activeTool,
setActiveTool,
isZoomMenuOpen,
isBackgroundSettingsOpen,
isMinimapOpen,
canvasBackgroundColor,
canvasBackgroundHexValue,
startProjectRename,
cancelProjectRename,
submitProjectRename,
resetProjectRenameError,
applyCanvasBackgroundColor,
handleCanvasBackgroundHexChange,
closeEditorChromePanels,
toggleSidebarPanel,
toggleZoomMenu,
closeZoomMenu,
toggleBackgroundSettings,
toggleMinimap,
};
}