mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-19 17:24:57 +02:00
chat : add browser-style conversation tabs with a new-chat screen
Track open conversations as tabs above the chat, one per open chat, plus a single New chat tab for the bare `#/` route. New chat is just the `#/` screen - no temporary conversations - and its tab is dropped when navigating away. Sending the first message creates a real conversation and opens a tab for it. Assisted-by: pi
This commit is contained in:
committed by
GitHub
parent
f8386b0368
commit
1144652072
@@ -78,12 +78,7 @@
|
||||
const conversation = conversationsStore.activeConversation;
|
||||
|
||||
if (conversation) {
|
||||
// an empty new-chat tab is unsaved, so drop it without confirmation
|
||||
if (conversationsStore.isTemporaryConversation(conversation.id)) {
|
||||
conversationsStore.deleteConversation(conversation.id);
|
||||
} else {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,20 +3,24 @@
|
||||
import { page } from '$app/state';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { cn } from '$lib/components/ui/utils';
|
||||
import { chatStore, conversationsStore, tabsStore } from '$lib/stores';
|
||||
import { chatStore, conversationsStore, NEW_CHAT_TAB_ID, tabsStore } from '$lib/stores';
|
||||
|
||||
let activeId = $derived(page.params.id);
|
||||
let activeId = $derived(page.params.id ?? NEW_CHAT_TAB_ID);
|
||||
|
||||
let tabs = $derived(
|
||||
tabsStore.openTabs.map((id) => ({
|
||||
id,
|
||||
isNewChat: conversationsStore.isTemporaryConversation(id),
|
||||
isNewChat: id === NEW_CHAT_TAB_ID,
|
||||
name:
|
||||
conversationsStore.conversations.find((c) => c.id === id)?.name ??
|
||||
(conversationsStore.isTemporaryConversation(id) ? 'New chat' : 'Chat')
|
||||
id === NEW_CHAT_TAB_ID
|
||||
? 'New chat'
|
||||
: (conversationsStore.conversations.find((c) => c.id === id)?.name ?? 'Chat')
|
||||
}))
|
||||
);
|
||||
|
||||
// hide the New chat button when the new-chat tab is the only one open
|
||||
let showNewChatButton = $derived(tabsStore.openTabs.some((id) => id !== NEW_CHAT_TAB_ID));
|
||||
|
||||
let loadingIds = $derived(new Set(chatStore.getAllLoadingChats()));
|
||||
|
||||
function handleClose(id: string) {
|
||||
@@ -87,23 +91,25 @@
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<button
|
||||
{...props}
|
||||
class="backdrop-blur-lg flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-foreground/5 hover:text-foreground"
|
||||
onclick={() => conversationsStore.openNewChatTab()}
|
||||
aria-label="New chat"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
</button>
|
||||
{/snippet}
|
||||
</Tooltip.Trigger>
|
||||
{#if showNewChatButton}
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<button
|
||||
{...props}
|
||||
class="backdrop-blur-lg flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-foreground/5 hover:text-foreground"
|
||||
onclick={() => conversationsStore.openNewChat()}
|
||||
aria-label="New chat"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
</button>
|
||||
{/snippet}
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content>
|
||||
<p>New chat</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
<Tooltip.Content>
|
||||
<p>New chat</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
+2
-2
@@ -113,7 +113,7 @@
|
||||
item.action === 'new-chat'
|
||||
? () => {
|
||||
onNewChat?.();
|
||||
conversationsStore.openNewChatTab();
|
||||
conversationsStore.openNewChat();
|
||||
}
|
||||
: item.route
|
||||
? () => {
|
||||
@@ -167,7 +167,7 @@
|
||||
item.action === 'new-chat'
|
||||
? () => {
|
||||
onNewChat?.();
|
||||
conversationsStore.openNewChatTab();
|
||||
conversationsStore.openNewChat();
|
||||
}
|
||||
: item.route
|
||||
? () => {
|
||||
|
||||
@@ -33,7 +33,7 @@ export function useKeyboardShortcuts(callbacks: KeyboardShortcutsCallbacks) {
|
||||
) {
|
||||
event.preventDefault();
|
||||
|
||||
conversationsStore.openNewChatTab();
|
||||
conversationsStore.openNewChat();
|
||||
}
|
||||
|
||||
if (event.shiftKey && isCmdOrCtrl && event.key === KeyboardKey.E_UPPER) {
|
||||
|
||||
@@ -167,8 +167,6 @@ class ChatStore implements ChatStreamHost, ChatFlowsHost {
|
||||
if (!activeConv) {
|
||||
await conversationsStore.createConversation();
|
||||
activeConv = conversationsStore.activeConversation;
|
||||
} else if (conversationsStore.isTemporaryConversation(activeConv.id)) {
|
||||
await conversationsStore.persistTemporaryConversation(activeConv.id);
|
||||
}
|
||||
|
||||
if (!activeConv) return;
|
||||
@@ -646,9 +644,6 @@ class ChatStore implements ChatStreamHost, ChatFlowsHost {
|
||||
if (!activeConv) {
|
||||
await conversationsStore.createConversation();
|
||||
isNewConversation = true;
|
||||
} else if (conversationsStore.isTemporaryConversation(activeConv.id)) {
|
||||
await conversationsStore.persistTemporaryConversation(activeConv.id);
|
||||
isNewConversation = true;
|
||||
}
|
||||
|
||||
const currentConv = conversationsStore.activeConversation;
|
||||
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
type ConversationsPreferencesHost
|
||||
} from '$lib/stores/conversations/preferences.svelte';
|
||||
import { settingsStore } from '$lib/stores/settings/index.svelte';
|
||||
import { tabsStore } from '$lib/stores/tabs.svelte';
|
||||
import { filterByLeafNodeId, findLeafNode, generateConversationTitle, uuid } from '$lib/utils';
|
||||
import { NEW_CHAT_TAB_ID, tabsStore } from '$lib/stores/tabs.svelte';
|
||||
import { filterByLeafNodeId, findLeafNode, generateConversationTitle } from '$lib/utils';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
@@ -39,13 +39,6 @@ class ConversationsStore implements ConversationsPreferencesHost {
|
||||
/** Whether the store has been initialized */
|
||||
isInitialized = $state(false);
|
||||
|
||||
/**
|
||||
* Unsaved new-chat tabs. Each carries a temporary id used directly as the
|
||||
* route (`#/chat/<id>`); it is persisted to the database - and moved to
|
||||
* `conversations` - only when the first message is sent.
|
||||
*/
|
||||
temporaryConversations = $state<DatabaseConversation[]>([]);
|
||||
|
||||
/** Per-chat options (MCP overrides, reasoning effort, cwd), composed here. */
|
||||
private _preferences = new ConversationPreferences(this);
|
||||
|
||||
@@ -289,7 +282,6 @@ class ConversationsStore implements ConversationsPreferencesHost {
|
||||
|
||||
this.clearActiveConversation();
|
||||
this.conversations = [];
|
||||
this.temporaryConversations = [];
|
||||
tabsStore.clear();
|
||||
this.notifyConversationsDeleted(allIds);
|
||||
|
||||
@@ -307,20 +299,6 @@ class ConversationsStore implements ConversationsPreferencesHost {
|
||||
* @param convId - The conversation ID to delete
|
||||
*/
|
||||
async deleteConversation(convId: string, options?: { deleteWithForks?: boolean }): Promise<void> {
|
||||
// an unsaved new-chat tab is not in the DB; just drop it and close its tab
|
||||
if (this.isTemporaryConversation(convId)) {
|
||||
this.temporaryConversations = this.temporaryConversations.filter((c) => c.id !== convId);
|
||||
|
||||
if (this.activeConversation?.id === convId) {
|
||||
this.clearActiveConversation();
|
||||
await tabsStore.close(convId, convId);
|
||||
} else {
|
||||
tabsStore.removeTabs([convId]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await DatabaseService.deleteConversation(convId, options);
|
||||
|
||||
@@ -396,17 +374,6 @@ class ConversationsStore implements ConversationsPreferencesHost {
|
||||
ConversationTransferService.downloadConversationFile({ conv: conversation, messages });
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist the active conversation if it is an unsaved new-chat tab.
|
||||
*/
|
||||
async ensureActiveConversationPersisted(): Promise<void> {
|
||||
const active = this.activeConversation;
|
||||
|
||||
if (active && this.isTemporaryConversation(active.id)) {
|
||||
await this.persistTemporaryConversation(active.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of a message in active messages.
|
||||
*
|
||||
@@ -522,11 +489,6 @@ class ConversationsStore implements ConversationsPreferencesHost {
|
||||
return this.initPromise;
|
||||
}
|
||||
|
||||
/** True if the id refers to an unsaved new-chat tab */
|
||||
isTemporaryConversation(id: string): boolean {
|
||||
return this.temporaryConversations.some((c) => c.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specific conversation and its messages
|
||||
* @param convId - The conversation ID to load
|
||||
@@ -534,17 +496,6 @@ class ConversationsStore implements ConversationsPreferencesHost {
|
||||
*/
|
||||
async loadConversation(convId: string): Promise<boolean> {
|
||||
try {
|
||||
// unsaved new-chat tab: present in memory only
|
||||
const temp = this.temporaryConversations.find((c) => c.id === convId);
|
||||
|
||||
if (temp) {
|
||||
this.preferences.pendingCwd = null;
|
||||
this.activeConversation = temp;
|
||||
this.activeMessages = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const conversation = await DatabaseService.getConversation(convId);
|
||||
|
||||
if (!conversation) {
|
||||
@@ -635,36 +586,14 @@ class ConversationsStore implements ConversationsPreferencesHost {
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a fresh new-chat tab (an unsaved conversation) and navigate to it.
|
||||
* Used by the "New chat" actions (sidebar, keyboard, tab bar, search) and
|
||||
* prompt/model deep-links.
|
||||
* Start a fresh chat by navigating to the bare `#/` new-chat screen. The
|
||||
* chat layout opens a new-chat tab for it when Conversation tabs are on.
|
||||
*/
|
||||
async openNewChatTab(): Promise<string> {
|
||||
const conversation = this.createTemporaryConversation();
|
||||
async openNewChat(): Promise<string> {
|
||||
this.clearActiveConversation();
|
||||
await goto(ROUTES.START);
|
||||
|
||||
await goto(RouterService.chat(conversation.id));
|
||||
|
||||
return conversation.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist an unsaved new-chat tab to the database, keeping its id so the
|
||||
* route and tab stay stable. Called on the first message of a new chat.
|
||||
* @param convId - The temporary conversation id to persist
|
||||
*/
|
||||
async persistTemporaryConversation(convId: string): Promise<void> {
|
||||
const temp = this.temporaryConversations.find((c) => c.id === convId);
|
||||
|
||||
if (!temp) return;
|
||||
|
||||
// clone out of the $state proxy so IndexedDB serializes plain data
|
||||
const conversation = { ...temp };
|
||||
|
||||
await DatabaseService.createConversationWithId(conversation);
|
||||
|
||||
this.temporaryConversations = this.temporaryConversations.filter((c) => c.id !== convId);
|
||||
this.conversations = [conversation, ...this.conversations];
|
||||
this.activeConversation = conversation;
|
||||
return NEW_CHAT_TAB_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -813,30 +742,6 @@ class ConversationsStore implements ConversationsPreferencesHost {
|
||||
*
|
||||
*/
|
||||
|
||||
/** Update an unsaved new-chat tab in place (not persisted to the DB) */
|
||||
updateTemporaryConversation(id: string, updates: Partial<DatabaseConversation>): void {
|
||||
this.temporaryConversations = this.temporaryConversations.map((c) =>
|
||||
c.id === id ? { ...c, ...updates } : c
|
||||
);
|
||||
}
|
||||
|
||||
/** Build an unsaved new-chat conversation, baking in the pending cwd/effort */
|
||||
private createTemporaryConversation(): DatabaseConversation {
|
||||
const conversation: DatabaseConversation = {
|
||||
currNode: '',
|
||||
cwd: this.preferences.pendingCwd ?? undefined,
|
||||
id: uuid(),
|
||||
lastModified: Date.now(),
|
||||
name: 'New chat',
|
||||
reasoningEffort: this.preferences.pendingReasoningEffort
|
||||
};
|
||||
|
||||
this.preferences.pendingCwd = null;
|
||||
this.temporaryConversations = [...this.temporaryConversations, conversation];
|
||||
|
||||
return conversation;
|
||||
}
|
||||
|
||||
private notifyConversationsDeleted(convIds: string[]): void {
|
||||
if (convIds.length === 0) return;
|
||||
|
||||
|
||||
@@ -46,8 +46,6 @@ export interface ConversationsPreferencesHost {
|
||||
activeConversation: DatabaseConversation | null;
|
||||
conversations: DatabaseConversation[];
|
||||
applyConversationUpdate(id: string, updates: Partial<DatabaseConversation>): void;
|
||||
isTemporaryConversation(id: string): boolean;
|
||||
updateTemporaryConversation(id: string, updates: Partial<DatabaseConversation>): void;
|
||||
}
|
||||
|
||||
export class ConversationPreferences {
|
||||
@@ -160,14 +158,6 @@ export class ConversationPreferences {
|
||||
cwd: trimmed
|
||||
});
|
||||
|
||||
// unsaved new-chat tab: keep the pick in memory until it is persisted
|
||||
if (this.host.isTemporaryConversation(id)) {
|
||||
this.host.updateTemporaryConversation(id, { cwd: trimmed });
|
||||
this.pendingCwd = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await DatabaseService.updateConversation(id, {
|
||||
cwd: trimmed
|
||||
});
|
||||
@@ -221,13 +211,6 @@ export class ConversationPreferences {
|
||||
mcpServerOverrides: overrides
|
||||
});
|
||||
|
||||
// unsaved new-chat tab: keep the override in memory until it is persisted
|
||||
if (this.host.isTemporaryConversation(id)) {
|
||||
this.host.updateTemporaryConversation(id, { mcpServerOverrides: overrides });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await DatabaseService.updateConversation(id, {
|
||||
mcpServerOverrides: overrides
|
||||
});
|
||||
@@ -252,13 +235,6 @@ export class ConversationPreferences {
|
||||
reasoningEffort: effort
|
||||
});
|
||||
|
||||
// unsaved new-chat tab: keep the effort in memory until it is persisted
|
||||
if (this.host.isTemporaryConversation(id)) {
|
||||
this.host.updateTemporaryConversation(id, { reasoningEffort: effort });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await DatabaseService.updateConversation(id, {
|
||||
reasoningEffort: effort
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ export { chatStore } from './chat/index.svelte';
|
||||
export { draftMessagesStore } from './chat/drafts.svelte';
|
||||
|
||||
// CONVERSATION TABS
|
||||
export { tabsStore } from './tabs.svelte';
|
||||
export { NEW_CHAT_TAB_ID, tabsStore } from './tabs.svelte';
|
||||
|
||||
// CONTEXT STATS (active conversation context window usage)
|
||||
export { contextStatsStore } from './chat/context-stats.svelte';
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/**
|
||||
* tabsStore - Reactive State Store for Browser-Style Conversation Tabs
|
||||
*
|
||||
* Tracks which conversations and new-chat screens are open as tabs in the
|
||||
* chat layout, in order. Every tab - real conversation or unsaved new-chat
|
||||
* tab - is a `#/chat/<id>` route; new-chat tabs simply carry a temporary
|
||||
* conversation id that is only persisted to the database once a message is
|
||||
* sent (see conversationsStore.temporaryConversations).
|
||||
* Tracks which conversations and the new-chat screen are open as tabs in
|
||||
* the chat layout, in order. Real conversation tabs are `#/chat/<id>`
|
||||
* routes; the new-chat tab is the bare `#/` route, represented here by the
|
||||
* `NEW_CHAT_TAB_ID` sentinel (see {@link NEW_CHAT_TAB_ID}).
|
||||
*
|
||||
* **Architecture & Relationships:**
|
||||
* - **conversationsStore**: owns conversation data; calls `removeTabs()` /
|
||||
@@ -13,8 +12,8 @@
|
||||
* so there is no circular dependency - tab names are resolved by the
|
||||
* ChatTabs component from conversationsStore.
|
||||
* - Tab order persists to localStorage and is pruned against the loaded
|
||||
* conversation list on init. Unsaved new-chat tabs are dropped on reload
|
||||
* (they are not in the database), which matches browser behavior.
|
||||
* conversation list on init. The new-chat tab is dropped on reload (it is
|
||||
* not a conversation), which matches browser behavior.
|
||||
*/
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
@@ -23,16 +22,19 @@ import { CHAT_TABS_LOCALSTORAGE_KEY, ROUTES } from '$lib/constants';
|
||||
import { RouterService } from '$lib/services/router.service';
|
||||
import { untrack } from 'svelte';
|
||||
|
||||
/** Sentinel tab id for the bare `#/` new-chat screen */
|
||||
export const NEW_CHAT_TAB_ID = 'new-chat';
|
||||
|
||||
class TabsStore {
|
||||
/** Ordered tab ids: conversation ids and temporary new-chat ids */
|
||||
/** Ordered tab ids: conversation ids and the `NEW_CHAT_TAB_ID` sentinel */
|
||||
openTabs = $state<string[]>([]);
|
||||
|
||||
/** False until init() has read the persisted tabs; save() is a no-op before that */
|
||||
private initialized = false;
|
||||
|
||||
/** Navigate to a tab */
|
||||
/** Navigate to a tab (the new-chat sentinel maps to the bare `#/` route) */
|
||||
async activate(id: string): Promise<void> {
|
||||
await goto(RouterService.chat(id));
|
||||
await goto(id === NEW_CHAT_TAB_ID ? ROUTES.START : RouterService.chat(id));
|
||||
}
|
||||
|
||||
/** Remove all tabs (e.g. after deleting all conversations) */
|
||||
@@ -61,7 +63,7 @@ class TabsStore {
|
||||
const target = (idx > 0 ? this.openTabs[idx - 1] : this.openTabs[0]) ?? null;
|
||||
|
||||
if (target) {
|
||||
await goto(RouterService.chat(target));
|
||||
await goto(target === NEW_CHAT_TAB_ID ? ROUTES.START : RouterService.chat(target));
|
||||
} else {
|
||||
await goto(ROUTES.START);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { ChatScreen, ChatTabs } from '$lib/components/app';
|
||||
import { conversationsStore, tabsStore } from '$lib/stores';
|
||||
import { NEW_CHAT_TAB_ID, settingsStore, tabsStore } from '$lib/stores';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
// the new-chat screen is a temporary conversation (unsaved) or the bare `#/` route
|
||||
let showCenteredEmpty = $derived(
|
||||
page.params.id ? conversationsStore.isTemporaryConversation(page.params.id) : true
|
||||
// the new-chat screen is the bare `#/` route (no conversation id)
|
||||
let showCenteredEmpty = $derived(!page.params.id);
|
||||
|
||||
// tabs show whenever the Conversation tabs setting is enabled, except on the
|
||||
// bare `#/` new-chat view when no real conversation tab is open yet
|
||||
let showTabs = $derived(
|
||||
Boolean(settingsStore.config.conversationTabs) &&
|
||||
(page.params.id || tabsStore.openTabs.some((id) => id !== NEW_CHAT_TAB_ID))
|
||||
);
|
||||
|
||||
// tabs appear only on chat-id routes; the bare `#/` new-chat view has none
|
||||
let showTabs = $derived(!!page.params.id);
|
||||
|
||||
// any navigation to a conversation or new-chat tab opens a tab for it
|
||||
// any navigation to a conversation or the new-chat screen opens a tab for it
|
||||
$effect(() => {
|
||||
const id = page.params.id;
|
||||
const id = page.params.id ?? (page.route.id === '/(chat)' ? NEW_CHAT_TAB_ID : undefined);
|
||||
|
||||
if (id) {
|
||||
if (id && settingsStore.config.conversationTabs) {
|
||||
tabsStore.syncWithRoute(id);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
|
||||
await modelsStore.fetch();
|
||||
|
||||
// a prompt/model deep-link routes to a new-chat tab, which sends it;
|
||||
// otherwise `#/` is a plain new-chat view with no tabs
|
||||
// a prompt/model deep-link opens a new chat (tab or plain view, per the
|
||||
// Conversation tabs setting); otherwise `#/` is a plain new-chat view
|
||||
if (qParam !== null || modelParam !== null) {
|
||||
await conversationsStore.openNewChatTab();
|
||||
await conversationsStore.openNewChat();
|
||||
}
|
||||
|
||||
await modelsStore.ensureFirstModelSelected();
|
||||
|
||||
@@ -24,8 +24,10 @@
|
||||
deviceStore,
|
||||
mcpStore,
|
||||
modelsStore,
|
||||
NEW_CHAT_TAB_ID,
|
||||
serverStore,
|
||||
settingsStore,
|
||||
tabsStore,
|
||||
versionStore
|
||||
} from '$lib/stores';
|
||||
import { initStores } from '$lib/stores/init';
|
||||
@@ -96,10 +98,24 @@
|
||||
if (targetIdx >= 0 && targetIdx < allConvs.length) {
|
||||
goto(RouterService.chat(allConvs[targetIdx].id));
|
||||
} else {
|
||||
conversationsStore.openNewChatTab();
|
||||
conversationsStore.openNewChat();
|
||||
}
|
||||
}
|
||||
|
||||
// navigating away from the new-chat screen drops its tab, so it does not
|
||||
// linger once the user moves to a real conversation or another route
|
||||
let previousChatId = $state<string | undefined>(undefined);
|
||||
|
||||
$effect(() => {
|
||||
const id = page.params.id ?? (page.route.id === '/(chat)' ? NEW_CHAT_TAB_ID : undefined);
|
||||
const prev = previousChatId;
|
||||
|
||||
previousChatId = id;
|
||||
|
||||
if (id !== prev && prev && settingsStore.config.conversationTabs && prev === NEW_CHAT_TAB_ID) {
|
||||
untrack(() => tabsStore.removeTabs([NEW_CHAT_TAB_ID]));
|
||||
}
|
||||
});
|
||||
// Global keyboard shortcuts
|
||||
const { handleKeydown } = useKeyboardShortcuts({
|
||||
editActiveConversation: () => chatSidebar?.editActiveConversation?.(),
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
// in-place search, so bounce back to a new-chat tab.
|
||||
$effect(() => {
|
||||
if (browser && !deviceStore.isMobile) {
|
||||
conversationsStore.openNewChatTab();
|
||||
conversationsStore.openNewChat();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
if (history.length > 1) {
|
||||
history.back();
|
||||
} else {
|
||||
conversationsStore.openNewChatTab();
|
||||
conversationsStore.openNewChat();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user