feat: Chat-Panel herausloesbar + Expand-Button Fix [appimage]
All checks were successful
Build AppImage / build (push) Successful in 6m3s
All checks were successful
Build AppImage / build (push) Successful in 6m3s
- Neues Fenster fuer Chat-Panel (auf zweiten Monitor ziehbar) - Expand/Collapse nutzt jetzt $state Array statt Set (Svelte 5 Reaktivitaet) - Kein Collapse waehrend Streaming (verhindert unklickbare Buttons) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e0734303af
commit
2b7dfa6e37
4 changed files with 93 additions and 7 deletions
31
src-tauri/src/chat_window.rs
Normal file
31
src-tauri/src/chat_window.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Claude Desktop — Chat-Fenster herauslösen
|
||||||
|
// Öffnet den Chat-Panel in einem separaten Fenster
|
||||||
|
|
||||||
|
use tauri::{AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn chat_window_open(app: AppHandle) -> Result<(), String> {
|
||||||
|
if let Some(win) = app.get_webview_window("chat-detached") {
|
||||||
|
win.show().map_err(|e| e.to_string())?;
|
||||||
|
win.set_focus().map_err(|e| e.to_string())?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
WebviewWindowBuilder::new(&app, "chat-detached", WebviewUrl::App("/chat-window".into()))
|
||||||
|
.title("Claude \u{2014} Chat")
|
||||||
|
.inner_size(800.0, 900.0)
|
||||||
|
.min_inner_size(500.0, 400.0)
|
||||||
|
.center()
|
||||||
|
.build()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn chat_window_close(app: AppHandle) -> Result<(), String> {
|
||||||
|
if let Some(win) = app.get_webview_window("chat-detached") {
|
||||||
|
win.close().map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ mod session;
|
||||||
mod teaching;
|
mod teaching;
|
||||||
mod update;
|
mod update;
|
||||||
mod voice;
|
mod voice;
|
||||||
|
mod chat_window;
|
||||||
|
|
||||||
/// Initialisiert die App
|
/// Initialisiert die App
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
|
@ -145,6 +146,9 @@ pub fn run() {
|
||||||
teaching::presentation_close,
|
teaching::presentation_close,
|
||||||
teaching::presentation_send_slide,
|
teaching::presentation_send_slide,
|
||||||
teaching::presentation_clear,
|
teaching::presentation_clear,
|
||||||
|
// Chat-Fenster (herauslösen)
|
||||||
|
chat_window::chat_window_open,
|
||||||
|
chat_window::chat_window_close,
|
||||||
// Auto-Update
|
// Auto-Update
|
||||||
update::check_for_update,
|
update::check_for_update,
|
||||||
update::download_update,
|
update::download_update,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import CommandPalette from './CommandPalette.svelte';
|
import CommandPalette from './CommandPalette.svelte';
|
||||||
|
|
||||||
|
// Props
|
||||||
|
let { detached = false }: { detached?: boolean } = $props();
|
||||||
|
|
||||||
// Input-Referenz für Focus-Shortcuts
|
// Input-Referenz für Focus-Shortcuts
|
||||||
let inputTextarea: HTMLTextAreaElement;
|
let inputTextarea: HTMLTextAreaElement;
|
||||||
|
|
||||||
|
|
@ -25,15 +28,14 @@
|
||||||
|
|
||||||
// Collapse: Nachrichten mit > 25 Zeilen werden eingeklappt
|
// Collapse: Nachrichten mit > 25 Zeilen werden eingeklappt
|
||||||
const COLLAPSE_LINES = 25;
|
const COLLAPSE_LINES = 25;
|
||||||
let expandedMessages = new Set<string>();
|
let expandedMessages = $state<string[]>([]);
|
||||||
|
|
||||||
function toggleExpand(msgId: string) {
|
function toggleExpand(msgId: string) {
|
||||||
if (expandedMessages.has(msgId)) {
|
if (expandedMessages.includes(msgId)) {
|
||||||
expandedMessages.delete(msgId);
|
expandedMessages = expandedMessages.filter(id => id !== msgId);
|
||||||
} else {
|
} else {
|
||||||
expandedMessages.add(msgId);
|
expandedMessages = [...expandedMessages, msgId];
|
||||||
}
|
}
|
||||||
expandedMessages = expandedMessages;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldCollapse(content: string): boolean {
|
function shouldCollapse(content: string): boolean {
|
||||||
|
|
@ -819,6 +821,11 @@
|
||||||
<span class="token-count" class:warning={estimatedTokens > 20000} class:danger={estimatedTokens > TOKEN_WARNING_THRESHOLD}>
|
<span class="token-count" class:warning={estimatedTokens > 20000} class:danger={estimatedTokens > TOKEN_WARNING_THRESHOLD}>
|
||||||
~{(estimatedTokens / 1000).toFixed(1)}k Token
|
~{(estimatedTokens / 1000).toFixed(1)}k Token
|
||||||
</span>
|
</span>
|
||||||
|
{#if !detached}
|
||||||
|
<button class="detach-btn" onclick={() => invoke('chat_window_open')} title="Chat herausloesen">⧉</button>
|
||||||
|
{:else}
|
||||||
|
<button class="detach-btn" onclick={() => invoke('chat_window_close')} title="Zurueck ins Hauptfenster">⧇</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -878,7 +885,7 @@
|
||||||
</div>
|
</div>
|
||||||
{:else if message.role === 'assistant'}
|
{:else if message.role === 'assistant'}
|
||||||
{#if message.content}
|
{#if message.content}
|
||||||
{#if shouldCollapse(message.content) && !expandedMessages.has(message.id)}
|
{#if shouldCollapse(message.content) && !expandedMessages.includes(message.id) && !($isProcessing && isLastAssistantMessage(index))}
|
||||||
<div class="msg-collapsed">
|
<div class="msg-collapsed">
|
||||||
{@html renderMarkdown(message.content.split('\n').slice(0, COLLAPSE_LINES).join('\n') + '\n...')}
|
{@html renderMarkdown(message.content.split('\n').slice(0, COLLAPSE_LINES).join('\n') + '\n...')}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -887,7 +894,7 @@
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
{@html renderMarkdown(message.content)}
|
{@html renderMarkdown(message.content)}
|
||||||
{#if shouldCollapse(message.content)}
|
{#if shouldCollapse(message.content) && !($isProcessing && isLastAssistantMessage(index))}
|
||||||
<button class="expand-btn" onclick={() => toggleExpand(message.id)}>
|
<button class="expand-btn" onclick={() => toggleExpand(message.id)}>
|
||||||
▲ Einklappen
|
▲ Einklappen
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1154,6 +1161,24 @@
|
||||||
color: var(--error);
|
color: var(--error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detach-btn {
|
||||||
|
padding: 0.2rem 0.45rem;
|
||||||
|
background: var(--bg-tertiary, #2a2a3e);
|
||||||
|
border: 1px solid var(--border, #333);
|
||||||
|
border-radius: var(--radius-sm, 4px);
|
||||||
|
color: var(--text-secondary, #aaa);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detach-btn:hover {
|
||||||
|
background: var(--accent, #6c8aff);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--accent, #6c8aff);
|
||||||
|
}
|
||||||
|
|
||||||
.chat-messages {
|
.chat-messages {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
|
||||||
26
src/routes/chat-window/+page.svelte
Normal file
26
src/routes/chat-window/+page.svelte
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import ChatPanel from '$lib/components/ChatPanel.svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
document.documentElement.classList.add('detached-window');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="detached-chat">
|
||||||
|
<ChatPanel detached={true} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(html.detached-window) {
|
||||||
|
background: var(--bg-primary, #1a1a2e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detached-chat {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in a new issue