refactor: Cleanup
This commit is contained in:
parent
3120a9fc94
commit
72ef132465
|
|
@ -10,7 +10,8 @@
|
|||
} from '$lib/components/app';
|
||||
import { INPUT_CLASSES } from '$lib/constants/css-classes';
|
||||
import { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config';
|
||||
import { MimeTypeText, SpecialFileType } from '$lib/enums';
|
||||
import { INITIAL_FILE_SIZE, PROMPT_CONTENT_SEPARATOR } from '$lib/constants/chat-form';
|
||||
import { ContentPartType, KeyboardKey, MimeTypeText, SpecialFileType } from '$lib/enums';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { modelOptions, selectedModelId } from '$lib/stores/models.svelte';
|
||||
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||
|
|
@ -232,13 +233,13 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Escape' && isPromptPickerOpen) {
|
||||
if (event.key === KeyboardKey.ESCAPE && isPromptPickerOpen) {
|
||||
isPromptPickerOpen = false;
|
||||
promptSearchQuery = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter' && !event.shiftKey && !isIMEComposing(event)) {
|
||||
if (event.key === KeyboardKey.ENTER && !event.shiftKey && !isIMEComposing(event)) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!canSubmit || disabled || isLoading || hasLoadingAttachments) return;
|
||||
|
|
@ -289,7 +290,7 @@
|
|||
name: att.name,
|
||||
size: att.content.length,
|
||||
type: SpecialFileType.MCP_PROMPT,
|
||||
file: new File([att.content], `${att.name}.txt`, { type: 'text/plain' }),
|
||||
file: new File([att.content], `${att.name}.txt`, { type: MimeTypeText.PLAIN }),
|
||||
isLoading: false,
|
||||
textContent: att.content,
|
||||
mcpPrompt: {
|
||||
|
|
@ -348,7 +349,7 @@
|
|||
const placeholder: ChatUploadedFile = {
|
||||
id: placeholderId,
|
||||
name: promptName,
|
||||
size: 0,
|
||||
size: INITIAL_FILE_SIZE,
|
||||
type: SpecialFileType.MCP_PROMPT,
|
||||
file: new File([], 'loading'),
|
||||
isLoading: true,
|
||||
|
|
@ -370,13 +371,13 @@
|
|||
if (typeof msg.content === 'string') {
|
||||
return msg.content;
|
||||
}
|
||||
if (msg.content.type === 'text') {
|
||||
if (msg.content.type === ContentPartType.TEXT) {
|
||||
return msg.content.text;
|
||||
}
|
||||
return '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n\n');
|
||||
.join(PROMPT_CONTENT_SEPARATOR);
|
||||
|
||||
uploadedFiles = uploadedFiles.map((f) =>
|
||||
f.id === placeholderId
|
||||
|
|
@ -385,7 +386,7 @@
|
|||
isLoading: false,
|
||||
textContent: promptText,
|
||||
size: promptText.length,
|
||||
file: new File([promptText], `${f.name}.txt`, { type: 'text/plain' })
|
||||
file: new File([promptText], `${f.name}.txt`, { type: MimeTypeText.PLAIN })
|
||||
}
|
||||
: f
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import { debounce } from '$lib/utils';
|
||||
import { KeyboardKey } from '$lib/enums';
|
||||
import type { MCPPromptInfo, GetPromptResult, MCPServerSettingsEntry } from '$lib/types';
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
import ChatFormPromptPickerList from './ChatFormPromptPickerList.svelte';
|
||||
|
|
@ -213,17 +214,17 @@
|
|||
|
||||
if (argSuggestions.length === 0 || activeAutocomplete !== argName) return;
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
if (event.key === KeyboardKey.ARROW_DOWN) {
|
||||
event.preventDefault();
|
||||
autocompleteIndex = Math.min(autocompleteIndex + 1, argSuggestions.length - 1);
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
} else if (event.key === KeyboardKey.ARROW_UP) {
|
||||
event.preventDefault();
|
||||
autocompleteIndex = Math.max(autocompleteIndex - 1, 0);
|
||||
} else if (event.key === 'Enter' && argSuggestions[autocompleteIndex]) {
|
||||
} else if (event.key === KeyboardKey.ENTER && argSuggestions[autocompleteIndex]) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
selectSuggestion(argName, argSuggestions[autocompleteIndex]);
|
||||
} else if (event.key === 'Escape') {
|
||||
} else if (event.key === KeyboardKey.ESCAPE) {
|
||||
event.preventDefault();
|
||||
suggestions[argName] = [];
|
||||
activeAutocomplete = null;
|
||||
|
|
@ -255,7 +256,7 @@
|
|||
export function handleKeydown(event: KeyboardEvent): boolean {
|
||||
if (!isOpen) return false;
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
if (event.key === KeyboardKey.ESCAPE) {
|
||||
event.preventDefault();
|
||||
if (selectedPrompt) {
|
||||
selectedPrompt = null;
|
||||
|
|
@ -267,7 +268,7 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
if (event.key === KeyboardKey.ARROW_DOWN) {
|
||||
event.preventDefault();
|
||||
if (selectedIndex < filteredPrompts.length - 1) {
|
||||
selectedIndex++;
|
||||
|
|
@ -276,7 +277,7 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
if (event.key === KeyboardKey.ARROW_UP) {
|
||||
event.preventDefault();
|
||||
if (selectedIndex > 0) {
|
||||
selectedIndex--;
|
||||
|
|
@ -285,7 +286,7 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter' && !selectedPrompt) {
|
||||
if (event.key === KeyboardKey.ENTER && !selectedPrompt) {
|
||||
event.preventDefault();
|
||||
if (filteredPrompts[selectedIndex]) {
|
||||
handlePromptClick(filteredPrompts[selectedIndex]);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
} from '$lib/components/app';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { Wrench, Loader2, AlertTriangle, Brain } from '@lucide/svelte';
|
||||
import { AgenticSectionType, AttachmentType } from '$lib/enums';
|
||||
import { AgenticSectionType, AttachmentType, FileTypeText } from '$lib/enums';
|
||||
import { formatJsonPretty } from '$lib/utils';
|
||||
import { ATTACHMENT_SAVED_REGEX } from '$lib/constants/agentic-ui';
|
||||
import { parseAgenticContent, type AgenticSection } from '$lib/utils/agentic';
|
||||
import type { DatabaseMessage, DatabaseMessageExtraImageFile } from '$lib/types/database';
|
||||
|
||||
|
|
@ -78,7 +79,7 @@
|
|||
): ToolResultLine[] {
|
||||
const lines = toolResult.split('\n');
|
||||
return lines.map((line) => {
|
||||
const match = line.match(/\[Attachment saved: ([^\]]+)\]/);
|
||||
const match = line.match(ATTACHMENT_SAVED_REGEX);
|
||||
if (!match || !extras) return { text: line };
|
||||
|
||||
const attachmentName = match[1];
|
||||
|
|
@ -124,7 +125,7 @@
|
|||
{#if section.toolArgs}
|
||||
<SyntaxHighlightedCode
|
||||
code={formatJsonPretty(section.toolArgs)}
|
||||
language="json"
|
||||
language={FileTypeText.JSON}
|
||||
maxHeight="20rem"
|
||||
class="text-xs"
|
||||
/>
|
||||
|
|
@ -162,7 +163,7 @@
|
|||
|
||||
<SyntaxHighlightedCode
|
||||
code={formatJsonPretty(section.toolArgs)}
|
||||
language="json"
|
||||
language={FileTypeText.JSON}
|
||||
maxHeight="20rem"
|
||||
class="text-xs"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { Button } from '$lib/components/ui/button';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import { INPUT_CLASSES } from '$lib/constants/css-classes';
|
||||
import { MessageRole } from '$lib/enums';
|
||||
import { MessageRole, KeyboardKey } from '$lib/enums';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||
|
|
@ -75,10 +75,10 @@
|
|||
let shouldBranchAfterEdit = $state(false);
|
||||
|
||||
function handleEditKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' && !event.shiftKey && !isIMEComposing(event)) {
|
||||
if (event.key === KeyboardKey.ENTER && !event.shiftKey && !isIMEComposing(event)) {
|
||||
event.preventDefault();
|
||||
editCtx.save();
|
||||
} else if (event.key === 'Escape') {
|
||||
} else if (event.key === KeyboardKey.ESCAPE) {
|
||||
event.preventDefault();
|
||||
editCtx.cancel();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import { Switch } from '$lib/components/ui/switch';
|
||||
import { ChatForm, DialogConfirmation } from '$lib/components/app';
|
||||
import { getMessageEditContext } from '$lib/contexts';
|
||||
import { KeyboardKey } from '$lib/enums';
|
||||
import { chatStore } from '$lib/stores/chat.svelte';
|
||||
import { processFilesToChatUploaded } from '$lib/utils/browser-only';
|
||||
|
||||
|
|
@ -34,7 +35,7 @@
|
|||
let canSubmit = $derived(editCtx.editedContent.trim().length > 0 || hasAttachments);
|
||||
|
||||
function handleGlobalKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') {
|
||||
if (event.key === KeyboardKey.ESCAPE) {
|
||||
event.preventDefault();
|
||||
attemptCancel();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { isIMEComposing } from '$lib/utils';
|
||||
import ChatMessageActions from './ChatMessageActions.svelte';
|
||||
import { MessageRole } from '$lib/enums';
|
||||
import { KeyboardKey, MessageRole } from '$lib/enums';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
|
|
@ -48,11 +48,11 @@
|
|||
const editCtx = getMessageEditContext();
|
||||
|
||||
function handleEditKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' && !event.shiftKey && !isIMEComposing(event)) {
|
||||
if (event.key === KeyboardKey.ENTER && !event.shiftKey && !isIMEComposing(event)) {
|
||||
event.preventDefault();
|
||||
|
||||
editCtx.save();
|
||||
} else if (event.key === 'Escape') {
|
||||
} else if (event.key === KeyboardKey.ESCAPE) {
|
||||
event.preventDefault();
|
||||
|
||||
editCtx.cancel();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
import * as Alert from '$lib/components/ui/alert';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import { INITIAL_SCROLL_DELAY } from '$lib/constants/auto-scroll';
|
||||
import { KeyboardKey } from '$lib/enums';
|
||||
import { createAutoScrollController } from '$lib/hooks/use-auto-scroll.svelte';
|
||||
import {
|
||||
chatStore,
|
||||
|
|
@ -211,7 +212,11 @@
|
|||
function handleKeydown(event: KeyboardEvent) {
|
||||
const isCtrlOrCmd = event.ctrlKey || event.metaKey;
|
||||
|
||||
if (isCtrlOrCmd && event.shiftKey && (event.key === 'd' || event.key === 'D')) {
|
||||
if (
|
||||
isCtrlOrCmd &&
|
||||
event.shiftKey &&
|
||||
(event.key === KeyboardKey.D_LOWER || event.key === KeyboardKey.D_UPPER)
|
||||
) {
|
||||
event.preventDefault();
|
||||
if (activeConversation()) {
|
||||
showDeleteDialog = true;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@
|
|||
import { rehypeResolveAttachmentImages } from '$lib/markdown/resolve-attachment-images';
|
||||
import { remarkLiteralHtml } from '$lib/markdown/literal-html';
|
||||
import { copyCodeToClipboard, preprocessLaTeX, getImageErrorFallbackHtml } from '$lib/utils';
|
||||
import {
|
||||
IMAGE_NOT_ERROR_BOUND_SELECTOR,
|
||||
DATA_ERROR_BOUND_ATTR,
|
||||
DATA_ERROR_HANDLED_ATTR,
|
||||
BOOL_TRUE_STRING
|
||||
} from '$lib/constants/markdown';
|
||||
import { UrlPrefix } from '$lib/enums';
|
||||
import {
|
||||
highlightCode,
|
||||
detectIncompleteCodeBlock,
|
||||
|
|
@ -470,10 +477,10 @@
|
|||
function setupImageErrorHandlers() {
|
||||
if (!containerRef) return;
|
||||
|
||||
const images = containerRef.querySelectorAll<HTMLImageElement>('img:not([data-error-bound])');
|
||||
const images = containerRef.querySelectorAll<HTMLImageElement>(IMAGE_NOT_ERROR_BOUND_SELECTOR);
|
||||
|
||||
for (const img of images) {
|
||||
img.dataset.errorBound = 'true';
|
||||
img.dataset[DATA_ERROR_BOUND_ATTR] = BOOL_TRUE_STRING;
|
||||
img.addEventListener('error', handleImageError);
|
||||
}
|
||||
}
|
||||
|
|
@ -487,8 +494,12 @@
|
|||
if (!img || !img.src) return;
|
||||
|
||||
// Don't handle data URLs or already-handled images
|
||||
if (img.src.startsWith('data:') || img.dataset.errorHandled === 'true') return;
|
||||
img.dataset.errorHandled = 'true';
|
||||
if (
|
||||
img.src.startsWith(UrlPrefix.DATA) ||
|
||||
img.dataset[DATA_ERROR_HANDLED_ATTR] === BOOL_TRUE_STRING
|
||||
)
|
||||
return;
|
||||
img.dataset[DATA_ERROR_HANDLED_ATTR] = BOOL_TRUE_STRING;
|
||||
|
||||
const src = img.src;
|
||||
// Create fallback element
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import type { Component } from 'svelte';
|
||||
import { KeyboardKey } from '$lib/enums';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
|
@ -29,7 +30,7 @@
|
|||
}: Props = $props();
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
if (event.key === KeyboardKey.ENTER) {
|
||||
event.preventDefault();
|
||||
onConfirm();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
</script>
|
||||
|
||||
<Card.Root class="grid gap-3 p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<Skeleton class="h-5 w-5 rounded" />
|
||||
<Skeleton class="h-5 w-28" />
|
||||
<Skeleton class="h-5 w-12 rounded-full" />
|
||||
</div>
|
||||
<Skeleton class="h-6 w-11 rounded-full" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<Skeleton class="h-5 w-14 rounded-full" />
|
||||
<Skeleton class="h-5 w-12 rounded-full" />
|
||||
<Skeleton class="h-5 w-16 rounded-full" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Skeleton class="h-4 w-40" />
|
||||
<Skeleton class="h-4 w-52" />
|
||||
</div>
|
||||
|
||||
<Skeleton class="h-3.5 w-36" />
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<Skeleton class="h-8 w-8 rounded" />
|
||||
<Skeleton class="h-8 w-8 rounded" />
|
||||
<Skeleton class="h-8 w-8 rounded" />
|
||||
</div>
|
||||
</Card.Root>
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
import { KeyValuePairs } from '$lib/components/app';
|
||||
import type { KeyValuePair } from '$lib/types';
|
||||
import { parseHeadersToArray, serializeHeaders } from '$lib/utils';
|
||||
import { UrlPrefix } from '$lib/enums';
|
||||
import { MCP_SERVER_URL_PLACEHOLDER } from '$lib/constants/mcp-form';
|
||||
|
||||
interface Props {
|
||||
url: string;
|
||||
|
|
@ -28,7 +30,8 @@
|
|||
}: Props = $props();
|
||||
|
||||
let isWebSocket = $derived(
|
||||
url.toLowerCase().startsWith('ws://') || url.toLowerCase().startsWith('wss://')
|
||||
url.toLowerCase().startsWith(UrlPrefix.WEBSOCKET) ||
|
||||
url.toLowerCase().startsWith(UrlPrefix.WEBSOCKET_SECURE)
|
||||
);
|
||||
|
||||
let headerPairs = $derived<KeyValuePair[]>(parseHeadersToArray(headers));
|
||||
|
|
@ -48,7 +51,7 @@
|
|||
<Input
|
||||
id="server-url-{id}"
|
||||
type="url"
|
||||
placeholder="https://mcp.example.com/sse"
|
||||
placeholder={MCP_SERVER_URL_PLACEHOLDER}
|
||||
value={url}
|
||||
oninput={(e) => onUrlChange(e.currentTarget.value)}
|
||||
class={urlError ? 'border-destructive' : ''}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@
|
|||
import { getFaviconUrl } from '$lib/utils';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { McpServerCard, McpServerForm } from '$lib/components/app/mcp';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import { McpServerCard, McpServerCardSkeleton, McpServerForm } from '$lib/components/app/mcp';
|
||||
|
||||
let servers = $derived(mcpStore.getServersSorted());
|
||||
|
||||
|
|
@ -135,35 +134,7 @@
|
|||
<div class="space-y-3">
|
||||
{#each servers as server (server.id)}
|
||||
{#if !initialLoadComplete}
|
||||
<Card.Root class="grid gap-3 p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<Skeleton class="h-5 w-5 rounded" />
|
||||
<Skeleton class="h-5 w-28" />
|
||||
<Skeleton class="h-5 w-12 rounded-full" />
|
||||
</div>
|
||||
<Skeleton class="h-6 w-11 rounded-full" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<Skeleton class="h-5 w-14 rounded-full" />
|
||||
<Skeleton class="h-5 w-12 rounded-full" />
|
||||
<Skeleton class="h-5 w-16 rounded-full" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Skeleton class="h-4 w-40" />
|
||||
<Skeleton class="h-4 w-52" />
|
||||
</div>
|
||||
|
||||
<Skeleton class="h-3.5 w-36" />
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<Skeleton class="h-8 w-8 rounded" />
|
||||
<Skeleton class="h-8 w-8 rounded" />
|
||||
<Skeleton class="h-8 w-8 rounded" />
|
||||
</div>
|
||||
</Card.Root>
|
||||
<McpServerCardSkeleton />
|
||||
{:else}
|
||||
<McpServerCard
|
||||
{server}
|
||||
|
|
|
|||
|
|
@ -204,6 +204,9 @@ export { default as McpServerCardEditForm } from './McpServerCard/McpServerCardE
|
|||
/** Delete confirmation dialog with server name display. */
|
||||
export { default as McpServerCardDeleteDialog } from './McpServerCard/McpServerCardDeleteDialog.svelte';
|
||||
|
||||
/** Skeleton loading state for server card during health checks. */
|
||||
export { default as McpServerCardSkeleton } from './McpServerCardSkeleton.svelte';
|
||||
|
||||
/**
|
||||
* **McpServerInfo** - Server instructions display
|
||||
*
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
routerModels,
|
||||
singleModelName
|
||||
} from '$lib/stores/models.svelte';
|
||||
import { ServerModelStatus } from '$lib/enums';
|
||||
import { KeyboardKey, ServerModelStatus } from '$lib/enums';
|
||||
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||
import {
|
||||
DialogModelInformation,
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
function handleSearchKeyDown(event: KeyboardEvent) {
|
||||
if (event.isComposing) return;
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
if (event.key === KeyboardKey.ARROW_DOWN) {
|
||||
event.preventDefault();
|
||||
if (filteredOptions.length === 0) return;
|
||||
|
||||
|
|
@ -142,7 +142,7 @@
|
|||
} else {
|
||||
highlightedIndex += 1;
|
||||
}
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
} else if (event.key === KeyboardKey.ARROW_UP) {
|
||||
event.preventDefault();
|
||||
if (filteredOptions.length === 0) return;
|
||||
|
||||
|
|
@ -151,7 +151,7 @@
|
|||
} else {
|
||||
highlightedIndex -= 1;
|
||||
}
|
||||
} else if (event.key === 'Enter') {
|
||||
} else if (event.key === KeyboardKey.ENTER) {
|
||||
event.preventDefault();
|
||||
if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {
|
||||
const option = filteredOptions[highlightedIndex];
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import { serverStore, serverLoading } from '$lib/stores/server.svelte';
|
||||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||
import { fade, fly, scale } from 'svelte/transition';
|
||||
import { KeyboardKey } from '$lib/enums';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
|
|
@ -117,7 +118,7 @@
|
|||
}
|
||||
|
||||
function handleApiKeyKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
if (event.key === KeyboardKey.ENTER) {
|
||||
handleSaveApiKey();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export const ATTACHMENT_SAVED_REGEX = /\[Attachment saved: ([^\]]+)\]/;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export const INITIAL_FILE_SIZE = 0;
|
||||
export const PROMPT_CONTENT_SEPARATOR = '\n\n';
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export const IMAGE_NOT_ERROR_BOUND_SELECTOR = 'img:not([data-error-bound])';
|
||||
export const DATA_ERROR_BOUND_ATTR = 'errorBound';
|
||||
export const DATA_ERROR_HANDLED_ATTR = 'errorHandled';
|
||||
export const BOOL_TRUE_STRING = 'true';
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const MCP_SERVER_URL_PLACEHOLDER = 'https://mcp.example.com/sse';
|
||||
|
|
@ -44,3 +44,5 @@ export { ServerRole, ServerModelStatus } from './server';
|
|||
export { ParameterSource, SyncableParameterType } from './settings';
|
||||
|
||||
export { ColorMode, McpPromptVariant, UrlPrefix } from './ui';
|
||||
|
||||
export { KeyboardKey } from './keyboard';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Keyboard key names for event handling
|
||||
*/
|
||||
export enum KeyboardKey {
|
||||
ENTER = 'Enter',
|
||||
ESCAPE = 'Escape',
|
||||
ARROW_UP = 'ArrowUp',
|
||||
ARROW_DOWN = 'ArrowDown',
|
||||
TAB = 'Tab',
|
||||
D_LOWER = 'd',
|
||||
D_UPPER = 'D',
|
||||
E_UPPER = 'E',
|
||||
K_LOWER = 'k',
|
||||
O_UPPER = 'O'
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { modelsStore } from '$lib/stores/models.svelte';
|
||||
import { TOOLTIP_DELAY_DURATION } from '$lib/constants/tooltip-config';
|
||||
import { KeyboardKey } from '$lib/enums';
|
||||
import { IsMobile } from '$lib/hooks/is-mobile.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
|
@ -43,7 +44,7 @@
|
|||
function handleKeydown(event: KeyboardEvent) {
|
||||
const isCtrlOrCmd = event.ctrlKey || event.metaKey;
|
||||
|
||||
if (isCtrlOrCmd && event.key === 'k') {
|
||||
if (isCtrlOrCmd && event.key === KeyboardKey.K_LOWER) {
|
||||
event.preventDefault();
|
||||
if (chatSidebar?.activateSearchMode) {
|
||||
chatSidebar.activateSearchMode();
|
||||
|
|
@ -51,12 +52,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (isCtrlOrCmd && event.shiftKey && event.key === 'O') {
|
||||
if (isCtrlOrCmd && event.shiftKey && event.key === KeyboardKey.O_UPPER) {
|
||||
event.preventDefault();
|
||||
goto('?new_chat=true#/');
|
||||
}
|
||||
|
||||
if (event.shiftKey && isCtrlOrCmd && event.key === 'E') {
|
||||
if (event.shiftKey && isCtrlOrCmd && event.key === KeyboardKey.E_UPPER) {
|
||||
event.preventDefault();
|
||||
|
||||
if (chatSidebar?.editActiveConversation) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue