mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
Pdf comment agent (#6196)
Co-authored-by: James Brunton <[email protected]>
This commit is contained in:
co-authored by
James Brunton
parent
2dc5276e8b
commit
86774d556e
@@ -1791,6 +1791,22 @@ bullet3 = "Keeps the original name if no suitable title is found"
|
||||
text = "Automatically finds the title from your PDF content and uses it as the filename."
|
||||
title = "Smart Renaming"
|
||||
|
||||
[pdfCommentAgent]
|
||||
submit = "Generate comments"
|
||||
prompt.label = "What should the AI comment on?"
|
||||
prompt.placeholder = "e.g. Flag any ambiguous dates and suggest clarifications"
|
||||
|
||||
[pdfCommentAgent.settings]
|
||||
title = "Comment instructions"
|
||||
|
||||
[pdfCommentAgent.results]
|
||||
title = "Commented PDF"
|
||||
|
||||
[pdfCommentAgent.error]
|
||||
failed = "Failed to generate comments"
|
||||
emptyPrompt = "Please describe what the AI should comment on"
|
||||
tooLong = "Prompt is too long (maximum {{max}} characters)"
|
||||
|
||||
[automate]
|
||||
copyToSaved = "Copy to Saved"
|
||||
desc = "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
|
||||
@@ -4052,6 +4068,11 @@ desc = "Auto renames a PDF file based on its detected header"
|
||||
tags = "auto-detect,header-based,organize,relabel,auto rename,automatic rename,smart rename,rename by content,filename,file naming,detect title"
|
||||
title = "Auto Rename PDF File"
|
||||
|
||||
[home.pdfCommentAgent]
|
||||
desc = "Ask AI to annotate a PDF with sticky-note comments based on your prompt"
|
||||
tags = "AI,agent,comment,annotate,sticky note,review,feedback,notes"
|
||||
title = "Add AI comments"
|
||||
|
||||
[home.autoSizeSplitPDF]
|
||||
desc = "Automatically split PDFs by file size or page count"
|
||||
tags = "auto,split,size"
|
||||
|
||||
@@ -22,6 +22,7 @@ import BuildRoundedIcon from "@mui/icons-material/BuildRounded";
|
||||
import TuneRoundedIcon from "@mui/icons-material/TuneRounded";
|
||||
import CodeRoundedIcon from "@mui/icons-material/CodeRounded";
|
||||
import { ProprietaryToolId } from "@app/types/proprietaryToolId";
|
||||
import { PrototypeToolId } from "@app/types/prototypeToolId";
|
||||
|
||||
export enum SubcategoryId {
|
||||
SIGNING = "signing",
|
||||
@@ -79,6 +80,7 @@ export type ProprietaryToolRegistry = Record<
|
||||
ProprietaryToolId,
|
||||
ToolRegistryEntry
|
||||
>;
|
||||
export type PrototypeToolRegistry = Record<PrototypeToolId, ToolRegistryEntry>;
|
||||
|
||||
export const SUBCATEGORY_ORDER: SubcategoryId[] = [
|
||||
SubcategoryId.SIGNING,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useMemo } from "react";
|
||||
import { type PrototypeToolRegistry } from "@app/data/toolsTaxonomy";
|
||||
|
||||
/**
|
||||
* Prototype tool registry extension.
|
||||
* This file is overridden in src/prototypes/data/usePrototypeToolRegistry.tsx
|
||||
* to add experimental tools that only ship in the prototypes build.
|
||||
*
|
||||
* No tools should be defined in this file.
|
||||
*/
|
||||
|
||||
// Empty hook that returns an empty registry (overridden in the prototypes overlay).
|
||||
export function usePrototypeToolRegistry(): PrototypeToolRegistry {
|
||||
return useMemo(() => ({}) as PrototypeToolRegistry, []);
|
||||
}
|
||||
@@ -75,6 +75,7 @@ import { bookletImpositionOperationConfig } from "@app/hooks/tools/bookletImposi
|
||||
import { mergeOperationConfig } from "@app/hooks/tools/merge/useMergeOperation";
|
||||
import { editTableOfContentsOperationConfig } from "@app/hooks/tools/editTableOfContents/useEditTableOfContentsOperation";
|
||||
import { autoRenameOperationConfig } from "@app/hooks/tools/autoRename/useAutoRenameOperation";
|
||||
import { usePrototypeToolRegistry } from "@app/data/usePrototypeToolRegistry";
|
||||
import { flattenOperationConfig } from "@app/hooks/tools/flatten/useFlattenOperation";
|
||||
import { redactOperationConfig } from "@app/hooks/tools/redact/useRedactOperation";
|
||||
import { rotateOperationConfig } from "@app/hooks/tools/rotate/useRotateOperation";
|
||||
@@ -149,11 +150,16 @@ export interface TranslatedToolCatalog {
|
||||
export function useTranslatedToolCatalog(): TranslatedToolCatalog {
|
||||
const { t } = useTranslation();
|
||||
const proprietaryTools = useProprietaryToolRegistry();
|
||||
const prototypeTools = usePrototypeToolRegistry();
|
||||
|
||||
return useMemo(() => {
|
||||
const allTools: ToolRegistry = {
|
||||
// Proprietary tools (if any)
|
||||
...proprietaryTools,
|
||||
// Prototype-only tools (empty in the main/core/proprietary/saas/desktop
|
||||
// builds; the prototypes build overlay injects experimental tools here
|
||||
// via src/prototypes/data/usePrototypeToolRegistry.tsx).
|
||||
...prototypeTools,
|
||||
// Recommended Tools in order
|
||||
pdfTextEditor: {
|
||||
icon: (
|
||||
@@ -985,7 +991,6 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
|
||||
synonyms: getSynonyms(t, "autoRename"),
|
||||
automationSettings: null,
|
||||
},
|
||||
|
||||
// Advanced Formatting
|
||||
|
||||
adjustContrast: {
|
||||
@@ -1357,5 +1362,5 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
|
||||
superTools,
|
||||
linkTools,
|
||||
};
|
||||
}, [t, proprietaryTools]); // Re-compute when translations or proprietary tools change
|
||||
}, [t, proprietaryTools, prototypeTools]); // Re-compute when translations, proprietary, or prototype tools change
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Prototype tool ID definitions.
|
||||
* This file is overridden in src/prototypes/types/prototypeToolId.ts
|
||||
* to add experimental tool IDs that only ship in the prototypes build.
|
||||
*
|
||||
* No tool IDs should be defined in this file.
|
||||
*/
|
||||
|
||||
export const PROTOTYPE_REGULAR_TOOL_IDS = [] as const;
|
||||
export const PROTOTYPE_SUPER_TOOL_IDS = [] as const;
|
||||
export const PROTOTYPE_LINK_TOOL_IDS = [] as const;
|
||||
|
||||
export type PrototypeRegularToolId =
|
||||
(typeof PROTOTYPE_REGULAR_TOOL_IDS)[number];
|
||||
export type PrototypeSuperToolId = (typeof PROTOTYPE_SUPER_TOOL_IDS)[number];
|
||||
export type PrototypeLinkToolId = (typeof PROTOTYPE_LINK_TOOL_IDS)[number];
|
||||
export type PrototypeToolId =
|
||||
| PrototypeRegularToolId
|
||||
| PrototypeSuperToolId
|
||||
| PrototypeLinkToolId;
|
||||
@@ -3,6 +3,11 @@ import {
|
||||
PROPRIETARY_SUPER_TOOL_IDS,
|
||||
PROPRIETARY_LINK_TOOL_IDS,
|
||||
} from "@app/types/proprietaryToolId";
|
||||
import {
|
||||
PROTOTYPE_REGULAR_TOOL_IDS,
|
||||
PROTOTYPE_SUPER_TOOL_IDS,
|
||||
PROTOTYPE_LINK_TOOL_IDS,
|
||||
} from "@app/types/prototypeToolId";
|
||||
|
||||
export type ToolKind = "regular" | "super" | "link";
|
||||
|
||||
@@ -72,16 +77,19 @@ export const CORE_LINK_TOOL_IDS = [
|
||||
export const REGULAR_TOOL_IDS = [
|
||||
...CORE_REGULAR_TOOL_IDS,
|
||||
...PROPRIETARY_REGULAR_TOOL_IDS,
|
||||
...PROTOTYPE_REGULAR_TOOL_IDS,
|
||||
] as const;
|
||||
|
||||
export const SUPER_TOOL_IDS = [
|
||||
...CORE_SUPER_TOOL_IDS,
|
||||
...PROPRIETARY_SUPER_TOOL_IDS,
|
||||
...PROTOTYPE_SUPER_TOOL_IDS,
|
||||
] as const;
|
||||
|
||||
export const LINK_TOOL_IDS = [
|
||||
...CORE_LINK_TOOL_IDS,
|
||||
...PROPRIETARY_LINK_TOOL_IDS,
|
||||
...PROTOTYPE_LINK_TOOL_IDS,
|
||||
] as const;
|
||||
|
||||
export const TOOL_IDS = [
|
||||
|
||||
@@ -10,10 +10,15 @@ export const CREDIT_COSTS = {
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Mapping of tool IDs to their credit costs
|
||||
* Based on backend ResourceWeight annotations
|
||||
* Mapping of tool IDs to their credit costs.
|
||||
* Based on backend ResourceWeight annotations.
|
||||
*
|
||||
* Typed as {@code Partial} so overlays can contribute per-build-only tool ids
|
||||
* (e.g. experimental tools in the prototypes build) without every overlay
|
||||
* needing to know every other overlay's ids. Unknown ids fall back to
|
||||
* {@link CREDIT_COSTS.MEDIUM} in {@link getToolCreditCost}.
|
||||
*/
|
||||
export const TOOL_CREDIT_COSTS: Record<ToolId, number> = {
|
||||
export const TOOL_CREDIT_COSTS: Partial<Record<ToolId, number>> = {
|
||||
// No cost operations (0 credits)
|
||||
showJS: CREDIT_COSTS.NONE,
|
||||
devApi: CREDIT_COSTS.NONE,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
import {
|
||||
SubcategoryId,
|
||||
ToolCategoryId,
|
||||
type PrototypeToolRegistry,
|
||||
} from "@app/data/toolsTaxonomy";
|
||||
import { pdfCommentAgentOperationConfig } from "@app/hooks/tools/pdfCommentAgent/pdfCommentAgentOperationConfig";
|
||||
import PdfCommentAgent from "@app/tools/PdfCommentAgent";
|
||||
import { getSynonyms } from "@app/utils/toolSynonyms";
|
||||
|
||||
/**
|
||||
* Prototype tool registry extension — real implementation.
|
||||
*
|
||||
* Overrides the empty stub at {@code core/data/usePrototypeToolRegistry.tsx}
|
||||
* when the build resolves {@code @app/*} through {@code src/prototypes/*}.
|
||||
* Experimental AI tools live here until they graduate to core / proprietary.
|
||||
*/
|
||||
export function usePrototypeToolRegistry(): PrototypeToolRegistry {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
({
|
||||
pdfCommentAgent: {
|
||||
icon: <LocalIcon icon="add-comment" width="1.5rem" height="1.5rem" />,
|
||||
name: t("home.pdfCommentAgent.title", "Add AI comments"),
|
||||
component: PdfCommentAgent,
|
||||
description: t(
|
||||
"home.pdfCommentAgent.desc",
|
||||
"Ask AI to annotate a PDF with sticky-note comments based on your prompt",
|
||||
),
|
||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||
subcategoryId: SubcategoryId.DOCUMENT_REVIEW,
|
||||
maxFiles: 1,
|
||||
endpoints: ["pdf-comment-agent"],
|
||||
operationConfig: pdfCommentAgentOperationConfig,
|
||||
automationSettings: null,
|
||||
synonyms: getSynonyms(t, "pdfCommentAgent"),
|
||||
versionStatus: "beta",
|
||||
},
|
||||
}) as PrototypeToolRegistry,
|
||||
[t],
|
||||
);
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
import apiClient from "@app/services/apiClient";
|
||||
import {
|
||||
ToolType,
|
||||
CustomToolOperationConfig,
|
||||
CustomProcessorResult,
|
||||
} from "@app/hooks/tools/shared/toolOperationTypes";
|
||||
import {
|
||||
PdfCommentAgentParameters,
|
||||
defaultParameters,
|
||||
} from "@app/hooks/tools/pdfCommentAgent/usePdfCommentAgentParameters";
|
||||
|
||||
export const PDF_COMMENT_AGENT_ENDPOINT = "/api/v1/ai/tools/pdf-comment-agent";
|
||||
|
||||
/** Build the multipart payload Java expects: fileInput + prompt. */
|
||||
export const buildPdfCommentAgentFormData = (
|
||||
parameters: PdfCommentAgentParameters,
|
||||
file: File,
|
||||
): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file);
|
||||
formData.append("prompt", parameters.prompt);
|
||||
return formData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reject filenames that are blank or contain path separators. The server is
|
||||
* trusted to supply a sensible value, but guarding here means a hostile or
|
||||
* buggy backend cannot convince the browser save-dialog to steer the download
|
||||
* into a parent directory.
|
||||
*/
|
||||
const sanitiseFilename = (raw: string): string | null => {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return null;
|
||||
if (/[\\/]/.test(trimmed)) return null;
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract the filename from a Content-Disposition header, falling back to a
|
||||
* sensible default based on the input file name. Handles both the quoted and
|
||||
* RFC 5987 (``filename*=UTF-8''encoded``) forms.
|
||||
*/
|
||||
const filenameFromContentDisposition = (
|
||||
header: string | undefined,
|
||||
inputName: string,
|
||||
): string => {
|
||||
const fallback = inputName.replace(/\.pdf$/i, "") + "-commented.pdf";
|
||||
if (!header) return fallback;
|
||||
|
||||
// RFC 5987: filename*=UTF-8''encoded
|
||||
const extended = /filename\*=[^']*''([^;]+)/i.exec(header);
|
||||
if (extended?.[1]) {
|
||||
try {
|
||||
const decoded = sanitiseFilename(decodeURIComponent(extended[1]));
|
||||
if (decoded) return decoded;
|
||||
} catch {
|
||||
// fall through to plain form
|
||||
}
|
||||
}
|
||||
|
||||
// Plain: filename="..." or filename=...
|
||||
const plain = /filename="?([^";]+)"?/i.exec(header);
|
||||
if (plain?.[1]) {
|
||||
const sanitised = sanitiseFilename(plain[1]);
|
||||
if (sanitised) return sanitised;
|
||||
}
|
||||
return fallback;
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom processor: POST the PDF + prompt to the Java AI endpoint, which streams
|
||||
* the annotated PDF back. Wrap the response Blob as a File so the standard
|
||||
* review panel (embedPDF viewer) renders it with the sticky-note annotations.
|
||||
*/
|
||||
const processPdfCommentAgent = async (
|
||||
parameters: PdfCommentAgentParameters,
|
||||
files: File[],
|
||||
): Promise<CustomProcessorResult> => {
|
||||
if (files.length === 0) {
|
||||
return { files: [] };
|
||||
}
|
||||
|
||||
const [inputFile] = files;
|
||||
const formData = buildPdfCommentAgentFormData(parameters, inputFile);
|
||||
|
||||
const response = await apiClient.post<Blob>(
|
||||
PDF_COMMENT_AGENT_ENDPOINT,
|
||||
formData,
|
||||
{ responseType: "blob" },
|
||||
);
|
||||
|
||||
const dispositionHeader =
|
||||
(response.headers as Record<string, string | undefined>)[
|
||||
"content-disposition"
|
||||
] ?? undefined;
|
||||
const fileName = filenameFromContentDisposition(
|
||||
dispositionHeader,
|
||||
inputFile.name,
|
||||
);
|
||||
|
||||
const resultFile = new File([response.data], fileName, {
|
||||
type: response.data.type || "application/pdf",
|
||||
});
|
||||
return { files: [resultFile] };
|
||||
};
|
||||
|
||||
export const pdfCommentAgentOperationConfig = {
|
||||
toolType: ToolType.custom,
|
||||
operationType: "pdfCommentAgent",
|
||||
endpoint: PDF_COMMENT_AGENT_ENDPOINT,
|
||||
customProcessor: processPdfCommentAgent,
|
||||
defaultParameters,
|
||||
} as const satisfies CustomToolOperationConfig<PdfCommentAgentParameters>;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useToolOperation } from "@app/hooks/tools/shared/useToolOperation";
|
||||
import { createStandardErrorHandler } from "@app/utils/toolErrorHandler";
|
||||
import { pdfCommentAgentOperationConfig } from "@app/hooks/tools/pdfCommentAgent/pdfCommentAgentOperationConfig";
|
||||
|
||||
export const usePdfCommentAgentOperation = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useToolOperation({
|
||||
...pdfCommentAgentOperationConfig,
|
||||
getErrorMessage: createStandardErrorHandler(
|
||||
t("pdfCommentAgent.error.failed", "Failed to generate comments"),
|
||||
),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { BaseParameters } from "@app/types/parameters";
|
||||
import {
|
||||
useBaseParameters,
|
||||
BaseParametersHook,
|
||||
} from "@app/hooks/tools/shared/useBaseParameters";
|
||||
|
||||
export interface PdfCommentAgentParameters extends BaseParameters {
|
||||
/** Natural-language instructions for what the AI should comment on. */
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
export const MAX_PROMPT_LENGTH = 4000;
|
||||
|
||||
export const defaultParameters: PdfCommentAgentParameters = {
|
||||
prompt: "",
|
||||
};
|
||||
|
||||
export type PdfCommentAgentParametersHook =
|
||||
BaseParametersHook<PdfCommentAgentParameters>;
|
||||
|
||||
export const usePdfCommentAgentParameters =
|
||||
(): PdfCommentAgentParametersHook => {
|
||||
return useBaseParameters({
|
||||
defaultParameters,
|
||||
endpointName: "pdf-comment-agent",
|
||||
validateFn: (params) => {
|
||||
const trimmed = params.prompt.trim();
|
||||
return trimmed.length > 0 && params.prompt.length <= MAX_PROMPT_LENGTH;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
import type { ChangeEvent } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Stack, Textarea } from "@mantine/core";
|
||||
import { createToolFlow } from "@app/components/tools/shared/createToolFlow";
|
||||
import { useBaseTool } from "@app/hooks/tools/shared/useBaseTool";
|
||||
import type { BaseToolProps } from "@app/types/tool";
|
||||
|
||||
import {
|
||||
MAX_PROMPT_LENGTH,
|
||||
usePdfCommentAgentParameters,
|
||||
} from "@app/hooks/tools/pdfCommentAgent/usePdfCommentAgentParameters";
|
||||
import { usePdfCommentAgentOperation } from "@app/hooks/tools/pdfCommentAgent/usePdfCommentAgentOperation";
|
||||
|
||||
const PdfCommentAgent = (props: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const base = useBaseTool(
|
||||
"pdf-comment-agent",
|
||||
usePdfCommentAgentParameters,
|
||||
usePdfCommentAgentOperation,
|
||||
props,
|
||||
);
|
||||
|
||||
const handlePromptChange = useCallback(
|
||||
(event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
base.params.updateParameter("prompt", event.currentTarget.value);
|
||||
},
|
||||
[base.params],
|
||||
);
|
||||
|
||||
// Inline validation error shown under the Textarea. Only rendered once the
|
||||
// user has typed something (or over-typed) — we don't want to yell about an
|
||||
// empty field that the user hasn't interacted with yet.
|
||||
const prompt = base.params.parameters.prompt;
|
||||
const trimmedLength = prompt.trim().length;
|
||||
let promptError: string | null = null;
|
||||
if (prompt.length > MAX_PROMPT_LENGTH) {
|
||||
promptError = t("pdfCommentAgent.error.tooLong", {
|
||||
max: MAX_PROMPT_LENGTH,
|
||||
defaultValue: `Prompt is too long (maximum ${MAX_PROMPT_LENGTH} characters)`,
|
||||
});
|
||||
} else if (prompt.length > 0 && trimmedLength === 0) {
|
||||
// User typed only whitespace — treat as empty with the empty-prompt message.
|
||||
promptError = t(
|
||||
"pdfCommentAgent.error.emptyPrompt",
|
||||
"Please describe what the AI should comment on",
|
||||
);
|
||||
}
|
||||
|
||||
return createToolFlow({
|
||||
files: {
|
||||
selectedFiles: base.selectedFiles,
|
||||
isCollapsed: base.hasResults,
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
title: t("pdfCommentAgent.settings.title", "Comment instructions"),
|
||||
isCollapsed: false,
|
||||
content: (
|
||||
<Stack gap="sm">
|
||||
<Textarea
|
||||
label={t(
|
||||
"pdfCommentAgent.prompt.label",
|
||||
"What should the AI comment on?",
|
||||
)}
|
||||
placeholder={t(
|
||||
"pdfCommentAgent.prompt.placeholder",
|
||||
"e.g. Flag any ambiguous dates and suggest clarifications",
|
||||
)}
|
||||
value={prompt}
|
||||
onChange={handlePromptChange}
|
||||
minRows={4}
|
||||
autosize
|
||||
maxLength={MAX_PROMPT_LENGTH}
|
||||
error={promptError}
|
||||
/>
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
],
|
||||
executeButton: {
|
||||
text: t("pdfCommentAgent.submit", "Generate comments"),
|
||||
isVisible: !base.hasResults,
|
||||
loadingText: t("loading"),
|
||||
onClick: base.handleExecute,
|
||||
endpointEnabled: base.endpointEnabled,
|
||||
paramsValid: base.params.validateParameters(),
|
||||
},
|
||||
review: {
|
||||
isVisible: base.hasResults,
|
||||
operation: base.operation,
|
||||
title: t("pdfCommentAgent.results.title", "Commented PDF"),
|
||||
onFileClick: base.handleThumbnailClick,
|
||||
onUndo: base.handleUndo,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default PdfCommentAgent;
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Prototype tool ID definitions — real implementation.
|
||||
*
|
||||
* Overrides the empty stub at {@code core/types/prototypeToolId.ts} when
|
||||
* built in prototypes mode. Tools listed here are only reachable from the
|
||||
* prototypes build (and any build whose {@code @app/*} alias chain reaches
|
||||
* {@code src/prototypes/*}) — they are invisible to the core / proprietary
|
||||
* / saas / desktop bundles.
|
||||
*
|
||||
* Add an id here when the accompanying tool file lives under
|
||||
* {@code frontend/src/prototypes/tools/...} and you want it surfaced in the
|
||||
* prototypes build's tool picker.
|
||||
*/
|
||||
|
||||
export const PROTOTYPE_REGULAR_TOOL_IDS = ["pdfCommentAgent"] as const;
|
||||
// "ai-workflow" is a generic marker stamped onto files produced by the AI
|
||||
// orchestrator (which may invoke one or more underlying tools). Lives here as
|
||||
// a super-tool so ``ToolOperation.toolId`` stays typed; not user-pickable —
|
||||
// see ChatContext.tsx. The prototype tool registry doesn't include it as an
|
||||
// entry, which is fine since the registry uses an ``as`` cast.
|
||||
export const PROTOTYPE_SUPER_TOOL_IDS = ["ai-workflow"] as const;
|
||||
export const PROTOTYPE_LINK_TOOL_IDS = [] as const;
|
||||
|
||||
export type PrototypeRegularToolId =
|
||||
(typeof PROTOTYPE_REGULAR_TOOL_IDS)[number];
|
||||
export type PrototypeSuperToolId = (typeof PROTOTYPE_SUPER_TOOL_IDS)[number];
|
||||
export type PrototypeLinkToolId = (typeof PROTOTYPE_LINK_TOOL_IDS)[number];
|
||||
export type PrototypeToolId =
|
||||
| PrototypeRegularToolId
|
||||
| PrototypeSuperToolId
|
||||
| PrototypeLinkToolId;
|
||||
Reference in New Issue
Block a user