import { useCallback, useMemo, useState } from "react"; import { useTheme } from "@tooee/themes"; import type { ActionDefinition } from "@tooee/commands"; import type { MarkSet } from "@tooee/marks"; import type { CodeBlockRenderer } from "@tooee/renderers"; import { DIFF_CODE_BLOCK_RENDERERS } from "@tooee/diff"; import { isCustomContent } from "./types.js"; import type { ContentProvider, ContentRenderer, MarkdownLinkActivateHandler } from "./types.js"; import { useContentLoader } from "./hooks/use-content-loader.js"; import { ViewState } from "./components/view-state.js"; import { MarkdownSubview, CodeSubview, TableSubview, ImageSubview, DiffSubview, CustomSubview, } from "./components/subviews/index.js"; import type { ReactNode } from "react"; interface ViewProps { contentProvider: ContentProvider; actions?: ActionDefinition[]; renderers?: Record; /** * Custom renderers for fenced code blocks in markdown content, keyed by * fence type (first word of the fence info string, case-insensitive). */ codeBlockRenderers?: Record; /** Handles primary-button activation of inline Markdown links. */ onMarkdownLinkActivate?: MarkdownLinkActivateHandler; } export const View = function View({ contentProvider, actions, renderers, codeBlockRenderers, onMarkdownLinkActivate, }: ViewProps): ReactNode { const { theme } = useTheme(); const { content, streaming, error, providerMarks, reload } = useContentLoader(contentProvider); const [userMarks, setUserMarks] = useState([]); const setMarkSet = useCallback((set: MarkSet) => { setUserMarks((prev) => { const filtered = prev.filter((s) => s.namespace !== set.namespace); return [...filtered, set]; }); }, []); const clearMarkNamespace = useCallback((namespace: string) => { setUserMarks((prev) => prev.filter((s) => s.namespace !== namespace)); }, []); const clearAllUserMarks = useCallback(() => { setUserMarks([]); }, []); // Mark sets are decoration layers; the controller composes them with the // interaction layers it generates, ordered by each set's own priority. const decorations = useMemo(() => [...providerMarks, ...userMarks], [providerMarks, userMarks]); // ```diff / ```patch fences render as Hunk diff blocks unless the host // registers its own renderer for those types. const mergedCodeBlockRenderers = useMemo( () => ({ ...DIFF_CODE_BLOCK_RENDERERS, ...codeBlockRenderers }), [codeBlockRenderers], ); if ((error?.length ?? 0) > 0) { return ; } if (!content) { return ; } const shared = { actions, clearAllUserMarks, clearMarkNamespace, decorations, providerMarks, reload, setMarkSet, streaming, userMarks, }; if (isCustomContent(content)) { return ; } switch (content.format) { case "markdown": { return ( ); } case "code": case "text": { return ; } case "image": { return ; } case "table": { return ; } case "diff": { return ; } default: { return null; } } };