From cffd1d798168f714e3e13b28c4ff2f4eed775d7a Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Mon, 27 Jul 2026 21:56:00 +0200 Subject: [PATCH 1/2] refactor(ai): remove the InfiniteAIAssistant command type It shipped in 1.2.0 and was never creatable in any released version. Across all 202 tags the identifier only ever touched CommandType, the interface, the modal (+test), main.ts, packagePreview, MacroDisclosure, modelRefPinning, refreshStaleDefaultModelSeeds and one test - never a builder, an engine or an API file. The only add-AI path hardcodes CommandType.AIAssistant. Chunked prompting shipped instead as quickAddApi.ai.chunkedPrompt(), which is a strict superset: documented, tested, auto-splitting on provider rejection. Wiring it up was the alternative, and it is not a missing `if`: IInfiniteAIAssistantCommand has no promptTemplate and no text source, and ChunkedPrompt throws without a chunk reference. It would mean designing where the text comes from plus a template picker - a new feature with zero recorded demand, competing with a superset. If chunking ever returns to the builder, the right shape is a "chunk long input" toggle on the existing AI Assistant command reusing its promptTemplate, not a second command type. Removed: the enum member, IInfiniteAIAssistantCommand, the 268-line modal and its test, the systemPromptFields entry, the devMode testQuickAdd command (whose entire body constructed that modal) and its label, the packagePreview case, the MacroDisclosure label, the isAICommand clauses in modelRefPinning and refreshStaleDefaultModelSeeds, and the orphaned aiHelpers pair getMaxChunkTokensUpperBound / getLargestModelMaxTokens with their test, FALLBACK_MODEL_MAX_TOKENS and the estimateModelInputBudget import they were the only local users of. No data migration. Converting to a plain AIAssistant is not inert - with promptTemplate.enable false it opens a template picker and fires a billable request, or throws under disableOnlineFeatures (the default). Dropping the command deletes user data to fix a hazard nobody demonstrably has. And chunkSeparator is a string here but a RegExp in chunkedPrompt, so a stored "\n" is a literal backslash-n that no conversion could carry faithfully. The dispatch loop covers the hazard permanently and generically instead. The engine keeps telling the truth about the type after the enum member is gone: RETIRED_COMMAND_TYPES holds the message, so a data.json still carrying one gets "never runnable, now removed, use chunkedPrompt" rather than the generic "can come from a newer version of QuickAdd", which would be the exact opposite of what happened. Docs: the anchor-pinned "Max chunk tokens" section sat under AIAssistant's "Model settings and token budgets" next to real settings, but the only control that ever rendered it was the slider inside the unreachable modal. It moves to the chunkedPrompt API docs, reframed as the API option it actually is. The frozen anchor is preserved on both pages - the alias-anchor pattern this docs tree already uses - so /docs/AIAssistant#max-chunk-tokens still lands on a pointer to the new home. AGENTS.md pointed agents at the deleted quickadd:testQuickAdd command; it now points at quickadd:run and eval. --- AGENTS.md | 7 +- docs/src/content/docs/docs/AIAssistant.md | 10 +- docs/src/content/docs/docs/QuickAddAPI.md | 13 +- .../AIAssistant.systemPromptLiteral.test.ts | 5 +- src/ai/aiHelpers.maxChunkTokens.test.ts | 93 ------ src/ai/aiHelpers.ts | 42 --- src/ai/modelRefPinning.ts | 5 +- src/commandLabels.ts | 1 - src/engine/MacroChoiceEngine.ts | 42 +-- .../MacroChoiceEngine.unknownCommand.test.ts | 9 +- ...istantInfiniteCommandSettingsModal.test.ts | 114 -------- ...AIAssistantInfiniteCommandSettingsModal.ts | 268 ------------------ src/gui/PackageManager/MacroDisclosure.svelte | 1 - src/gui/ai/systemPromptFields.test.ts | 29 +- src/gui/ai/systemPromptLiteralNote.ts | 2 +- src/main.commandLabels.test.ts | 1 - src/main.ts | 32 --- .../refreshStaleDefaultModelSeeds.ts | 5 +- src/services/packagePreview.test.ts | 28 ++ src/services/packagePreview.ts | 3 +- src/types/macros/CommandType.ts | 1 - .../QuickCommands/IAIAssistantCommand.ts | 8 - 22 files changed, 90 insertions(+), 629 deletions(-) delete mode 100644 src/ai/aiHelpers.maxChunkTokens.test.ts delete mode 100644 src/gui/MacroGUIs/AIAssistantInfiniteCommandSettingsModal.test.ts delete mode 100644 src/gui/MacroGUIs/AIAssistantInfiniteCommandSettingsModal.ts diff --git a/AGENTS.md b/AGENTS.md index af788f888..7a0944cf4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,9 +48,10 @@ instance teardown, and the dev-tools loop. This section is the QuickAdd-specific brief that a skill-less agent still needs. - Plugin id `quickadd`. Reload with `obsidian vault= plugin:reload - id=quickadd`; the runner's ready probe is `quickadd:list`. Trigger the test - action with `obsidian vault= command id=quickadd:testQuickAdd` (or via - the runner: `pnpm run obsidian:e2e -- command id=quickadd:testQuickAdd`). + id=quickadd`; the runner's ready probe is `quickadd:list`. Run a choice with + `pnpm run obsidian:e2e -- quickadd:run choice=`, and drive anything else + through `pnpm run obsidian:e2e -- eval code=` (an expression, not + statements; wrap async work in an IIFE). - The four runner scripts - `provision:e2e-vault`, `start:e2e-obsidian`, `stop:e2e-obsidian`, `obsidian:e2e` - run the shared `obsidian-e2e` bin, configured by `obsidian-e2e.config.mjs` at the repo root (plugin id, the diff --git a/docs/src/content/docs/docs/AIAssistant.md b/docs/src/content/docs/docs/AIAssistant.md index ef0d07ee6..94cbf4b81 100644 --- a/docs/src/content/docs/docs/AIAssistant.md +++ b/docs/src/content/docs/docs/AIAssistant.md @@ -200,14 +200,10 @@ Use these rules when choosing a value: - For a local model, use the context window configured for that local model. - If you do not know the value, import models from the provider if possible, or use the provider's model documentation. -### Max chunk tokens {#max-chunk-tokens} + -The chunked AI prompt flow has a separate **Max chunk tokens** setting. It -controls the estimated token budget for the text inserted into `{{VALUE:chunk}}` -for each chunk. - -The system prompt and prompt template are counted separately. Values above the -selected model's estimated input budget are capped automatically. +Chunk sizing has no setting of its own. It is the `maxChunkTokens` option of +[`chunkedPrompt()`](/docs/QuickAddAPI/#max-chunk-tokens) in the script API. ### Output length {#output-length} diff --git a/docs/src/content/docs/docs/QuickAddAPI.md b/docs/src/content/docs/docs/QuickAddAPI.md index 8e91917b9..34f9919e6 100644 --- a/docs/src/content/docs/docs/QuickAddAPI.md +++ b/docs/src/content/docs/docs/QuickAddAPI.md @@ -617,7 +617,7 @@ results. Use this for inputs that are too large for a single request. - `chunkSeparator`: `RegExp` used to split `text` (default: `/\n/`) - `chunkJoiner`: String inserted between chunk results (default: `"\n"`) - `shouldMerge`: Merge small adjacent chunks up to the budget (default: `true`) - - `maxChunkTokens`: Maximum **estimated** tokens for each chunk's text (the `{{VALUE:chunk}}` portion only - the system prompt and prompt template are budgeted separately). Token counts are estimated locally; values above the model's estimated input budget are capped automatically. + - `maxChunkTokens`: Maximum **estimated** tokens for each chunk's text - see [Max chunk tokens](#max-chunk-tokens) below. **Behavior:** - Chunk sizes are estimated locally (QuickAdd no longer bundles model-specific tokenizers); the configured provider remains the source of truth for exact limits. @@ -636,6 +636,17 @@ const result = await quickAddApi.ai.chunkedPrompt( ); ``` +#### Max chunk tokens {#max-chunk-tokens} + +`maxChunkTokens` is the estimated token budget for the text inserted into +`{{VALUE:chunk}}` for each chunk. It is an option of this API call, not a +QuickAdd setting - there is no **Max chunk tokens** field anywhere in the +plugin's settings or in the Macro AI Assistant command. + +The system prompt and prompt template are counted separately. Token counts are +estimated locally, and values above the selected model's estimated input budget +are capped automatically. + ### Give the model tools to call: `ai.agent(config)` {#tool--function-calling--aiagentconfig} _Introduced in QuickAdd 2.14.0._ diff --git a/src/ai/AIAssistant.systemPromptLiteral.test.ts b/src/ai/AIAssistant.systemPromptLiteral.test.ts index db558c67f..046ca0331 100644 --- a/src/ai/AIAssistant.systemPromptLiteral.test.ts +++ b/src/ai/AIAssistant.systemPromptLiteral.test.ts @@ -18,8 +18,9 @@ import type { CommonResponse } from "./OpenAIRequest"; * * If #1572 ever makes system prompts formattable, this test fails, and whoever * changes it is the person who should also restore the preview and the token - * autocomplete in AIAssistantSettingsModal / AIAssistantCommandSettingsModal / - * AIAssistantInfiniteCommandSettingsModal. + * autocomplete in AIAssistantSettingsModal / AIAssistantCommandSettingsModal. + * (A third modal, AIAssistantInfiniteCommandSettingsModal, carried the same + * affordance; it went with the unreachable command type it configured, #1571.) */ const storeState = vi.hoisted(() => ({ diff --git a/src/ai/aiHelpers.maxChunkTokens.test.ts b/src/ai/aiHelpers.maxChunkTokens.test.ts deleted file mode 100644 index adb16a9f6..000000000 --- a/src/ai/aiHelpers.maxChunkTokens.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { beforeEach, describe, expect, it } from "vitest"; -import { DEFAULT_SETTINGS } from "src/settings"; -import { settingsStore } from "src/settingsStore"; -import { deepClone } from "src/utils/deepClone"; -import type { AIProvider } from "src/ai/Provider"; -import { - getLargestModelMaxTokens, - getMaxChunkTokensUpperBound, -} from "./aiHelpers"; -import { estimateModelInputBudget } from "./tokenEstimator"; - -function setProviders(providers: AIProvider[]): void { - settingsStore.setState({ - ai: { ...settingsStore.getState().ai, providers }, - }); -} - -function provider(name: string, models: AIProvider["models"]): AIProvider { - return { - name, - endpoint: "https://example.com/v1", - apiKey: "", - models, - modelSource: "providerApi", - }; -} - -describe("getLargestModelMaxTokens", () => { - beforeEach(() => { - settingsStore.replaceState(deepClone(DEFAULT_SETTINGS)); - }); - - it("returns the most permissive configured model's context window", () => { - setProviders([ - provider("A", [{ name: "small", maxTokens: 4096 }]), - provider("B", [{ name: "big", maxTokens: 200000 }]), - ]); - expect(getLargestModelMaxTokens()).toBe(200000); - }); - - it("ignores non-finite / non-positive maxTokens values", () => { - setProviders([ - provider("A", [ - { name: "bad", maxTokens: Number.NaN }, - { name: "zero", maxTokens: 0 }, - { name: "ok", maxTokens: 8000 }, - ]), - ]); - expect(getLargestModelMaxTokens()).toBe(8000); - }); - - it("falls back to a conservative default when no models are configured", () => { - setProviders([]); - expect(getLargestModelMaxTokens()).toBe(4096); - }); -}); - -describe("getMaxChunkTokensUpperBound", () => { - beforeEach(() => { - settingsStore.replaceState(deepClone(DEFAULT_SETTINGS)); - setProviders([ - provider("OpenAI", [{ name: "gpt-x", maxTokens: 128000 }]), - provider("Big", [{ name: "huge", maxTokens: 1000000 }]), - ]); - }); - - it("uses the selected model's budget minus the system-prompt overhead", () => { - expect(getMaxChunkTokensUpperBound("gpt-x", 0)).toBe( - estimateModelInputBudget(128000), - ); - expect(getMaxChunkTokensUpperBound("gpt-x", 1000)).toBe( - estimateModelInputBudget(128000) - 1000, - ); - }); - - it("falls back to the largest configured model for the 'Ask me' sentinel", () => { - // "Ask me" is not a real model; it must not throw and must yield the - // most-permissive bound rather than collapsing to the floor. - expect(getMaxChunkTokensUpperBound("Ask me", 0)).toBe( - estimateModelInputBudget(1000000), - ); - }); - - it("falls back for a model that no longer exists", () => { - expect(getMaxChunkTokensUpperBound("deleted-model", 0)).toBe( - estimateModelInputBudget(1000000), - ); - }); - - it("never returns below 1 even when the system prompt exceeds the budget", () => { - expect(getMaxChunkTokensUpperBound("gpt-x", 10_000_000)).toBe(1); - }); -}); diff --git a/src/ai/aiHelpers.ts b/src/ai/aiHelpers.ts index 92aa714c3..bcb922fd9 100644 --- a/src/ai/aiHelpers.ts +++ b/src/ai/aiHelpers.ts @@ -1,10 +1,6 @@ import { log } from "src/logger/logManager"; import { settingsStore } from "src/settingsStore"; import type { AIProvider, Model, ModelRef } from "./Provider"; -import { estimateModelInputBudget } from "./tokenEstimator"; - -/** Conservative context-window fallback when no model is known or configured. */ -const FALLBACK_MODEL_MAX_TOKENS = 4096; /** A model together with the provider that will serve it. */ export interface ResolvedModel { @@ -242,41 +238,3 @@ export function getModelMaxTokens(model: ModelInput) { ); } -/** - * Largest context window among all configured models, or a conservative - * fallback when none are configured. Used when the selected model is unknown at - * config time (see getMaxChunkTokensUpperBound). - */ -export function getLargestModelMaxTokens(): number { - const aiSettings = settingsStore.getState().ai; - - const tokens = aiSettings.providers - .flatMap((provider) => provider.models) - .map((model) => model.maxTokens) - .filter((value) => Number.isFinite(value) && value > 0); - - return tokens.length ? Math.max(...tokens) : FALLBACK_MODEL_MAX_TOKENS; -} - -/** - * Upper bound for the "Max chunk tokens" slider: the model's estimated input - * budget minus the system-prompt overhead, floored at 1. - * - * The selected model can be unknown at config time — the "Ask me" sentinel - * (resolved at runtime) or a model that was removed from settings. In that case - * we fall back to the most permissive configured model instead of throwing, - * which would blank the settings modal. The runtime still caps each chunk to the - * actual model's budget, so a generous UI bound never lets a request exceed the - * real limit. - */ -export function getMaxChunkTokensUpperBound( - model: ModelInput, - systemPromptTokens: number, -): number { - // Silent: this feeds a settings-modal slider bound; rendering a modal must - // not consume the warn-once budget that belongs to an actual run. - const resolved = resolveModel(model, { silent: true }); - const maxTokens = resolved?.model.maxTokens ?? getLargestModelMaxTokens(); - - return Math.max(1, estimateModelInputBudget(maxTokens) - systemPromptTokens); -} diff --git a/src/ai/modelRefPinning.ts b/src/ai/modelRefPinning.ts index 6bbba016c..22d3b1a5f 100644 --- a/src/ai/modelRefPinning.ts +++ b/src/ai/modelRefPinning.ts @@ -10,10 +10,7 @@ type AICommandish = ICommand & { }; function isAICommand(command: ICommand): command is AICommandish { - return ( - command.type === CommandType.AIAssistant || - command.type === CommandType.InfiniteAIAssistant - ); + return command.type === CommandType.AIAssistant; } /** diff --git a/src/commandLabels.ts b/src/commandLabels.ts index 5f0ccf046..f9fb091bc 100644 --- a/src/commandLabels.ts +++ b/src/commandLabels.ts @@ -3,6 +3,5 @@ export const QUICK_ADD_COMMAND_LABELS = { runTemplateFromFolder: "New note from template", applyTemplate: "Apply template to active note", reloadDev: "Reload (dev)", - testDev: "Test (dev)", openSettings: "Open settings", } as const; diff --git a/src/engine/MacroChoiceEngine.ts b/src/engine/MacroChoiceEngine.ts index 77e839144..6c2290ba4 100644 --- a/src/engine/MacroChoiceEngine.ts +++ b/src/engine/MacroChoiceEngine.ts @@ -97,14 +97,31 @@ function getUserScriptSettings( return isRecord(settings) ? settings : undefined; } +/** + * Command types QuickAdd used to declare and no longer does. Without this, the + * generic message would tell a user whose data.json holds one of these that it + * "can come from a newer version of QuickAdd" - the opposite of the truth, and + * no help at all. A removed type is a fact we know; say the known thing. + * + * A Map, not an object literal: the key comes straight from a hand-edited + * data.json, and `{}["constructor"]` is not a miss. + */ +const RETIRED_COMMAND_TYPES = new Map([ + [ + "InfiniteAIAssistant", + 'no released QuickAdd has ever been able to run an "Infinite AI Assistant" command - it had no macro-builder entry and no engine branch in any version, and it has now been removed. Delete the step from the macro. For chunked AI prompts, use quickAddApi.ai.chunkedPrompt() from a user script.', + ], +]); + /** * Why a command could not be run, phrased for the person reading the notice. * - * Two shapes reach here, and telling them apart is the difference between - * actionable and baffling. A real-but-unknown type name is worth quoting: it is - * what the user greps for. An entry with no usable type has nothing to quote - - * printing `'undefined'` or `'[object Object]'` (a hand-authored package can put - * any JSON here) would send them looking for a command type that never existed. + * Three shapes reach here, and telling them apart is the difference between + * actionable and baffling. A type QuickAdd used to declare gets the truth about + * where it came from. Any other real type name is worth quoting: it is what the + * user greps for. An entry with no usable type has nothing to quote - printing + * `'undefined'` or `'[object Object]'` (a hand-authored package can put any JSON + * here) would send them looking for a command type that never existed. */ function describeUnknownType( type: unknown, @@ -114,6 +131,10 @@ function describeUnknownType( return `the saved entry has no ${kind} type, so QuickAdd cannot tell what it was meant to do. It is in .obsidian/plugins/quickadd/data.json.`; } + const retired = + kind === "command" ? RETIRED_COMMAND_TYPES.get(type) : undefined; + if (retired) return retired; + return `QuickAdd does not recognise the ${kind} type '${type}'. It can come from a newer version of QuickAdd, an imported package, or a hand-edited .obsidian/plugins/quickadd/data.json.`; } @@ -318,17 +339,6 @@ export class MacroChoiceEngine extends QuickAddChoiceEngine { case CommandType.AIAssistant: await this.executeAIAssistant(command as IAIAssistantCommand); break; - case CommandType.InfiniteAIAssistant: - // Never creatable, never executable: it shipped in 1.2.0 with no - // builder entry and no engine branch, so the only way to hold one - // is a hand-edited data.json or a hand-authored package. Naming it - // here is what the `never` check costs, and it turns the silent - // drop into a visible one until the type is removed outright. - this.reportUnrunnableCommand( - command, - 'no released QuickAdd has ever been able to run an "Infinite AI Assistant" command - it cannot be created or configured in the macro builder either. Delete the step from the macro.', - ); - break; case CommandType.OpenFile: await this.executeOpenFile(command as IOpenFileCommand); break; diff --git a/src/engine/MacroChoiceEngine.unknownCommand.test.ts b/src/engine/MacroChoiceEngine.unknownCommand.test.ts index fd13fc580..f223a0ea1 100644 --- a/src/engine/MacroChoiceEngine.unknownCommand.test.ts +++ b/src/engine/MacroChoiceEngine.unknownCommand.test.ts @@ -125,12 +125,15 @@ describe("MacroChoiceEngine over a command type it cannot run (#1571)", () => { expect(errors).toHaveLength(1); }); - it("shouts about the Infinite AI Assistant command, which never ran anywhere", async () => { + it("tells the truth about a retired type, not the newer-version guess", async () => { + // A real legacy string, exactly as 2.19.x and earlier wrote it. The + // generic message would say it "can come from a newer version of + // QuickAdd" - the opposite of the truth for a type we removed ourselves. const { run, executeCommandById } = runMacro([ { id: "c1", name: "Summarise", - type: CommandType.InfiniteAIAssistant, + type: "InfiniteAIAssistant", model: "gpt-4", outputVariableName: "out", }, @@ -141,6 +144,8 @@ describe("MacroChoiceEngine over a command type it cannot run (#1571)", () => { expect(errors).toHaveLength(1); expect(errors[0]).toContain("Summarise"); expect(errors[0]).toMatch(/Infinite AI Assistant/i); + expect(errors[0]).toContain("chunkedPrompt"); + expect(errors[0]).not.toMatch(/newer version/i); expect(executeCommandById).toHaveBeenCalledWith(OBSIDIAN_COMMAND_ID); }); diff --git a/src/gui/MacroGUIs/AIAssistantInfiniteCommandSettingsModal.test.ts b/src/gui/MacroGUIs/AIAssistantInfiniteCommandSettingsModal.test.ts deleted file mode 100644 index 01f1b5f43..000000000 --- a/src/gui/MacroGUIs/AIAssistantInfiniteCommandSettingsModal.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { App } from "obsidian"; -import { DEFAULT_SETTINGS } from "src/settings"; -import { settingsStore } from "src/settingsStore"; -import { deepClone } from "src/utils/deepClone"; -import type { IInfiniteAIAssistantCommand } from "src/types/macros/QuickCommands/IAIAssistantCommand"; - -// The system-prompt field wires up a format-syntax suggester and a live -// formatter; neither is relevant to this regression, so stub them out to keep -// the modal constructable in jsdom. -vi.mock("src/quickAddInstance", () => ({ - getQuickAddInstance: () => ({}), -})); -vi.mock("./../suggesters/formatSyntaxSuggester", () => ({ - FormatSyntaxSuggester: class { - constructor() { - // no-op: real suggester needs app.dom/keymap - } - }, -})); -vi.mock("src/formatters/formatDisplayFormatter", () => ({ - FormatDisplayFormatter: class { - async format(input: string): Promise { - return input; - } - }, -})); - -import { InfiniteAIAssistantCommandSettingsModal } from "./AIAssistantInfiniteCommandSettingsModal"; - -function makeSettings(model: string): IInfiniteAIAssistantCommand { - return { - id: "infinite-test", - name: "Summarize", - type: "AIAssistant", - model, - systemPrompt: "You are a helpful assistant.", - outputVariableName: "output", - modelParameters: {}, - resultJoiner: "\n", - chunkSeparator: "\n", - maxChunkTokens: 1000, - mergeChunks: false, - } as unknown as IInfiniteAIAssistantCommand; -} - -describe("InfiniteAIAssistantCommandSettingsModal max-chunk-tokens", () => { - beforeEach(() => { - settingsStore.replaceState(deepClone(DEFAULT_SETTINGS)); - }); - - afterEach(() => { - document.body.innerHTML = ""; - }); - - it("renders the full modal when the model is the 'Ask me' sentinel instead of throwing/blanking", () => { - const settings = makeSettings("Ask me"); - - let modal: InfiniteAIAssistantCommandSettingsModal | undefined; - expect(() => { - modal = new InfiniteAIAssistantCommandSettingsModal( - new App() as App, - settings, - ); - }).not.toThrow(); - - // Both the slider (early in display()) and the System Prompt (last in - // display()) must render — proving display() did not abort midway and - // blank the modal. - const text = modal!.contentEl.textContent ?? ""; - expect(text).toContain("Max chunk tokens"); - expect(text).toContain("System prompt"); - }); - - it("renders without throwing when the configured model no longer exists", () => { - const settings = makeSettings("removed-model-9000"); - - expect(() => { - new InfiniteAIAssistantCommandSettingsModal(new App() as App, settings); - }).not.toThrow(); - }); - - it("surfaces a removed model as a disabled (missing) option instead of a blank dropdown", () => { - const settings = makeSettings("removed-model-9000"); - - const modal = new InfiniteAIAssistantCommandSettingsModal( - new App() as App, - settings, - ); - - const select = modal.contentEl.querySelector("select"); - expect(select).not.toBeNull(); - - const missing = Array.from(select!.options).find((option) => - option.textContent?.startsWith("(missing)"), - ); - expect(missing).toBeDefined(); - expect(missing!.textContent).toBe("(missing) removed-model-9000"); - expect(missing!.disabled).toBe(true); - }); - - it("still renders for a known model", () => { - const settings = makeSettings("gpt-4o"); - - let modal: InfiniteAIAssistantCommandSettingsModal | undefined; - expect(() => { - modal = new InfiniteAIAssistantCommandSettingsModal( - new App() as App, - settings, - ); - }).not.toThrow(); - expect(modal!.contentEl.textContent ?? "").toContain("Max chunk tokens"); - }); -}); diff --git a/src/gui/MacroGUIs/AIAssistantInfiniteCommandSettingsModal.ts b/src/gui/MacroGUIs/AIAssistantInfiniteCommandSettingsModal.ts deleted file mode 100644 index 0ce316265..000000000 --- a/src/gui/MacroGUIs/AIAssistantInfiniteCommandSettingsModal.ts +++ /dev/null @@ -1,268 +0,0 @@ -import type { App } from "obsidian"; -import { Modal, Setting, TextAreaComponent, debounce } from "obsidian"; -import { mountSystemPromptLiteralNote } from "../ai/systemPromptLiteralNote"; -import type { IInfiniteAIAssistantCommand } from "src/types/macros/QuickCommands/IAIAssistantCommand"; -import GenericInputPrompt from "../GenericInputPrompt/GenericInputPrompt"; -import { estimateTokenCount } from "src/ai/tokenEstimator"; -import { getMaxChunkTokensUpperBound } from "src/ai/aiHelpers"; -import { populateModelDropdown } from "../modelSelect"; -import { activeModelRef } from "src/ai/Provider"; -import { addSamplingParamSettings } from "./samplingParamSettings"; - -export class InfiniteAIAssistantCommandSettingsModal extends Modal { - public waitForClose: Promise; - - private resolvePromise: (settings: IInfiniteAIAssistantCommand) => void; - private rejectPromise: (reason?: unknown) => void; - - private settings: IInfiniteAIAssistantCommand; - private showAdvancedSettings = false; - - private get systemPromptTokenLength(): number { - // The estimate is provider-agnostic, so it no longer depends on the model. - return estimateTokenCount(this.settings.systemPrompt); - } - - constructor(app: App, settings: IInfiniteAIAssistantCommand) { - super(app); - - this.settings = settings; - - this.waitForClose = new Promise( - (resolve, reject) => { - this.rejectPromise = reject; - this.resolvePromise = resolve; - } - ); - - this.open(); - this.display(); - } - - private display(): void { - this.contentEl.empty(); - const header = this.contentEl.createEl("h2"); - header.addClass("qa-clickable-modal-title"); - - // Rename affordance is a real