feat: Chat-Detach richtig — Hauptfenster blendet Chat aus, separates Fenster lädt Session
Some checks failed
Build AppImage / build (push) Failing after 15s
Some checks failed
Build AppImage / build (push) Failing after 15s
- chatDetached Store: Hauptfenster blendet Chat-Pane aus wenn Fenster offen - Placeholder mit "Zurückholen"-Button statt leerem Pane - Backend sendet chat-detached/chat-reattached Events ans Hauptfenster - on_window_event(Destroyed): Automatisches Reattach wenn Fenster geschlossen - Chat-Window lädt aktive Session + Nachrichten aus DB beim Öffnen - Mehr Platz für andere Panels wenn Chat herausgelöst [appimage] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
51374720e7
commit
e87ac9ffc1
4 changed files with 121 additions and 7 deletions
|
|
@ -1,17 +1,22 @@
|
||||||
// Claude Desktop — Chat-Fenster herauslösen
|
// Claude Desktop — Chat-Fenster herauslösen
|
||||||
// Öffnet den Chat-Panel in einem separaten Fenster
|
// Öffnet den Chat-Panel in einem separaten Fenster.
|
||||||
|
// Sendet Events an das Hauptfenster damit es den Chat-Bereich aus-/einblendet.
|
||||||
|
|
||||||
use tauri::{AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};
|
use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindowBuilder};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn chat_window_open(app: AppHandle) -> Result<(), String> {
|
pub async fn chat_window_open(app: AppHandle) -> Result<(), String> {
|
||||||
|
// Falls Fenster schon existiert: anzeigen und fokussieren
|
||||||
if let Some(win) = app.get_webview_window("chat-detached") {
|
if let Some(win) = app.get_webview_window("chat-detached") {
|
||||||
win.show().map_err(|e| e.to_string())?;
|
win.show().map_err(|e| e.to_string())?;
|
||||||
win.set_focus().map_err(|e| e.to_string())?;
|
win.set_focus().map_err(|e| e.to_string())?;
|
||||||
|
// Event ans Hauptfenster: Chat ist herausgelöst
|
||||||
|
app.emit("chat-detached", ()).ok();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
WebviewWindowBuilder::new(&app, "chat-detached", WebviewUrl::App("/chat-window".into()))
|
// Neues Fenster erstellen
|
||||||
|
let win = WebviewWindowBuilder::new(&app, "chat-detached", WebviewUrl::App("/chat-window".into()))
|
||||||
.title("Claude \u{2014} Chat")
|
.title("Claude \u{2014} Chat")
|
||||||
.inner_size(800.0, 900.0)
|
.inner_size(800.0, 900.0)
|
||||||
.min_inner_size(500.0, 400.0)
|
.min_inner_size(500.0, 400.0)
|
||||||
|
|
@ -19,6 +24,17 @@ pub async fn chat_window_open(app: AppHandle) -> Result<(), String> {
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Event ans Hauptfenster: Chat ist herausgelöst
|
||||||
|
app.emit("chat-detached", ()).ok();
|
||||||
|
|
||||||
|
// Wenn das Chat-Fenster geschlossen wird → Hauptfenster benachrichtigen
|
||||||
|
let app_clone = app.clone();
|
||||||
|
win.on_window_event(move |event| {
|
||||||
|
if let tauri::WindowEvent::Destroyed = event {
|
||||||
|
app_clone.emit("chat-reattached", ()).ok();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,5 +43,8 @@ pub async fn chat_window_close(app: AppHandle) -> Result<(), String> {
|
||||||
if let Some(win) = app.get_webview_window("chat-detached") {
|
if let Some(win) = app.get_webview_window("chat-detached") {
|
||||||
win.close().map_err(|e| e.to_string())?;
|
win.close().map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
|
// Event wird automatisch durch on_window_event/Destroyed gesendet
|
||||||
|
// Zur Sicherheit aber auch manuell senden
|
||||||
|
app.emit("chat-reattached", ()).ok();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ export const permissions = writable<Permission[]>([]);
|
||||||
|
|
||||||
// UI-State
|
// UI-State
|
||||||
export const isProcessing = writable(false);
|
export const isProcessing = writable(false);
|
||||||
|
export const chatDetached = writable(false);
|
||||||
export const currentInput = writable('');
|
export const currentInput = writable('');
|
||||||
export const selectedAgentId = writable<string | null>(null);
|
export const selectedAgentId = writable<string | null>(null);
|
||||||
export const currentModel = writable('');
|
export const currentModel = writable('');
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,25 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PaneGroup, Pane, PaneResizer } from 'paneforge';
|
import { PaneGroup, Pane, PaneResizer } from 'paneforge';
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { listen } from '@tauri-apps/api/event';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { chatDetached } from '$lib/stores/app';
|
||||||
// Kritische Panels sofort laden (immer sichtbar)
|
// Kritische Panels sofort laden (immer sichtbar)
|
||||||
import SessionList from '$lib/components/SessionList.svelte';
|
import SessionList from '$lib/components/SessionList.svelte';
|
||||||
import ChatPanel from '$lib/components/ChatPanel.svelte';
|
import ChatPanel from '$lib/components/ChatPanel.svelte';
|
||||||
|
|
||||||
|
// Chat-Detach: Listener für Fenster-Events
|
||||||
|
onMount(async () => {
|
||||||
|
// Wenn das Chat-Fenster geschlossen wird → Chat wieder einblenden
|
||||||
|
await listen('chat-reattached', () => {
|
||||||
|
$chatDetached = false;
|
||||||
|
});
|
||||||
|
// Wenn das Chat-Fenster geöffnet wird → Chat ausblenden
|
||||||
|
await listen('chat-detached', () => {
|
||||||
|
$chatDetached = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Sekundäre Panels: Lazy-Load bei erstem Tab-Wechsel
|
// Sekundäre Panels: Lazy-Load bei erstem Tab-Wechsel
|
||||||
const lazyPanels = {
|
const lazyPanels = {
|
||||||
activity: () => import('$lib/components/ActivityPanel.svelte'),
|
activity: () => import('$lib/components/ActivityPanel.svelte'),
|
||||||
|
|
@ -69,9 +85,21 @@
|
||||||
</PaneResizer>
|
</PaneResizer>
|
||||||
|
|
||||||
<!-- Chat -->
|
<!-- Chat -->
|
||||||
<Pane defaultSize={35} minSize={15} class="panel">
|
{#if !$chatDetached}
|
||||||
<ChatPanel />
|
<Pane defaultSize={35} minSize={15} class="panel">
|
||||||
</Pane>
|
<ChatPanel />
|
||||||
|
</Pane>
|
||||||
|
{:else}
|
||||||
|
<Pane defaultSize={12} minSize={5} maxSize={20} class="panel">
|
||||||
|
<div class="detached-placeholder">
|
||||||
|
<span class="detached-icon">💬</span>
|
||||||
|
<p>Chat ist herausgelöst</p>
|
||||||
|
<button class="reattach-btn" onclick={() => invoke('chat_window_close')}>
|
||||||
|
Zurückholen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Pane>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<PaneResizer class="resizer">
|
<PaneResizer class="resizer">
|
||||||
<div class="resizer-line"></div>
|
<div class="resizer-line"></div>
|
||||||
|
|
@ -204,4 +232,44 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Detached-Chat Placeholder */
|
||||||
|
.detached-placeholder {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detached-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detached-placeholder p {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reattach-btn {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reattach-btn:hover {
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,35 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ChatPanel from '$lib/components/ChatPanel.svelte';
|
import ChatPanel from '$lib/components/ChatPanel.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { currentSessionId, setMessagesFromDb, type DbMessage } from '$lib/stores/app';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
document.documentElement.classList.add('detached-window');
|
document.documentElement.classList.add('detached-window');
|
||||||
|
|
||||||
|
// Aktive Session und Nachrichten laden (aus DB, nicht aus Store — der ist leer)
|
||||||
|
try {
|
||||||
|
interface Session {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
message_count: number;
|
||||||
|
token_input: number;
|
||||||
|
token_output: number;
|
||||||
|
cost_usd: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const session: Session | null = await invoke('get_active_session');
|
||||||
|
if (session) {
|
||||||
|
$currentSessionId = session.id;
|
||||||
|
const messages: DbMessage[] = await invoke('load_messages', { sessionId: session.id });
|
||||||
|
if (messages.length > 0) {
|
||||||
|
setMessagesFromDb(messages);
|
||||||
|
console.log(`💬 Chat-Window: ${messages.length} Nachrichten geladen`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Chat-Window: Session laden fehlgeschlagen:', err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue