From 92f8801864612945ffc9e30321792186185d7b01 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 29 Oct 2025 16:11:05 +0100 Subject: [PATCH] Fix message editor height adjustment, requires Chrome +123 --- package.json | 1 + packages/ai/package.json | 1 + packages/web-ui/package.json | 1 + packages/web-ui/src/ChatPanel.ts | 2 ++ .../web-ui/src/components/AgentInterface.ts | 14 ++++++++- .../web-ui/src/components/MessageEditor.ts | 31 +------------------ packages/web-ui/src/components/MessageList.ts | 2 ++ packages/web-ui/src/components/Messages.ts | 5 ++- .../components/StreamingMessageContainer.ts | 2 ++ packages/web-ui/src/dialogs/SettingsDialog.ts | 5 ++- packages/web-ui/src/utils/i18n.ts | 15 ++++++--- 11 files changed, 41 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index eaf065af..42928d92 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "clean": "npm run clean --workspaces", "build": "npm run build -w @mariozechner/pi-tui && npm run build -w @mariozechner/pi-ai && npm run build -w @mariozechner/pi-agent && npm run build -w @mariozechner/pi-agent-old && npm run build -w @mariozechner/coding-agent && npm run build -w @mariozechner/pi-web-ui && npm run build -w @mariozechner/pi-proxy && npm run build -w @mariozechner/pi", "dev": "concurrently --names \"ai,web-ui,tui,proxy\" --prefix-colors \"cyan,green,magenta,blue\" \"npm run dev -w @mariozechner/pi-ai\" \"npm run dev -w @mariozechner/pi-web-ui\" \"npm run dev -w @mariozechner/pi-tui\" \"npm run dev -w @mariozechner/pi-proxy\"", + "dev:tsc": "concurrently --names \"ai,web-ui\" --prefix-colors \"cyan,green\" \"npm run dev:tsc -w @mariozechner/pi-ai\" \"npm run dev:tsc -w @mariozechner/pi-web-ui\"", "check": "biome check --write . && npm run check --workspaces && tsc --noEmit", "test": "npm run test --workspaces --if-present", "version:patch": "npm version patch -ws --no-git-tag-version && node scripts/sync-versions.js", diff --git a/packages/ai/package.json b/packages/ai/package.json index 7fe5920d..18eca83a 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -14,6 +14,7 @@ "generate-models": "npx tsx scripts/generate-models.ts", "build": "npm run generate-models && tsc -p tsconfig.build.json", "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput", + "dev:tsc": "tsc -p tsconfig.build.json --watch --preserveWatchOutput", "check": "biome check --write .", "test": "vitest --run", "prepublishOnly": "npm run clean && npm run build" diff --git a/packages/web-ui/package.json b/packages/web-ui/package.json index 3ca05539..e65909eb 100644 --- a/packages/web-ui/package.json +++ b/packages/web-ui/package.json @@ -13,6 +13,7 @@ "clean": "rm -rf dist", "build": "tsc -p tsconfig.build.json && tailwindcss -i ./src/app.css -o ./dist/app.css --minify", "dev": "concurrently --names \"build,example\" --prefix-colors \"cyan,green\" \"tsc -p tsconfig.build.json --watch --preserveWatchOutput\" \"tailwindcss -i ./src/app.css -o ./dist/app.css --watch\" \"npm run dev --prefix example\"", + "dev:tsc": "concurrently --names \"build\" --prefix-colors \"cyan\" \"tsc -p tsconfig.build.json --watch --preserveWatchOutput\" \"tailwindcss -i ./src/app.css -o ./dist/app.css --watch\"", "typecheck": "tsc --noEmit && cd example && tsc --noEmit", "check": "npm run typecheck" }, diff --git a/packages/web-ui/src/ChatPanel.ts b/packages/web-ui/src/ChatPanel.ts index 9999868c..decc1e89 100644 --- a/packages/web-ui/src/ChatPanel.ts +++ b/packages/web-ui/src/ChatPanel.ts @@ -59,6 +59,7 @@ export class ChatPanel extends LitElement { config?: { onApiKeyRequired?: (provider: string) => Promise; onBeforeSend?: () => void | Promise; + onCostClick?: () => void; sandboxUrlProvider?: () => string; toolsFactory?: ( agent: Agent, @@ -79,6 +80,7 @@ export class ChatPanel extends LitElement { this.agentInterface.showThemeToggle = false; this.agentInterface.onApiKeyRequired = config?.onApiKeyRequired; this.agentInterface.onBeforeSend = config?.onBeforeSend; + this.agentInterface.onCostClick = config?.onCostClick; // Set up artifacts panel this.artifactsPanel = new ArtifactsPanel(); diff --git a/packages/web-ui/src/components/AgentInterface.ts b/packages/web-ui/src/components/AgentInterface.ts index fdd78447..db824039 100644 --- a/packages/web-ui/src/components/AgentInterface.ts +++ b/packages/web-ui/src/components/AgentInterface.ts @@ -29,6 +29,8 @@ export class AgentInterface extends LitElement { @property({ attribute: false }) onBeforeSend?: () => void | Promise; // Optional callback called before executing a tool call - return false to prevent execution @property({ attribute: false }) onBeforeToolCall?: (toolName: string, args: any) => boolean | Promise; + // Optional callback called when cost display is clicked + @property({ attribute: false }) onCostClick?: () => void; // References @query("message-editor") private _messageEditor!: MessageEditor; @@ -226,6 +228,7 @@ export class AgentInterface extends LitElement { .tools=${state.tools} .pendingToolCalls=${this.session ? this.session.state.pendingToolCalls : new Set()} .isStreaming=${state.isStreaming} + .onCostClick=${this.onCostClick} > @@ -235,6 +238,7 @@ export class AgentInterface extends LitElement { .isStreaming=${state.isStreaming} .pendingToolCalls=${state.pendingToolCalls} .toolResultsById=${toolResultsById} + .onCostClick=${this.onCostClick} > `; @@ -275,7 +279,15 @@ export class AgentInterface extends LitElement {
${this.showThemeToggle ? html`` : html``}
-
${totalsText ? html`${totalsText}` : ""}
+
+ ${ + totalsText + ? this.onCostClick + ? html`${totalsText}` + : html`${totalsText}` + : "" + } +
`; } diff --git a/packages/web-ui/src/components/MessageEditor.ts b/packages/web-ui/src/components/MessageEditor.ts index b4cb6036..6508cd1f 100644 --- a/packages/web-ui/src/components/MessageEditor.ts +++ b/packages/web-ui/src/components/MessageEditor.ts @@ -22,13 +22,6 @@ export class MessageEditor extends LitElement { const oldValue = this._value; this._value = val; this.requestUpdate("value", oldValue); - this.updateComplete.then(() => { - const textarea = this.textareaRef.value; - if (textarea) { - textarea.style.height = "auto"; - textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`; - } - }); } @property() isStreaming = false; @@ -60,8 +53,6 @@ export class MessageEditor extends LitElement { private handleTextareaInput = (e: Event) => { const textarea = e.target as HTMLTextAreaElement; this.value = textarea.value; - textarea.style.height = "auto"; - textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`; this.onInput?.(this.value); }; @@ -231,33 +222,13 @@ export class MessageEditor extends LitElement { this.processingFiles = false; }; - private adjustTextareaHeight() { - const textarea = this.textareaRef.value; - if (textarea) { - // Reset height to auto to get accurate scrollHeight - textarea.style.height = "auto"; - // Only adjust if there's content, otherwise keep minimal height - if (this.value.trim()) { - textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`; - } - } - } - override firstUpdated() { const textarea = this.textareaRef.value; if (textarea) { - // Don't adjust height on first render - let it be minimal textarea.focus(); } } - override updated() { - // Only adjust height when component updates if there's content - if (this.value) { - this.adjustTextareaHeight(); - } - } - override render() { // Check if current model supports thinking/reasoning const model = this.currentModel; @@ -304,7 +275,7 @@ export class MessageEditor extends LitElement { class="w-full bg-transparent p-4 text-foreground placeholder-muted-foreground outline-none resize-none overflow-y-auto" placeholder=${i18n("Type a message...")} rows="1" - style="max-height: 200px;" + style="max-height: 200px; field-sizing: content; min-height: 1lh; height: auto;" .value=${this.value} @input=${this.handleTextareaInput} @keydown=${this.handleKeyDown} diff --git a/packages/web-ui/src/components/MessageList.ts b/packages/web-ui/src/components/MessageList.ts index 59d2567f..806ddec1 100644 --- a/packages/web-ui/src/components/MessageList.ts +++ b/packages/web-ui/src/components/MessageList.ts @@ -15,6 +15,7 @@ export class MessageList extends LitElement { @property({ type: Array }) tools: AgentTool[] = []; @property({ type: Object }) pendingToolCalls?: Set; @property({ type: Boolean }) isStreaming: boolean = false; + @property({ attribute: false }) onCostClick?: () => void; protected override createRenderRoot(): HTMLElement | DocumentFragment { return this; @@ -68,6 +69,7 @@ export class MessageList extends LitElement { .pendingToolCalls=${this.pendingToolCalls} .toolResultsById=${resultByCallId} .hideToolCalls=${false} + .onCostClick=${this.onCostClick} >`, }); index++; diff --git a/packages/web-ui/src/components/Messages.ts b/packages/web-ui/src/components/Messages.ts index 3cf70861..6cdb8b73 100644 --- a/packages/web-ui/src/components/Messages.ts +++ b/packages/web-ui/src/components/Messages.ts @@ -88,6 +88,7 @@ export class AssistantMessage extends LitElement { @property({ type: Boolean }) hideToolCalls = false; @property({ type: Object }) toolResultsById?: Map; @property({ type: Boolean }) isStreaming: boolean = false; + @property({ attribute: false }) onCostClick?: () => void; protected override createRenderRoot(): HTMLElement | DocumentFragment { return this; @@ -133,7 +134,9 @@ export class AssistantMessage extends LitElement { ${orderedParts.length ? html`
${orderedParts}
` : ""} ${ this.message.usage - ? html`
${formatUsage(this.message.usage)}
` + ? this.onCostClick + ? html`
${formatUsage(this.message.usage)}
` + : html`
${formatUsage(this.message.usage)}
` : "" } ${ diff --git a/packages/web-ui/src/components/StreamingMessageContainer.ts b/packages/web-ui/src/components/StreamingMessageContainer.ts index d4c4b4a6..31397f30 100644 --- a/packages/web-ui/src/components/StreamingMessageContainer.ts +++ b/packages/web-ui/src/components/StreamingMessageContainer.ts @@ -8,6 +8,7 @@ export class StreamingMessageContainer extends LitElement { @property({ type: Boolean }) isStreaming = false; @property({ type: Object }) pendingToolCalls?: Set; @property({ type: Object }) toolResultsById?: Map; + @property({ attribute: false }) onCostClick?: () => void; @state() private _message: Message | null = null; private _pendingMessage: Message | null = null; @@ -87,6 +88,7 @@ export class StreamingMessageContainer extends LitElement { .pendingToolCalls=${this.pendingToolCalls} .toolResultsById=${this.toolResultsById} .hideToolCalls=${false} + .onCostClick=${this.onCostClick} > ${this.isStreaming ? html`` : ""} diff --git a/packages/web-ui/src/dialogs/SettingsDialog.ts b/packages/web-ui/src/dialogs/SettingsDialog.ts index c43f36c7..45a50fbc 100644 --- a/packages/web-ui/src/dialogs/SettingsDialog.ts +++ b/packages/web-ui/src/dialogs/SettingsDialog.ts @@ -84,7 +84,7 @@ export class ProxyTab extends SettingsTab { return html`

- ${i18n("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.")} + ${i18n("Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.")}

@@ -109,6 +109,9 @@ export class ProxyTab extends SettingsTab { }, onChange: () => this.saveProxySettings(), })} +

+ ${i18n("Format: The proxy must accept requests as /?url=")} +

`; diff --git a/packages/web-ui/src/utils/i18n.ts b/packages/web-ui/src/utils/i18n.ts index 33f9eb07..1bef3a3c 100644 --- a/packages/web-ui/src/utils/i18n.ts +++ b/packages/web-ui/src/utils/i18n.ts @@ -137,11 +137,12 @@ declare module "@mariozechner/mini-lit" { Proxy: string; "Use CORS Proxy": string; "Proxy URL": string; + "Format: The proxy must accept requests as /?url=": string; "Settings are stored locally in your browser": string; Clear: string; "API Key Required": 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; + "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.": string; Off: string; Minimal: string; Low: string; @@ -312,12 +313,14 @@ export const translations = { Proxy: "Proxy", "Use CORS Proxy": "Use CORS Proxy", "Proxy URL": "Proxy URL", + "Format: The proxy must accept requests as /?url=": + "Format: The proxy must accept requests as /?url=", "Settings are stored locally in your browser": "Settings are stored locally in your browser", Clear: "Clear", "API Key Required": "API Key Required", "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.", + "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.": + "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.", Off: "Off", Minimal: "Minimal", Low: "Low", @@ -491,12 +494,14 @@ export const translations = { Proxy: "Proxy", "Use CORS Proxy": "CORS-Proxy verwenden", "Proxy URL": "Proxy-URL", + "Format: The proxy must accept requests as /?url=": + "Format: Der Proxy muss Anfragen als /?url= akzeptieren", "Settings are stored locally in your browser": "Einstellungen werden lokal in Ihrem Browser gespeichert", Clear: "Löschen", "API Key Required": "API-Schlüssel erforderlich", "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.": - "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.", + "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.": + "Ermöglicht browserbasierten Anwendungen, CORS-Einschränkungen beim Aufruf von LLM-Anbietern zu umgehen. Erforderlich für Z-AI und Anthropic mit OAuth-Token.", Off: "Aus", Minimal: "Minimal", Low: "Niedrig",