Enable thinking selector in MessageEditor

- Import Select component from mini-lit and Brain icon from lucide
- Add thinkingLevel property to track current thinking level (off/minimal/low/medium/high)
- Enable showThinkingSelector (was disabled before)
- Add onThinkingChange callback for thinking level changes
- Render Select component with thinking level options when model supports reasoning
- Position thinking selector on left side next to attachment button
- Add i18n translations for Off/Minimal/Low/Medium/High in English and German
- Rename showThinking → showThinkingSelector in AgentInterface and ChatPanel for consistency
- Remove showDebugToggle property from AgentInterface (unused)
- Clean up browser-javascript tool output message (remove CSP note)
This commit is contained in:
Mario Zechner 2025-10-05 23:32:30 +02:00
parent 414a4eb8fd
commit 53e339ddb8
5 changed files with 54 additions and 12 deletions

View file

@ -309,9 +309,7 @@ This ensures reliable execution.`,
); );
return { return {
output: output: output.trim() || "Code executed successfully (no output)",
output.trim() ||
"Code executed successfully (no output)\n\n⚠ Note: CSP blocked direct execution. Code ran via JailJS interpreter.",
isError: false, isError: false,
details: { files }, details: { files },
}; };

View file

@ -168,7 +168,7 @@ export class ChatPanel extends LitElement {
.session=${this.session} .session=${this.session}
.enableAttachments=${true} .enableAttachments=${true}
.enableModelSelector=${true} .enableModelSelector=${true}
.enableThinking=${true} .showThinkingSelector=${true}
.showThemeToggle=${false} .showThemeToggle=${false}
.showDebugToggle=${false} .showDebugToggle=${false}
.onApiKeyRequired=${this.onApiKeyRequired} .onApiKeyRequired=${this.onApiKeyRequired}

View file

@ -21,9 +21,8 @@ export class AgentInterface extends LitElement {
@property({ attribute: false }) session?: AgentSession; @property({ attribute: false }) session?: AgentSession;
@property() enableAttachments = true; @property() enableAttachments = true;
@property() enableModelSelector = true; @property() enableModelSelector = true;
@property() enableThinking = true; @property() enableThinkingSelector = true;
@property() showThemeToggle = false; @property() showThemeToggle = false;
@property() showDebugToggle = false;
// Optional custom API key prompt handler - if not provided, uses default dialog // Optional custom API key prompt handler - if not provided, uses default dialog
@property({ attribute: false }) onApiKeyRequired?: (provider: string) => Promise<boolean>; @property({ attribute: false }) onApiKeyRequired?: (provider: string) => Promise<boolean>;
@ -286,7 +285,7 @@ export class AgentInterface extends LitElement {
.thinkingLevel=${state.thinkingLevel} .thinkingLevel=${state.thinkingLevel}
.showAttachmentButton=${this.enableAttachments} .showAttachmentButton=${this.enableAttachments}
.showModelSelector=${this.enableModelSelector} .showModelSelector=${this.enableModelSelector}
.showThinking=${this.enableThinking} .showThinkingSelector=${this.enableThinkingSelector}
.onSend=${(input: string, attachments: Attachment[]) => { .onSend=${(input: string, attachments: Attachment[]) => {
this.sendMessage(input, attachments); this.sendMessage(input, attachments);
}} }}
@ -295,7 +294,7 @@ export class AgentInterface extends LitElement {
ModelSelector.open(state.model, (model) => session.setModel(model)); ModelSelector.open(state.model, (model) => session.setModel(model));
}} }}
.onThinkingChange=${ .onThinkingChange=${
this.enableThinking this.enableThinkingSelector
? (level: "off" | "minimal" | "low" | "medium" | "high") => { ? (level: "off" | "minimal" | "low" | "medium" | "high") => {
session.setThinkingLevel(level); session.setThinkingLevel(level);
} }

View file

@ -1,9 +1,9 @@
import { Button, html, icon } from "@mariozechner/mini-lit"; import { Button, html, icon, Select, type SelectOption } from "@mariozechner/mini-lit";
import type { Model } from "@mariozechner/pi-ai"; import type { Model } from "@mariozechner/pi-ai";
import { LitElement } from "lit"; import { LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { createRef, ref } from "lit/directives/ref.js"; import { createRef, ref } from "lit/directives/ref.js";
import { Loader2, Paperclip, Send, Sparkles, Square } from "lucide"; import { Brain, Loader2, Paperclip, Send, Sparkles, Square } from "lucide";
import "./AttachmentTile.js"; import "./AttachmentTile.js";
import { type Attachment, loadAttachment } from "../utils/attachment-utils.js"; import { type Attachment, loadAttachment } from "../utils/attachment-utils.js";
import { i18n } from "../utils/i18n.js"; import { i18n } from "../utils/i18n.js";
@ -33,13 +33,15 @@ export class MessageEditor extends LitElement {
@property() isStreaming = false; @property() isStreaming = false;
@property() currentModel?: Model<any>; @property() currentModel?: Model<any>;
@property() thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" = "off";
@property() showAttachmentButton = true; @property() showAttachmentButton = true;
@property() showModelSelector = true; @property() showModelSelector = true;
@property() showThinking = false; // Disabled for now @property() showThinkingSelector = true;
@property() onInput?: (value: string) => void; @property() onInput?: (value: string) => void;
@property() onSend?: (input: string, attachments: Attachment[]) => void; @property() onSend?: (input: string, attachments: Attachment[]) => void;
@property() onAbort?: () => void; @property() onAbort?: () => void;
@property() onModelSelect?: () => void; @property() onModelSelect?: () => void;
@property() onThinkingChange?: (level: "off" | "minimal" | "low" | "medium" | "high") => void;
@property() onFilesChange?: (files: Attachment[]) => void; @property() onFilesChange?: (files: Attachment[]) => void;
@property() attachments: Attachment[] = []; @property() attachments: Attachment[] = [];
@property() maxFiles = 10; @property() maxFiles = 10;
@ -150,6 +152,10 @@ export class MessageEditor extends LitElement {
} }
override render() { override render() {
// Check if current model supports thinking/reasoning
const model = this.currentModel;
const supportsThinking = model?.reasoning === true; // Models with reasoning:true support thinking
return html` return html`
<div class="bg-card rounded-xl border border-border shadow-sm"> <div class="bg-card rounded-xl border border-border shadow-sm">
<!-- Attachments --> <!-- Attachments -->
@ -194,7 +200,7 @@ export class MessageEditor extends LitElement {
<!-- Button Row --> <!-- Button Row -->
<div class="px-2 pb-2 flex items-center justify-between"> <div class="px-2 pb-2 flex items-center justify-between">
<!-- Left side - attachment and quick action buttons --> <!-- Left side - attachment and thinking selector -->
<div class="flex gap-2 items-center"> <div class="flex gap-2 items-center">
${ ${
this.showAttachmentButton this.showAttachmentButton
@ -215,6 +221,30 @@ export class MessageEditor extends LitElement {
` `
: "" : ""
} }
${
supportsThinking && this.showThinkingSelector
? html`
${Select({
value: this.thinkingLevel,
placeholder: i18n("Off"),
options: [
{ value: "off", label: i18n("Off"), icon: icon(Brain, "sm") },
{ value: "minimal", label: i18n("Minimal"), icon: icon(Brain, "sm") },
{ value: "low", label: i18n("Low"), icon: icon(Brain, "sm") },
{ value: "medium", label: i18n("Medium"), icon: icon(Brain, "sm") },
{ value: "high", label: i18n("High"), icon: icon(Brain, "sm") },
] as SelectOption[],
onChange: (value: string) => {
this.onThinkingChange?.(value as "off" | "minimal" | "low" | "medium" | "high");
},
width: "80px",
size: "sm",
variant: "ghost",
fitContent: true,
})}
`
: ""
}
</div> </div>
<!-- Model selector and send on the right --> <!-- Model selector and send on the right -->

View file

@ -108,6 +108,11 @@ declare module "@mariozechner/mini-lit" {
"API Key Required": string; "API Key Required": string;
"Enter your API key for {provider}": string; "Enter your API key for {provider}": string;
"The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.": string; "The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.": string;
Off: string;
Minimal: string;
Low: string;
Medium: string;
High: string;
} }
} }
@ -223,6 +228,11 @@ const translations = {
"Enter your API key for {provider}": "Enter your API key for {provider}", "Enter your API key for {provider}": "Enter your API key for {provider}",
"The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.": "The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.":
"The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.", "The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.",
Off: "Off",
Minimal: "Minimal",
Low: "Low",
Medium: "Medium",
High: "High",
}, },
de: { de: {
...defaultGerman, ...defaultGerman,
@ -335,6 +345,11 @@ const translations = {
"Enter your API key for {provider}": "Geben Sie Ihren API-Schlüssel für {provider} ein", "Enter your API key for {provider}": "Geben Sie Ihren API-Schlüssel für {provider} ein",
"The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.": "The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.":
"Der CORS-Proxy entfernt CORS-Header aus API-Antworten und ermöglicht browserbasierte Anwendungen, direkte Aufrufe an LLM-Anbieter ohne CORS-Einschränkungen durchzuführen. Er leitet Anfragen an Anbieter weiter und entfernt Header, die sonst Cross-Origin-Anfragen blockieren würden.", "Der CORS-Proxy entfernt CORS-Header aus API-Antworten und ermöglicht browserbasierte Anwendungen, direkte Aufrufe an LLM-Anbieter ohne CORS-Einschränkungen durchzuführen. Er leitet Anfragen an Anbieter weiter und entfernt Header, die sonst Cross-Origin-Anfragen blockieren würden.",
Off: "Aus",
Minimal: "Minimal",
Low: "Niedrig",
Medium: "Mittel",
High: "Hoch",
}, },
}; };