Merge origin/master: 保留电商上传优化版,接入master新增面板组件

冲突解决:EcommercePage.tsx 保留本地版本(上传预览大图+缩略图切换交互)
master新增:6个独立面板组件、图片校验工具、视频工作区增强、CSS更新
This commit is contained in:
2026-06-05 10:34:19 +08:00
102 changed files with 9951 additions and 4345 deletions
+10 -43
View File
@@ -41,6 +41,7 @@ import { preUploadReference, resolvePreUploadedUrl } from "../../api/referenceUp
import { assetClient } from "../../api/assetClient";
import { communityClient } from "../../api/communityClient";
import { RechargeModal } from "../../components/RechargeModal/RechargeModal";
import { useGenerationTasks } from "../../hooks/useGenerationTasks";
import { conversationClient, type ConversationSummary } from "../../api/conversationClient";
import { modelCapabilitiesClient } from "../../api/modelCapabilitiesClient";
@@ -236,9 +237,9 @@ function WorkbenchPage({
const keepaliveTasksRef = useRef<Record<string, WorkbenchKeepaliveTask>>(readStoredKeepaliveTasks());
const taskAbortControllersRef = useRef<Map<string, AbortController>>(new Map());
const lastScrollTopRef = useRef(0);
const scrollActionsHideTimerRef = useRef<number | null>(null);
const shouldFollowNewMessagesRef = useRef(true);
const pendingScrollToLatestRef = useRef(true);
const genTracker = useGenerationTasks({ sourceView: "workbench" });
const renderedMessageIdsRef = useRef<string[]>([]);
const hasHandledInitialMessagesRef = useRef(false);
@@ -274,8 +275,6 @@ function WorkbenchPage({
const [promptSelectionRange, setPromptSelectionRange] = useState({ start: 0, end: 0 });
const [mentionActiveIndex, setMentionActiveIndex] = useState(0);
const [composerHidden, setComposerHidden] = useState(false);
const [scrollActionsVisible, setScrollActionsVisible] = useState(false);
const [scrollActionDirection, setScrollActionDirection] = useState<"top" | "bottom" | null>(null);
const [workspaceStarted, setWorkspaceStarted] = useState(false);
useEffect(() => {
@@ -444,27 +443,6 @@ function WorkbenchPage({
"--accent-glow": `0 0 24px rgba(${accentRgb}, 0.22)`,
} as CSSProperties;
const revealScrollActionsTemporarily = useCallback((direction: "top" | "bottom") => {
setScrollActionDirection(direction);
setScrollActionsVisible(true);
if (scrollActionsHideTimerRef.current !== null) {
window.clearTimeout(scrollActionsHideTimerRef.current);
}
scrollActionsHideTimerRef.current = window.setTimeout(() => {
setScrollActionsVisible(false);
setScrollActionDirection(null);
scrollActionsHideTimerRef.current = null;
}, 950);
}, []);
useEffect(() => {
return () => {
if (scrollActionsHideTimerRef.current !== null) {
window.clearTimeout(scrollActionsHideTimerRef.current);
}
};
}, []);
const scrollMessagesToLatest = useCallback((behavior: ScrollBehavior = "smooth") => {
const scroll = () => {
const surface = messagesSurfaceRef.current;
@@ -475,7 +453,6 @@ function WorkbenchPage({
setComposerHidden(false);
shouldFollowNewMessagesRef.current = true;
revealScrollActionsTemporarily("bottom");
surface.scrollTo({ top: surface.scrollHeight, behavior });
lastScrollTopRef.current = surface.scrollTop;
};
@@ -484,7 +461,7 @@ function WorkbenchPage({
scroll();
window.setTimeout(scroll, 80);
});
}, [revealScrollActionsTemporarily]);
}, []);
const imageSettingGroups = useMemo<WorkbenchFieldGroup[]>(
() => [
@@ -1022,11 +999,6 @@ function WorkbenchPage({
});
removeKeepaliveTask(task.taskId);
onRefreshUsage?.();
if (status.status === "completed") {
import("../../utils/generationNotifier").then((m) =>
m.notifyTaskCompleted(task.mode === "video" ? "视频" : "图片", task.mode as "image" | "video"),
);
}
return;
}
@@ -1398,9 +1370,6 @@ function WorkbenchPage({
const delta = top - lastScrollTopRef.current;
const atTop = top <= edgeThreshold;
const atBottom = top + surface.clientHeight >= surface.scrollHeight - edgeThreshold;
if (surface.scrollHeight > surface.clientHeight + edgeThreshold && Math.abs(delta) > 1) {
revealScrollActionsTemporarily(delta > 0 ? "bottom" : "top");
}
shouldFollowNewMessagesRef.current = atBottom;
if (atTop || atBottom) {
setComposerHidden(false);
@@ -1412,7 +1381,7 @@ function WorkbenchPage({
surface.addEventListener("scroll", handleScroll, { passive: true });
return () => surface.removeEventListener("scroll", handleScroll);
}, [hasActivatedWorkspace, revealScrollActionsTemporarily]);
}, [hasActivatedWorkspace]);
const scrollMessagesSurface = useCallback((direction: "top" | "bottom") => {
const surface = messagesSurfaceRef.current;
@@ -1420,9 +1389,8 @@ function WorkbenchPage({
const top = direction === "top" ? 0 : surface.scrollHeight;
setComposerHidden(false);
revealScrollActionsTemporarily(direction);
surface.scrollTo({ top, behavior: "smooth" });
}, [revealScrollActionsTemporarily]);
}, []);
const closeToolbarMenus = () => setToolbarMenuId(null);
const toggleToolbarMenu = (menuId: Exclude<ToolbarMenuId, null>) => {
@@ -1880,6 +1848,7 @@ function WorkbenchPage({
referenceUrls: refUrls.length ? refUrls : undefined,
});
taskId = result.taskId;
genTracker.submitTask({ title: trimmedPrompt.slice(0, 60), type: "image", status: "running", progress: 5, prompt: trimmedPrompt, sourceView: "workbench", taskId });
} else {
let requestModel = resolveVideoRequestModel({
model: taskInput.params?.model || ENTERPRISE_DEFAULT_VIDEO_MODEL,
@@ -1899,6 +1868,7 @@ function WorkbenchPage({
hasReferenceVideo: requestReferenceItems.some((item) => item.kind === "video"),
});
taskId = result.taskId;
genTracker.submitTask({ title: trimmedPrompt.slice(0, 60), type: "video", status: "running", progress: 5, prompt: trimmedPrompt, sourceView: "workbench", taskId });
}
onRefreshUsage?.();
@@ -3051,13 +3021,10 @@ function WorkbenchPage({
{renderComposerToolbar(false, isGenerating)}
</div>
</section>
<div
className={`wb-chat-scroll-actions${scrollActionsVisible ? " is-visible" : ""}${scrollActionDirection ? ` is-${scrollActionDirection}` : ""}`}
aria-label="聊天滚动"
>
<div className="wb-chat-scroll-actions" aria-label="聊天滚动">
<button
type="button"
className="wb-chat-scroll-actions__button wb-chat-scroll-actions__button--top"
className="wb-chat-scroll-actions__button"
title="返回聊天顶部"
aria-label="返回聊天顶部"
onClick={() => scrollMessagesSurface("top")}
@@ -3066,7 +3033,7 @@ function WorkbenchPage({
</button>
<button
type="button"
className="wb-chat-scroll-actions__button wb-chat-scroll-actions__button--bottom"
className="wb-chat-scroll-actions__button"
title="到达聊天底部"
aria-label="到达聊天底部"
onClick={() => scrollMessagesSurface("bottom")}