generated from nhcarrigan/template
feat: stuffy feature bundle #159
@@ -1082,17 +1082,37 @@ fn process_json_line(
|
|||||||
stats.write().increment_code_blocks();
|
stats.write().increment_code_blocks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let is_prompt_too_long = text.starts_with("Prompt is too long");
|
||||||
|
|
||||||
let _ = app.emit(
|
let _ = app.emit(
|
||||||
"claude:output",
|
"claude:output",
|
||||||
OutputEvent {
|
OutputEvent {
|
||||||
line_type: "assistant".to_string(),
|
line_type: if is_prompt_too_long {
|
||||||
|
"error".to_string()
|
||||||
|
} else {
|
||||||
|
"assistant".to_string()
|
||||||
|
},
|
||||||
content: text.clone(),
|
content: text.clone(),
|
||||||
tool_name: None,
|
tool_name: None,
|
||||||
conversation_id: conversation_id.clone(),
|
conversation_id: conversation_id.clone(),
|
||||||
cost: message_cost.clone(), // Include cost with assistant text
|
cost: message_cost.clone(),
|
||||||
parent_tool_use_id: parent_tool_use_id.clone(),
|
parent_tool_use_id: parent_tool_use_id.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if is_prompt_too_long {
|
||||||
|
let _ = app.emit(
|
||||||
|
"claude:output",
|
||||||
|
OutputEvent {
|
||||||
|
line_type: "compact-prompt".to_string(),
|
||||||
|
content: String::new(),
|
||||||
|
tool_name: None,
|
||||||
|
conversation_id: conversation_id.clone(),
|
||||||
|
cost: None,
|
||||||
|
parent_tool_use_id: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ContentBlock::Thinking { thinking } => {
|
ContentBlock::Thinking { thinking } => {
|
||||||
state = CharacterState::Thinking;
|
state = CharacterState::Thinking;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { claudeStore, type TerminalLine } from "$lib/stores/claude";
|
import { claudeStore, type TerminalLine } from "$lib/stores/claude";
|
||||||
import { afterUpdate, tick, onMount, onDestroy } from "svelte";
|
import { afterUpdate, tick, onMount, onDestroy } from "svelte";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
import ConversationTabs from "./ConversationTabs.svelte";
|
import ConversationTabs from "./ConversationTabs.svelte";
|
||||||
import Markdown from "./Markdown.svelte";
|
import Markdown from "./Markdown.svelte";
|
||||||
@@ -95,6 +96,8 @@
|
|||||||
return "terminal-thinking";
|
return "terminal-thinking";
|
||||||
case "rate-limit":
|
case "rate-limit":
|
||||||
return "terminal-rate-limit";
|
return "terminal-rate-limit";
|
||||||
|
case "compact-prompt":
|
||||||
|
return "terminal-compact-prompt";
|
||||||
default:
|
default:
|
||||||
return "terminal-default";
|
return "terminal-default";
|
||||||
}
|
}
|
||||||
@@ -193,6 +196,11 @@
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleCompact() {
|
||||||
|
if (!currentConversationId) return;
|
||||||
|
await invoke("send_prompt", { conversationId: currentConversationId, message: "/compact" });
|
||||||
|
}
|
||||||
|
|
||||||
// Collapsible tool lines
|
// Collapsible tool lines
|
||||||
const TOOL_COLLAPSE_THRESHOLD = 60;
|
const TOOL_COLLAPSE_THRESHOLD = 60;
|
||||||
let expandedToolLines: Record<string, boolean> = {};
|
let expandedToolLines: Record<string, boolean> = {};
|
||||||
@@ -283,7 +291,11 @@
|
|||||||
{#if line.toolName}
|
{#if line.toolName}
|
||||||
<span class="terminal-tool-name mr-2">[{line.toolName}]</span>
|
<span class="terminal-tool-name mr-2">[{line.toolName}]</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if line.type === "assistant" || line.type === "user"}
|
{#if line.type === "compact-prompt"}
|
||||||
|
<button class="compact-action-btn" onclick={handleCompact}>
|
||||||
|
⚡ Compact Conversation
|
||||||
|
</button>
|
||||||
|
{:else if line.type === "assistant" || line.type === "user"}
|
||||||
<div class="message-content-wrapper">
|
<div class="message-content-wrapper">
|
||||||
<Markdown
|
<Markdown
|
||||||
content={maskPaths(line.content, hidePaths)}
|
content={maskPaths(line.content, hidePaths)}
|
||||||
@@ -370,6 +382,30 @@
|
|||||||
color: var(--terminal-rate-limit, #fb923c);
|
color: var(--terminal-rate-limit, #fb923c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.terminal-compact-prompt {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-action-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4em;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--terminal-error, #f87171);
|
||||||
|
color: var(--terminal-error, #f87171);
|
||||||
|
padding: 0.3em 0.8em;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-action-btn:hover {
|
||||||
|
background: color-mix(in srgb, var(--terminal-error, #f87171) 15%, transparent);
|
||||||
|
color: var(--terminal-error, #f87171);
|
||||||
|
}
|
||||||
|
|
||||||
.terminal-default {
|
.terminal-default {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ function getLineClass(type: string): string {
|
|||||||
return "terminal-thinking";
|
return "terminal-thinking";
|
||||||
case "rate-limit":
|
case "rate-limit":
|
||||||
return "terminal-rate-limit";
|
return "terminal-rate-limit";
|
||||||
|
case "compact-prompt":
|
||||||
|
return "terminal-compact-prompt";
|
||||||
default:
|
default:
|
||||||
return "terminal-default";
|
return "terminal-default";
|
||||||
}
|
}
|
||||||
@@ -110,6 +112,10 @@ describe("getLineClass", () => {
|
|||||||
expect(getLineClass("rate-limit")).toBe("terminal-rate-limit");
|
expect(getLineClass("rate-limit")).toBe("terminal-rate-limit");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns terminal-compact-prompt for compact-prompt lines", () => {
|
||||||
|
expect(getLineClass("compact-prompt")).toBe("terminal-compact-prompt");
|
||||||
|
});
|
||||||
|
|
||||||
it("returns terminal-default for unknown line types", () => {
|
it("returns terminal-default for unknown line types", () => {
|
||||||
expect(getLineClass("unknown")).toBe("terminal-default");
|
expect(getLineClass("unknown")).toBe("terminal-default");
|
||||||
expect(getLineClass("")).toBe("terminal-default");
|
expect(getLineClass("")).toBe("terminal-default");
|
||||||
@@ -142,6 +148,10 @@ describe("getLinePrefix", () => {
|
|||||||
expect(getLinePrefix("rate-limit")).toBe("[rate-limit]");
|
expect(getLinePrefix("rate-limit")).toBe("[rate-limit]");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns empty string for compact-prompt lines (button renders instead)", () => {
|
||||||
|
expect(getLinePrefix("compact-prompt")).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
it("returns empty string for thinking lines (no prefix)", () => {
|
it("returns empty string for thinking lines (no prefix)", () => {
|
||||||
expect(getLinePrefix("thinking")).toBe("");
|
expect(getLinePrefix("thinking")).toBe("");
|
||||||
});
|
});
|
||||||
|
|||||||
+18
-2
@@ -323,7 +323,15 @@ export async function initializeTauriListeners() {
|
|||||||
if (conversation_id) {
|
if (conversation_id) {
|
||||||
claudeStore.addLineToConversation(
|
claudeStore.addLineToConversation(
|
||||||
conversation_id,
|
conversation_id,
|
||||||
line_type as "user" | "assistant" | "system" | "tool" | "error" | "thinking" | "rate-limit",
|
line_type as
|
||||||
|
| "user"
|
||||||
|
| "assistant"
|
||||||
|
| "system"
|
||||||
|
| "tool"
|
||||||
|
| "error"
|
||||||
|
| "thinking"
|
||||||
|
| "rate-limit"
|
||||||
|
| "compact-prompt",
|
||||||
content,
|
content,
|
||||||
tool_name || undefined,
|
tool_name || undefined,
|
||||||
costData,
|
costData,
|
||||||
@@ -332,7 +340,15 @@ export async function initializeTauriListeners() {
|
|||||||
} else {
|
} else {
|
||||||
// Fallback to active conversation if no conversation_id provided
|
// Fallback to active conversation if no conversation_id provided
|
||||||
claudeStore.addLine(
|
claudeStore.addLine(
|
||||||
line_type as "user" | "assistant" | "system" | "tool" | "error" | "thinking" | "rate-limit",
|
line_type as
|
||||||
|
| "user"
|
||||||
|
| "assistant"
|
||||||
|
| "system"
|
||||||
|
| "tool"
|
||||||
|
| "error"
|
||||||
|
| "thinking"
|
||||||
|
| "rate-limit"
|
||||||
|
| "compact-prompt",
|
||||||
content,
|
content,
|
||||||
tool_name || undefined,
|
tool_name || undefined,
|
||||||
costData,
|
costData,
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
export interface TerminalLine {
|
export interface TerminalLine {
|
||||||
id: string;
|
id: string;
|
||||||
type: "user" | "assistant" | "system" | "tool" | "error" | "thinking" | "rate-limit";
|
type:
|
||||||
|
| "user"
|
||||||
|
| "assistant"
|
||||||
|
| "system"
|
||||||
|
| "tool"
|
||||||
|
| "error"
|
||||||
|
| "thinking"
|
||||||
|
| "rate-limit"
|
||||||
|
| "compact-prompt";
|
||||||
content: string;
|
content: string;
|
||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
toolName?: string;
|
toolName?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user