diff --git a/packages/answer/src/index.ts b/packages/answer/src/index.ts index 464b85be31311eb952aed7bc2ac275da87b1bdd2..12d61cdb46ef9f3d96e0aafe512999200fed850d 100644 --- a/packages/answer/src/index.ts +++ b/packages/answer/src/index.ts @@ -188,10 +188,10 @@ const PREFERRED_EXTRACTION_MODEL_ID = "nemotron-3-super-120b-a12b"; * Resolve the extraction model: prefer a lightweight model from the registry, * fall back to the current session model. */ -function resolveExtractionModel(ctx: ExtensionContext) { +function resolveExtractionModel(ctx: ExtensionContext, fallback: NonNullable) { const available = ctx.modelRegistry.getAvailable(); const preferred = available.find((m) => m.id === PREFERRED_EXTRACTION_MODEL_ID); - return preferred ?? ctx.model!; + return preferred ?? fallback; } /** @@ -272,9 +272,11 @@ class QnAComponent implements Component { const editorTheme: EditorTheme = { borderColor: this.dim, selectList: { - selectedBg: (s: string) => `\x1b[44m${s}\x1b[0m`, - matchHighlight: this.cyan, - itemSecondary: this.gray, + selectedPrefix: this.cyan, + selectedText: (s: string) => `\x1b[44m${s}\x1b[0m`, + description: this.gray, + scrollInfo: this.dim, + noMatch: this.dim, }, }; @@ -296,11 +298,6 @@ class QnAComponent implements Component { }; } - private allQuestionsAnswered(): boolean { - this.saveCurrentAnswer(); - return this.answers.every((a) => (a?.trim() || "").length > 0); - } - private saveCurrentAnswer(): void { this.answers[this.currentIndex] = this.editor.getText(); } @@ -473,10 +470,10 @@ class QnAComponent implements Component { }; // Title - lines.push(padToWidth(this.dim("╭" + horizontalLine(boxWidth - 2) + "╮"))); + lines.push(padToWidth(this.dim(`╭${horizontalLine(boxWidth - 2)}╮`))); const title = `${this.bold(this.cyan("Questions"))} ${this.dim(`(${this.currentIndex + 1}/${this.questions.length})`)}`; lines.push(padToWidth(boxLine(title))); - lines.push(padToWidth(this.dim("├" + horizontalLine(boxWidth - 2) + "┤"))); + lines.push(padToWidth(this.dim(`├${horizontalLine(boxWidth - 2)}┤`))); // Progress indicator const progressParts: string[] = []; @@ -525,7 +522,7 @@ class QnAComponent implements Component { lines.push(padToWidth(boxLine(answerPrefix + editorLines[i]))); } else { // Subsequent lines get padding to align with the first line - lines.push(padToWidth(boxLine(" " + editorLines[i]))); + lines.push(padToWidth(boxLine(` ${editorLines[i]}`))); } } @@ -537,7 +534,7 @@ class QnAComponent implements Component { const notesEditorWidth = contentWidth - 4; const notesEditorLines = this.notesEditor.render(notesEditorWidth); for (let i = 1; i < notesEditorLines.length - 1; i++) { - lines.push(padToWidth(boxLine(" " + notesEditorLines[i]))); + lines.push(padToWidth(boxLine(` ${notesEditorLines[i]}`))); } } else if (this.notesText) { lines.push(padToWidth(emptyBoxLine())); @@ -556,15 +553,15 @@ class QnAComponent implements Component { // Confirmation dialog or footer with controls if (this.showingConfirmation) { - lines.push(padToWidth(this.dim("├" + horizontalLine(boxWidth - 2) + "┤"))); + lines.push(padToWidth(this.dim(`├${horizontalLine(boxWidth - 2)}┤`))); const confirmMsg = `${this.yellow("Submit all answers?")} ${this.dim("(Enter/y to confirm, Esc/n to cancel)")}`; lines.push(padToWidth(boxLine(truncateToWidth(confirmMsg, contentWidth)))); } else { - lines.push(padToWidth(this.dim("├" + horizontalLine(boxWidth - 2) + "┤"))); + lines.push(padToWidth(this.dim(`├${horizontalLine(boxWidth - 2)}┤`))); const controls = `${this.dim("Tab/Enter")} next · ${this.dim("Shift+Tab")} prev · ${this.dim("Shift+Enter")} newline · ${this.dim("Alt+N")} note · ${this.dim("Esc")} cancel`; lines.push(padToWidth(boxLine(truncateToWidth(controls, contentWidth)))); } - lines.push(padToWidth(this.dim("╰" + horizontalLine(boxWidth - 2) + "╯"))); + lines.push(padToWidth(this.dim(`╰${horizontalLine(boxWidth - 2)}╯`))); this.cachedWidth = width; this.cachedLines = lines; @@ -613,6 +610,10 @@ export default function (pi: ExtensionAPI) { return; } + // Capture narrowed values so closures can use them without non-null assertions. + const sessionModel = ctx.model; + const assistantText = lastAssistantText; + // Run extraction with loader UI. // The result distinguishes user cancellation from errors so we can // show the right message after the custom UI closes. @@ -622,7 +623,7 @@ export default function (pi: ExtensionAPI) { | { kind: "error"; message: string }; const outcome = await ctx.ui.custom((tui, theme, _kb, done) => { - const extractionModel = resolveExtractionModel(ctx); + const extractionModel = resolveExtractionModel(ctx, sessionModel); const loader = new BorderedLoader(tui, theme, `Extracting questions using ${extractionModel.name}...`); loader.onAbort = () => done({ kind: "cancelled" }); @@ -640,7 +641,7 @@ export default function (pi: ExtensionAPI) { content: [ { type: "text", - text: `\n${lastAssistantText!}\n\n\n${instructionBlock}`, + text: `\n${assistantText}\n\n\n${instructionBlock}`, }, ], timestamp: Date.now(), @@ -679,9 +680,9 @@ export default function (pi: ExtensionAPI) { // If the preferred model errored and it's not already the // session model, fall back to the session model. - if (result.kind === "model_error" && extractionModel.id !== ctx.model!.id) { - ctx.ui.notify(`${extractionModel.name} unavailable, falling back to ${ctx.model!.name}...`, "warning"); - result = await tryExtract(ctx.model!); + if (result.kind === "model_error" && extractionModel.id !== sessionModel.id) { + ctx.ui.notify(`${extractionModel.name} unavailable, falling back to ${sessionModel.name}...`, "warning"); + result = await tryExtract(sessionModel); } switch (result.kind) { @@ -739,7 +740,7 @@ export default function (pi: ExtensionAPI) { pi.sendMessage( { customType: "answers", - content: "I answered your questions in the following way:\n\n" + answersResult, + content: `I answered your questions in the following way:\n\n${answersResult}`, display: true, }, { triggerTurn: true },