refactor(workbench): extract types, constants, utils, sub-components from WorkbenchPage
WorkbenchPage.tsx: 4146 → 3047 lines (-27%) Extracted to 6 sibling modules: - workbenchConstants.ts (403L): types, MODE_META, option arrays, shared helpers - workbenchStorage.ts (172L): localStorage read/write/persist functions - workbenchReferenceUtils.ts (210L): image compression, fingerprint, file helpers - workbenchMentionUtils.tsx (79L): prompt mention parsing and token rendering - WorkbenchPromptPreview.tsx (87L): ReferencePreview, PromptPreviewLayer components - WorkbenchSelectChips.tsx (263L): SelectChip, CompoundSelectChip, InlineOptionChip All extracted code is imported back via ES module imports — no logic changes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import { FileTextOutlined, SoundOutlined } from "@ant-design/icons";
|
||||
import type { PromptMentionItem, PromptMentionTokenRange, ReferenceItem } from "./workbenchConstants";
|
||||
import { renderPromptPreviewNodes, getPromptMentionTokenRanges } from "./workbenchMentionUtils";
|
||||
|
||||
export { getPromptMentionTokenRanges };
|
||||
|
||||
export function findPromptMentionRangeInside(index: number, ranges: PromptMentionTokenRange[]) {
|
||||
return ranges.find((range) => index > range.start && index < range.end);
|
||||
}
|
||||
|
||||
export function findPromptMentionRangeOverlap(start: number, end: number, ranges: PromptMentionTokenRange[]) {
|
||||
return ranges.find((range) => start < range.end && end > range.start);
|
||||
}
|
||||
|
||||
export function ReferenceInlinePreview({
|
||||
item,
|
||||
}: {
|
||||
item: Pick<ReferenceItem, "kind" | "name" | "previewUrl">;
|
||||
}) {
|
||||
if ((item.kind === "image" || item.kind === "video") && item.previewUrl) {
|
||||
return item.kind === "video" ? (
|
||||
<video src={item.previewUrl} muted playsInline />
|
||||
) : (
|
||||
<img src={item.previewUrl} alt={item.name} loading="lazy" />
|
||||
);
|
||||
}
|
||||
|
||||
return item.kind === "audio" ? <SoundOutlined /> : <FileTextOutlined />;
|
||||
}
|
||||
|
||||
export function ReferencePreview({
|
||||
item,
|
||||
label,
|
||||
}: {
|
||||
item: Pick<ReferenceItem, "kind" | "name" | "previewUrl">;
|
||||
label?: string;
|
||||
}) {
|
||||
if ((item.kind === "image" || item.kind === "video") && item.previewUrl) {
|
||||
return item.kind === "video" ? (
|
||||
<video src={item.previewUrl} muted playsInline />
|
||||
) : (
|
||||
<img src={item.previewUrl} alt={item.name} loading="lazy" />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="wb-composer__ref-icon">
|
||||
{item.kind === "audio" ? <SoundOutlined /> : <FileTextOutlined />}
|
||||
{label ? <span>{label}</span> : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function PromptPreviewLayer({
|
||||
text,
|
||||
items,
|
||||
onTokenPointerDown,
|
||||
}: {
|
||||
text: string;
|
||||
items: PromptMentionItem[];
|
||||
onTokenPointerDown?: (index: number) => void;
|
||||
}) {
|
||||
const nodes = renderPromptPreviewNodes(text, items);
|
||||
if (nodes.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="wb-composer__highlight"
|
||||
aria-hidden="true"
|
||||
onPointerDown={(event) => {
|
||||
const target =
|
||||
event.target instanceof Element
|
||||
? event.target.closest<HTMLElement>(".wb-composer__mention-inline-chip")
|
||||
: null;
|
||||
if (!target) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const tokenEnd = Number(target.dataset.tokenEnd);
|
||||
if (Number.isFinite(tokenEnd)) {
|
||||
onTokenPointerDown?.(tokenEnd);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{nodes}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user