summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-03-24 14:52:40 +0900
committerAdam Malczewski <[email protected]>2026-03-24 14:52:40 +0900
commit4e43498aec88c3f974fd179bef9e81d1cef63e9c (patch)
tree7b73e6f54cf3ecffa087f89aeb78ccb9414e7581
parentc34eca01c8fc8bc8ff6a14d0c48a9c2323daf915 (diff)
downloadai-pulse-obsidian-plugin-4e43498aec88c3f974fd179bef9e81d1cef63e9c.tar.gz
ai-pulse-obsidian-plugin-4e43498aec88c3f974fd179bef9e81d1cef63e9c.zip
Fix mobile load error, add FAB and collapse UI
-rw-r--r--.gitignore2
-rw-r--r--.rules/changelog/2026-03/24/09.md34
-rw-r--r--src/chat-view.ts83
-rw-r--r--src/ollama-client.ts155
-rw-r--r--src/settings-modal.ts2
-rw-r--r--styles.css196
6 files changed, 417 insertions, 55 deletions
diff --git a/.gitignore b/.gitignore
index 11416d6..782a86e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,4 +21,4 @@ data.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store
-obsidian-developer-docs
+reference
diff --git a/.rules/changelog/2026-03/24/09.md b/.rules/changelog/2026-03/24/09.md
new file mode 100644
index 0000000..e9a39b3
--- /dev/null
+++ b/.rules/changelog/2026-03/24/09.md
@@ -0,0 +1,34 @@
+# Changelog — 2026-03-24 #09
+
+## Mobile "Load failed" Fix
+
+- **`src/ollama-client.ts`**: Imported `Platform` from Obsidian for runtime mobile detection.
+- Split `sendChatMessageStreaming` into three functions:
+ - `sendChatMessageStreaming()` — dispatcher that checks `Platform.isMobile`
+ - `sendChatMessageStreamingMobile()` — uses `requestUrl()` (non-streaming) to bypass WebView sandbox restrictions on mobile
+ - `sendChatMessageStreamingDesktop()` — preserves native `fetch()` for real token-by-token streaming on desktop
+- Enhanced error messages in `testConnection()` and the mobile chat path with mobile-specific hints (e.g., "use your computer's LAN IP instead of localhost").
+- Added `"load failed"` to the list of caught network error patterns.
+
+## UI: DaisyUI-inspired Collapse for Tool Call Details
+
+- **`src/chat-view.ts`**: Replaced native `<details>/<summary>` in `appendToolCall` with a checkbox-driven CSS grid collapse.
+- **`styles.css`**: Added `.ai-organizer-collapse` styles using `grid-template-rows: 0fr → 1fr` transition with a rotating arrow indicator, inspired by DaisyUI's collapse component.
+
+## UI: FAB / Speed Dial
+
+- **`src/chat-view.ts`**: Removed Settings and Tools buttons from the input row. Added a floating action button (FAB) in the top-right of the messages area.
+ - Main trigger: gear icon, rotates 90° on open.
+ - Actions fan downward with staggered scale/opacity animations.
+ - Three actions: **AI Settings** (sliders icon), **Tools** (wrench icon), **Clear Chat** (trash icon).
+ - Clicking an action opens the modal/clears chat and blurs focus to auto-close the FAB.
+- **`styles.css`**: Added FAB positioning, animation, label, and action button styles using Obsidian CSS variables.
+
+## UI: Settings Modal Rename
+
+- **`src/settings-modal.ts`**: Changed modal title from "AI Organizer Settings" to "AI Settings".
+
+## Removed
+
+- Removed the `.ai-organizer-tools-active` accent styling from the tools FAB button.
+- Removed the old inline `.ai-organizer-input-buttons`, `.ai-organizer-settings-btn`, and `.ai-organizer-tools-btn` styles.
diff --git a/src/chat-view.ts b/src/chat-view.ts
index 4ba89f9..524c731 100644
--- a/src/chat-view.ts
+++ b/src/chat-view.ts
@@ -45,26 +45,36 @@ export class ChatView extends ItemView {
const messagesArea = contentEl.createDiv({ cls: "ai-organizer-messages-area" });
this.messageContainer = messagesArea.createDiv({ cls: "ai-organizer-messages" });
- const inputRow = messagesArea.createDiv({ cls: "ai-organizer-input-row" });
- this.textarea = inputRow.createEl("textarea", {
- attr: { placeholder: "Type a message...", rows: "2" },
- });
-
- const buttonGroup = inputRow.createDiv({ cls: "ai-organizer-input-buttons" });
+ // --- FAB Speed Dial ---
+ const fab = messagesArea.createDiv({ cls: "ai-organizer-fab" });
- // Settings button
- const settingsBtn = buttonGroup.createEl("button", {
- cls: "ai-organizer-settings-btn",
+ // Main FAB trigger button (first child)
+ const fabTrigger = fab.createEl("button", {
+ cls: "ai-organizer-fab-trigger",
+ attr: { "aria-label": "Actions", tabindex: "0" },
+ });
+ setIcon(fabTrigger, "settings");
+
+ // Speed dial actions (revealed on focus-within)
+ const settingsAction = fab.createDiv({ cls: "ai-organizer-fab-action" });
+ const settingsLabel = settingsAction.createSpan({ cls: "ai-organizer-fab-label", text: "AI Settings" });
+ void settingsLabel;
+ const settingsBtn = settingsAction.createEl("button", {
+ cls: "ai-organizer-fab-btn",
attr: { "aria-label": "Settings" },
});
- setIcon(settingsBtn, "settings");
+ setIcon(settingsBtn, "sliders-horizontal");
settingsBtn.addEventListener("click", () => {
new SettingsModal(this.plugin).open();
+ // Blur to close the FAB
+ (document.activeElement as HTMLElement)?.blur();
});
- // Tools button
- this.toolsButton = buttonGroup.createEl("button", {
- cls: "ai-organizer-tools-btn",
+ const toolsAction = fab.createDiv({ cls: "ai-organizer-fab-action" });
+ const toolsLabel = toolsAction.createSpan({ cls: "ai-organizer-fab-label", text: "Tools" });
+ void toolsLabel;
+ this.toolsButton = toolsAction.createEl("button", {
+ cls: "ai-organizer-fab-btn",
attr: { "aria-label": "Tools" },
});
setIcon(this.toolsButton, "wrench");
@@ -75,10 +85,32 @@ export class ChatView extends ItemView {
this.updateToolsButtonState();
};
modal.open();
+ (document.activeElement as HTMLElement)?.blur();
+ });
+
+ const clearAction = fab.createDiv({ cls: "ai-organizer-fab-action" });
+ const clearLabel = clearAction.createSpan({ cls: "ai-organizer-fab-label", text: "Clear Chat" });
+ void clearLabel;
+ const clearBtn = clearAction.createEl("button", {
+ cls: "ai-organizer-fab-btn",
+ attr: { "aria-label": "Clear Chat" },
+ });
+ setIcon(clearBtn, "trash-2");
+ clearBtn.addEventListener("click", () => {
+ this.messages = [];
+ if (this.messageContainer !== null) {
+ this.messageContainer.empty();
+ }
+ (document.activeElement as HTMLElement)?.blur();
+ });
+
+ const inputRow = messagesArea.createDiv({ cls: "ai-organizer-input-row" });
+ this.textarea = inputRow.createEl("textarea", {
+ attr: { placeholder: "Type a message...", rows: "2" },
});
// Send button
- this.sendButton = buttonGroup.createEl("button", { text: "Send" });
+ this.sendButton = inputRow.createEl("button", { text: "Send" });
this.textarea.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
@@ -290,16 +322,31 @@ export class ChatView extends ItemView {
container.createDiv({ text: event.summary, cls: "ai-organizer-tool-call-summary" });
container.createDiv({ text: event.resultSummary, cls: "ai-organizer-tool-call-result-summary" });
- const details = container.createEl("details", { cls: "ai-organizer-tool-call-details" });
- details.createEl("summary", { text: "Details" });
+ // DaisyUI-style collapse with checkbox
+ const collapse = container.createDiv({ cls: "ai-organizer-collapse ai-organizer-collapse-arrow" });
+ const collapseId = `tool-collapse-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
+ const checkbox = collapse.createEl("input", {
+ type: "checkbox",
+ attr: { id: collapseId },
+ });
+ checkbox.addClass("ai-organizer-collapse-toggle");
+ const titleEl = collapse.createEl("label", {
+ cls: "ai-organizer-collapse-title",
+ attr: { for: collapseId },
+ text: "Details",
+ });
+ void titleEl; // suppress unused warning
+
+ const collapseContent = collapse.createDiv({ cls: "ai-organizer-collapse-content" });
+ const contentInner = collapseContent.createDiv({ cls: "ai-organizer-collapse-content-inner" });
const argsStr = JSON.stringify(event.args, null, 2);
- details.createEl("pre", { text: argsStr, cls: "ai-organizer-tool-call-args" });
+ contentInner.createEl("pre", { text: argsStr, cls: "ai-organizer-tool-call-args" });
const resultPreview = event.result.length > 500
? event.result.substring(0, 500) + "..."
: event.result;
- details.createEl("pre", { text: resultPreview, cls: "ai-organizer-tool-call-result" });
+ contentInner.createEl("pre", { text: resultPreview, cls: "ai-organizer-tool-call-result" });
}
private scrollToBottom(): void {
diff --git a/src/ollama-client.ts b/src/ollama-client.ts
index c9e4042..6ada18a 100644
--- a/src/ollama-client.ts
+++ b/src/ollama-client.ts
@@ -1,4 +1,4 @@
-import { requestUrl } from "obsidian";
+import { Platform, requestUrl } from "obsidian";
import type { App } from "obsidian";
import type { OllamaToolDefinition } from "./tools";
import { findToolByName } from "./tools";
@@ -48,7 +48,13 @@ export async function testConnection(ollamaUrl: string): Promise<string> {
} catch (err: unknown) {
if (err instanceof Error) {
const msg = err.message.toLowerCase();
- if (msg.includes("net") || msg.includes("fetch") || msg.includes("failed to fetch")) {
+ if (msg.includes("net") || msg.includes("fetch") || msg.includes("failed to fetch") || msg.includes("load failed")) {
+ if (Platform.isMobile) {
+ throw new Error(
+ "Ollama is unreachable. On mobile, use your computer's LAN IP " +
+ "(e.g. http://192.168.1.x:11434) instead of localhost."
+ );
+ }
throw new Error("Ollama is unreachable. Is the server running?");
}
throw err;
@@ -257,17 +263,153 @@ async function* readNdjsonStream(
* Streams text chunks via onChunk callback. Supports tool-calling agent loop:
* tool execution rounds are non-streamed, only the final text response streams.
* Returns the full accumulated response text.
+ *
+ * On mobile platforms, falls back to non-streaming via Obsidian's requestUrl()
+ * because native fetch() cannot reach local network addresses from the mobile
+ * WebView sandbox.
*/
export async function sendChatMessageStreaming(
opts: StreamingChatOptions,
): Promise<string> {
+ if (Platform.isMobile) {
+ return sendChatMessageStreamingMobile(opts);
+ }
+ return sendChatMessageStreamingDesktop(opts);
+}
+
+/**
+ * Mobile fallback: uses Obsidian's requestUrl() (non-streaming) so the request
+ * goes through the native networking layer and can reach localhost / LAN.
+ */
+async function sendChatMessageStreamingMobile(
+ opts: StreamingChatOptions,
+): Promise<string> {
+ const { ollamaUrl, model, messages, tools, app, onChunk, onToolCall, onCreateBubble } = opts;
+ const maxIterations = 10;
+ let iterations = 0;
+
+ const workingMessages = messages.map((m) => ({ ...m }));
+
+ if (tools !== undefined && tools.length > 0) {
+ const systemPrompt: ChatMessage = {
+ role: "system",
+ content:
+ "You are a helpful assistant with access to tools for interacting with an Obsidian vault. " +
+ "When you use the search_files tool, the results contain exact file paths. " +
+ "You MUST use these exact paths when calling read_file or referencing files. " +
+ "NEVER guess or modify file paths — always use the paths returned by search_files verbatim.",
+ };
+ workingMessages.unshift(systemPrompt);
+ }
+
+ while (iterations < maxIterations) {
+ iterations++;
+
+ onCreateBubble();
+
+ const body: Record<string, unknown> = {
+ model,
+ messages: workingMessages,
+ stream: false,
+ };
+
+ if (tools !== undefined && tools.length > 0) {
+ body.tools = tools;
+ }
+
+ try {
+ const response = await requestUrl({
+ url: `${ollamaUrl}/api/chat`,
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body),
+ });
+
+ const messageObj = (response.json as Record<string, unknown>).message;
+ if (typeof messageObj !== "object" || messageObj === null) {
+ throw new Error("Unexpected response format: missing message.");
+ }
+
+ const msg = messageObj as Record<string, unknown>;
+ const content = typeof msg.content === "string" ? msg.content : "";
+ const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls as ToolCallResponse[] : [];
+
+ // Deliver the full content as a single chunk to the UI
+ if (content !== "") {
+ onChunk(content);
+ }
+
+ if (toolCalls.length === 0) {
+ return content;
+ }
+
+ const assistantMsg: ChatMessage = {
+ role: "assistant",
+ content,
+ tool_calls: toolCalls,
+ };
+ workingMessages.push(assistantMsg);
+
+ if (app === undefined) {
+ throw new Error("App reference required for tool execution.");
+ }
+
+ for (const tc of toolCalls) {
+ const fnName = tc.function.name;
+ const fnArgs = tc.function.arguments;
+ const toolEntry = findToolByName(fnName);
+
+ let result: string;
+ if (toolEntry === undefined) {
+ result = `Error: Unknown tool "${fnName}".`;
+ } else {
+ result = await toolEntry.execute(app, fnArgs);
+ }
+
+ if (onToolCall !== undefined) {
+ const friendlyName = toolEntry !== undefined ? toolEntry.friendlyName : fnName;
+ const summary = toolEntry !== undefined ? toolEntry.summarize(fnArgs) : `Called ${fnName}`;
+ const resultSummary = toolEntry !== undefined ? toolEntry.summarizeResult(result) : "";
+ onToolCall({ toolName: fnName, friendlyName, summary, resultSummary, args: fnArgs, result });
+ }
+
+ workingMessages.push({
+ role: "tool",
+ tool_name: fnName,
+ content: result,
+ });
+ }
+ } catch (err: unknown) {
+ if (err instanceof Error) {
+ const msg = err.message.toLowerCase();
+ if (msg.includes("net") || msg.includes("fetch") || msg.includes("load") || msg.includes("failed")) {
+ throw new Error(
+ `Cannot reach Ollama at ${ollamaUrl}. ` +
+ "On mobile, Ollama must be accessible over your network (not localhost). " +
+ "Set the Ollama URL to your computer's LAN IP (e.g. http://192.168.1.x:11434)."
+ );
+ }
+ throw new Error(`Chat request failed: ${err.message}`);
+ }
+ throw new Error("Chat request failed: unknown error.");
+ }
+ }
+
+ throw new Error("Tool calling loop exceeded maximum iterations.");
+}
+
+/**
+ * Desktop streaming: uses native fetch() for real token-by-token streaming.
+ */
+async function sendChatMessageStreamingDesktop(
+ opts: StreamingChatOptions,
+): Promise<string> {
const { ollamaUrl, model, messages, tools, app, onChunk, onToolCall, onCreateBubble, abortSignal } = opts;
const maxIterations = 10;
let iterations = 0;
const workingMessages = messages.map((m) => ({ ...m }));
- // Inject a system prompt when tools are available to guide the model
if (tools !== undefined && tools.length > 0) {
const systemPrompt: ChatMessage = {
role: "system",
@@ -283,7 +425,6 @@ export async function sendChatMessageStreaming(
while (iterations < maxIterations) {
iterations++;
- // Signal the UI to create a new bubble for this round
onCreateBubble();
const body: Record<string, unknown> = {
@@ -332,18 +473,15 @@ export async function sendChatMessageStreaming(
}
} catch (err: unknown) {
if (err instanceof DOMException && err.name === "AbortError") {
- // User cancelled — return whatever we accumulated
return content;
}
throw err;
}
- // If no tool calls, we're done
if (toolCalls.length === 0) {
return content;
}
- // Tool calling: append assistant message and execute tools
const assistantMsg: ChatMessage = {
role: "assistant",
content,
@@ -380,9 +518,6 @@ export async function sendChatMessageStreaming(
content: result,
});
}
-
- // Reset content for next streaming round
- // (tool call content was intermediate, next round streams the final answer)
}
throw new Error("Tool calling loop exceeded maximum iterations.");
diff --git a/src/settings-modal.ts b/src/settings-modal.ts
index 2daca89..4fb089f 100644
--- a/src/settings-modal.ts
+++ b/src/settings-modal.ts
@@ -14,7 +14,7 @@ export class SettingsModal extends Modal {
contentEl.empty();
contentEl.addClass("ai-organizer-settings-modal");
- this.setTitle("AI Organizer Settings");
+ this.setTitle("AI Settings");
// Ollama URL setting
new Setting(contentEl)
diff --git a/styles.css b/styles.css
index 0a4e53f..4aa810b 100644
--- a/styles.css
+++ b/styles.css
@@ -10,12 +10,14 @@
flex-direction: column;
overflow: hidden;
min-height: 0;
+ position: relative;
}
.ai-organizer-messages {
flex: 1;
overflow-y: auto;
padding: 8px;
+ padding-top: 52px;
display: flex;
flex-direction: column;
gap: 6px;
@@ -60,6 +62,8 @@
color: var(--text-error);
}
+/* ===== Input Row ===== */
+
.ai-organizer-input-row {
display: flex;
flex-direction: row;
@@ -84,52 +88,127 @@
outline: none;
}
-.ai-organizer-input-buttons {
+/* ===== FAB / Speed Dial ===== */
+
+.ai-organizer-fab {
+ position: absolute;
+ top: 8px;
+ right: 12px;
+ z-index: 10;
display: flex;
flex-direction: column;
- gap: 4px;
+ align-items: flex-end;
+ gap: 8px;
+ pointer-events: none;
+}
+
+.ai-organizer-fab > * {
+ pointer-events: auto;
}
-.ai-organizer-settings-btn {
+/* Main FAB trigger */
+.ai-organizer-fab-trigger {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ border: none;
+ background-color: var(--interactive-accent);
+ color: var(--text-on-accent);
+ cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
- padding: 4px;
- background: transparent;
- border: 1px solid var(--background-modifier-border);
- border-radius: 4px;
- color: var(--text-muted);
- cursor: pointer;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+ transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
+ background-color 0.2s;
}
-.ai-organizer-settings-btn:hover {
- color: var(--text-normal);
- background-color: var(--background-modifier-hover);
+.ai-organizer-fab-trigger:hover {
+ background-color: var(--interactive-accent-hover);
}
+.ai-organizer-fab-trigger svg {
+ width: 18px;
+ height: 18px;
+}
-.ai-organizer-tools-btn {
+/* Rotate trigger when FAB is open */
+.ai-organizer-fab:focus-within > .ai-organizer-fab-trigger {
+ transform: rotate(90deg);
+}
+
+/* FAB action items (hidden by default) */
+.ai-organizer-fab-action {
display: flex;
align-items: center;
- justify-content: center;
- padding: 4px;
- background: transparent;
- border: 1px solid var(--background-modifier-border);
+ gap: 8px;
+ visibility: hidden;
+ opacity: 0;
+ transform: scale(0.8);
+ transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
+ visibility 0.2s cubic-bezier(0.4, 0, 0.2, 1),
+ transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+/* Staggered delays */
+.ai-organizer-fab-action:nth-child(2) {
+ transition-delay: 30ms;
+}
+
+.ai-organizer-fab-action:nth-child(3) {
+ transition-delay: 60ms;
+}
+
+.ai-organizer-fab-action:nth-child(4) {
+ transition-delay: 90ms;
+}
+
+/* Reveal actions on focus-within */
+.ai-organizer-fab:focus-within > .ai-organizer-fab-action {
+ visibility: visible;
+ opacity: 1;
+ transform: scale(1);
+}
+
+/* FAB action label */
+.ai-organizer-fab-label {
+ padding: 3px 8px;
border-radius: 4px;
+ background-color: var(--background-secondary);
+ color: var(--text-normal);
+ font-size: 0.8em;
+ white-space: nowrap;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
+}
+
+/* FAB action button (circle) */
+.ai-organizer-fab-btn {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ border: 1px solid var(--background-modifier-border);
+ background-color: var(--background-secondary);
color: var(--text-muted);
cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
+ transition: background-color 0.2s, color 0.2s, border-color 0.2s;
}
-.ai-organizer-tools-btn:hover {
+.ai-organizer-fab-btn:hover {
color: var(--text-normal);
background-color: var(--background-modifier-hover);
}
-.ai-organizer-tools-btn.ai-organizer-tools-active {
- color: var(--interactive-accent);
- border-color: var(--interactive-accent);
+.ai-organizer-fab-btn svg {
+ width: 18px;
+ height: 18px;
}
+/* ===== Tool Call Bubbles ===== */
+
.ai-organizer-tool-call {
align-self: flex-start;
max-width: 85%;
@@ -175,21 +254,87 @@
font-size: 0.9em;
}
-.ai-organizer-tool-call-details {
+/* ===== DaisyUI-inspired Collapse ===== */
+
+.ai-organizer-collapse {
+ display: grid;
+ position: relative;
+ overflow: hidden;
+ border-radius: 6px;
+ width: 100%;
+ grid-template-rows: max-content 0fr;
+ grid-template-columns: minmax(0, 1fr);
+ transition: grid-template-rows 0.2s ease-out;
margin-top: 4px;
}
-.ai-organizer-tool-call-details > summary {
+.ai-organizer-collapse-toggle {
+ position: absolute;
+ opacity: 0;
+ width: 0;
+ height: 0;
+ pointer-events: none;
+}
+
+.ai-organizer-collapse-title {
+ grid-column-start: 1;
+ grid-row-start: 1;
+ position: relative;
+ width: 100%;
+ padding: 4px 28px 4px 0;
cursor: pointer;
color: var(--text-muted);
font-size: 0.9em;
user-select: none;
+ transition: color 0.15s;
}
-.ai-organizer-tool-call-details > summary:hover {
+.ai-organizer-collapse-title:hover {
color: var(--text-normal);
}
+/* Collapse arrow indicator */
+.ai-organizer-collapse-arrow > .ai-organizer-collapse-title::after {
+ content: "";
+ position: absolute;
+ top: 50%;
+ right: 8px;
+ width: 0.45em;
+ height: 0.45em;
+ border-right: 2px solid currentColor;
+ border-bottom: 2px solid currentColor;
+ transform: translateY(-75%) rotate(45deg);
+ transform-origin: 75% 75%;
+ transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+ pointer-events: none;
+}
+
+/* Arrow rotates when open */
+.ai-organizer-collapse-toggle:checked ~ .ai-organizer-collapse-title::after {
+ transform: translateY(-25%) rotate(225deg);
+}
+
+/* Expand grid when checked */
+.ai-organizer-collapse:has(.ai-organizer-collapse-toggle:checked) {
+ grid-template-rows: max-content 1fr;
+}
+
+.ai-organizer-collapse-content {
+ grid-column-start: 1;
+ grid-row-start: 2;
+ min-height: 0;
+ overflow: hidden;
+ transition: min-height 0.2s ease-out;
+}
+
+.ai-organizer-collapse-toggle:checked ~ .ai-organizer-collapse-content {
+ min-height: fit-content;
+}
+
+.ai-organizer-collapse-content-inner {
+ padding: 4px 0 6px 0;
+}
+
.ai-organizer-tool-call-args,
.ai-organizer-tool-call-result {
margin: 2px 0;
@@ -207,13 +352,14 @@
color: var(--text-muted);
}
+/* ===== Misc ===== */
+
.ai-organizer-tool-modal-desc {
color: var(--text-muted);
font-size: 0.9em;
margin-bottom: 8px;
}
-
.ai-organizer-stop-btn {
background-color: var(--text-error) !important;
color: var(--text-on-accent) !important;