mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-08-15 20:18:33 +02:00
ui: Constants refactor (#26908)
* refactor: Constants * refactor: Constants/Enums cleanup * refactor: Constant objects instead of multiple single value constants * refactor: Cleanup constants
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { writeThemeFavicons } from './scripts/favicon-colorize';
|
||||
import { FAVICON_COLORS, PWA_ASSET_GENERATOR } from './src/lib/constants/pwa';
|
||||
import { FAVICON_COLORS, PWA_ASSET_GENERATOR } from './src/lib/constants/pwa.constants';
|
||||
import { defineConfig } from '@vite-pwa/assets-generator/config';
|
||||
|
||||
writeThemeFavicons(FAVICON_COLORS.LIGHT, FAVICON_COLORS.DARK, {
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
PWA_ASSET_GENERATOR,
|
||||
PWA_GENERATOR_DEVICES,
|
||||
THEME_COLORS
|
||||
} from './src/lib/constants/pwa';
|
||||
} from './src/lib/constants/pwa.constants';
|
||||
import { SplashOrientation } from './src/lib/enums/splash.enums';
|
||||
import {
|
||||
combinePresetAndAppleSplashScreens,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BUILD_CONFIG } from '../src/lib/constants/pwa';
|
||||
import { BUILD_CONFIG } from '../src/lib/constants/pwa.constants';
|
||||
import { existsSync, writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'path';
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BUILD_CONFIG } from '../src/lib/constants/pwa';
|
||||
import { BUILD_CONFIG } from '../src/lib/constants/pwa.constants';
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'path';
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { NEWLINE, TAB } from '../src/lib/constants/code';
|
||||
import { APPLE_DEVICES, BUILD_CONFIG, REGEX_PATTERNS, SPLASH_LINK } from '../src/lib/constants/pwa';
|
||||
import {
|
||||
APPLE_DEVICES,
|
||||
BUILD_CONFIG,
|
||||
REGEX_PATTERNS,
|
||||
SPLASH_LINK
|
||||
} from '../src/lib/constants/pwa.constants';
|
||||
import { NEWLINE, TAB } from '../src/lib/constants/special-characters.constants';
|
||||
import { SplashOrientation } from '../src/lib/enums/splash.enums';
|
||||
import type { SplashDimensions } from '../src/lib/types';
|
||||
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import ActionIcon from './ActionIcon.svelte';
|
||||
import { Copy } from '@lucide/svelte';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
|
||||
export let ariaLabel: string = 'Copy to clipboard';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Music, Video, X } from '@lucide/svelte';
|
||||
import { ActionIcon } from '$lib/components/app';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { AttachmentType } from '$lib/enums';
|
||||
import {
|
||||
formatFileSize,
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
import { SyntaxHighlightedCode } from '$lib/components/app';
|
||||
import * as Alert from '$lib/components/ui/alert';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { PdfViewMode } from '$lib/enums';
|
||||
import type { ChatAttachmentDisplayItem } from '$lib/types';
|
||||
import { getLanguageFromFilename } from '$lib/utils';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { FileText, Music, Video } from '@lucide/svelte';
|
||||
import { HorizontalScrollCarousel } from '$lib/components/app/misc';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
|
||||
interface PreviewItem {
|
||||
id: string;
|
||||
|
||||
+1
-2
@@ -2,8 +2,7 @@
|
||||
import { Plus } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { ATTACHMENT_TOOLTIP_TEXT } from '$lib/constants';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ATTACHMENT_TOOLTIP_TEXT, ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean;
|
||||
|
||||
+1
-1
@@ -12,9 +12,9 @@
|
||||
import {
|
||||
ATTACHMENT_FILE_ITEMS,
|
||||
ATTACHMENT_TOOLTIP_TEXT,
|
||||
ICON_CLASS_DEFAULT,
|
||||
TOOLTIP_DELAY_DURATION
|
||||
} from '$lib/constants';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { useAttachmentMenu } from '$lib/hooks/use-attachment-menu.svelte';
|
||||
|
||||
interface Props {
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@
|
||||
import { DropdownMenuSearchable, McpLogo, McpServerIdentity } from '$lib/components/app';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ROUTES } from '$lib/constants/routes';
|
||||
import { ICON_CLASS_DEFAULT, ROUTES } from '$lib/constants';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
import { Check, Info, Lightbulb, LightbulbOff } from '@lucide/svelte';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { useReasoningMenu } from '$lib/hooks/use-reasoning-menu.svelte';
|
||||
|
||||
const reasoning = useReasoningMenu();
|
||||
|
||||
+5
-3
@@ -14,9 +14,11 @@
|
||||
import * as Sheet from '$lib/components/ui/sheet';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { TOOLTIP_DELAY_DURATION } from '$lib/constants';
|
||||
import { ATTACHMENT_FILE_ITEMS } from '$lib/constants/attachment-menu';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import {
|
||||
ATTACHMENT_FILE_ITEMS,
|
||||
ICON_CLASS_DEFAULT,
|
||||
TOOLTIP_DELAY_DURATION
|
||||
} from '$lib/constants';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { AttachmentAction } from '$lib/enums/attachment.enums';
|
||||
import { useAttachmentMenu } from '$lib/hooks/use-attachment-menu.svelte';
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@
|
||||
import * as Collapsible from '$lib/components/ui/collapsible';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { CLI_FLAGS } from '$lib/constants';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { CLI_FLAGS, ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { useToolsPanel } from '$lib/hooks/use-tools-panel.svelte';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import { toolsStore } from '$lib/stores/tools.svelte';
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
import { Mic, Square } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
|
||||
+1
-2
@@ -10,8 +10,7 @@
|
||||
ChatFormContextGauge
|
||||
} from '$lib/components/app';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ROUTES } from '$lib/constants/routes';
|
||||
import { ICON_CLASS_DEFAULT, ROUTES } from '$lib/constants';
|
||||
import { FileTypeCategory, MessageRole } from '$lib/enums';
|
||||
import { ChatService } from '$lib/services';
|
||||
import {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { TRIM_LEADING_PADDING_REGEX, TRIM_TRAILING_PADDING_REGEX } from '$lib/constants';
|
||||
import { CODE_BLOCK } from '$lib/constants';
|
||||
import { ColorMode } from '$lib/enums';
|
||||
import { isMobile } from '$lib/stores/viewport.svelte';
|
||||
import type { ContentToken, SourceHistoryEntry } from '$lib/utils';
|
||||
@@ -105,8 +105,8 @@
|
||||
const prefix = open[0];
|
||||
const language = open[1].trim().split(/\s+/)[0] ?? '';
|
||||
const content = segment.slice(prefix.length, -3);
|
||||
const leading = content.match(TRIM_LEADING_PADDING_REGEX)?.[0] ?? '';
|
||||
const trailing = content.match(TRIM_TRAILING_PADDING_REGEX)?.[0] ?? '';
|
||||
const leading = content.match(CODE_BLOCK.TRIM_LEADING_PADDING_REGEX)?.[0] ?? '';
|
||||
const trailing = content.match(CODE_BLOCK.TRIM_TRAILING_PADDING_REGEX)?.[0] ?? '';
|
||||
const core = content.slice(leading.length, content.length - trailing.length);
|
||||
// autoDetect off: re-guessing the language on every keystroke
|
||||
// costs ~38ms a call and flickers while typing
|
||||
|
||||
+3
-7
@@ -4,11 +4,7 @@
|
||||
import HighlightedMatch from '$lib/components/app/forms/HighlightedMatch.svelte';
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import {
|
||||
FILE_GLOB_SEARCH_PICKERS_DEFAULT_SEARCH_DEPTH,
|
||||
HOME_TILDE,
|
||||
SEARCH_DEBOUNCE_MS
|
||||
} from '$lib/constants';
|
||||
import { FILE_GLOB_SEARCH_PICKERS, HOME_TILDE, SEARCH } from '$lib/constants';
|
||||
import { BuiltInTool, FileMentionEntryType, GlobSearchType, KeyboardKey } from '$lib/enums';
|
||||
import { useDebouncedSearch } from '$lib/hooks/use-debounced-search.svelte';
|
||||
import { usePickerNavigation } from '$lib/hooks/use-picker-navigation.svelte';
|
||||
@@ -70,7 +66,7 @@
|
||||
const searchDepth = $derived.by(() => {
|
||||
const n = Number(config().mentionSearchMaxDepth);
|
||||
|
||||
return Number.isInteger(n) && n > 0 ? n : FILE_GLOB_SEARCH_PICKERS_DEFAULT_SEARCH_DEPTH;
|
||||
return Number.isInteger(n) && n > 0 ? n : FILE_GLOB_SEARCH_PICKERS.DEFAULT_SEARCH_DEPTH;
|
||||
});
|
||||
|
||||
const home = $derived(toolsStore.serverHome);
|
||||
@@ -80,7 +76,7 @@
|
||||
|
||||
const search = useDebouncedSearch({
|
||||
canRun: () => isOpen && fileSearchEnabled,
|
||||
debounceMs: SEARCH_DEBOUNCE_MS,
|
||||
debounceMs: SEARCH.DEBOUNCE_MS,
|
||||
getQuery: () => trimmedQuery,
|
||||
run: async (query, signal, isCurrent) => {
|
||||
try {
|
||||
|
||||
@@ -4,16 +4,7 @@
|
||||
import { FolderOpen } from '@lucide/svelte';
|
||||
import SearchInput from '$lib/components/app/forms/SearchInput.svelte';
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import {
|
||||
DEFAULT_MOBILE_BREAKPOINT,
|
||||
HOME_TILDE,
|
||||
MAX_RESULTS_SHOWN,
|
||||
NATIVE_LIMIT,
|
||||
NATIVE_MAX_DEPTH,
|
||||
SEARCH_DEBOUNCE_MS,
|
||||
SEARCH_LIMIT,
|
||||
SEARCH_MAX_DEPTH
|
||||
} from '$lib/constants';
|
||||
import { DEFAULT_MOBILE_BREAKPOINT, HOME_TILDE, SEARCH } from '$lib/constants';
|
||||
import { BuiltInTool, GlobSearchType, KeyboardKey } from '$lib/enums';
|
||||
import { useDebouncedSearch } from '$lib/hooks/use-debounced-search.svelte';
|
||||
import { usePickerNavigation } from '$lib/hooks/use-picker-navigation.svelte';
|
||||
@@ -142,7 +133,7 @@
|
||||
// children too, so path navigation does not require a trailing slash.
|
||||
const search = useDebouncedSearch({
|
||||
canRun: () => isOpen && fileSearchEnabled,
|
||||
debounceMs: SEARCH_DEBOUNCE_MS,
|
||||
debounceMs: SEARCH.DEBOUNCE_MS,
|
||||
getQuery: () => query.trim(),
|
||||
run: async (q, signal, isCurrent) => {
|
||||
const trimmed = q.trim();
|
||||
@@ -162,8 +153,8 @@
|
||||
const res = await runGlobSearchWithChildren(
|
||||
trimmed,
|
||||
homeBase ?? HOME_TILDE,
|
||||
SEARCH_MAX_DEPTH,
|
||||
SEARCH_LIMIT,
|
||||
SEARCH.MAX_DEPTH,
|
||||
SEARCH.LIMIT,
|
||||
signal,
|
||||
{ type: GlobSearchType.DIR }
|
||||
);
|
||||
@@ -179,7 +170,7 @@
|
||||
}
|
||||
|
||||
searchScope = res.exactDir ?? res.args.path;
|
||||
queryResults = res.entries.map((e) => e.path).slice(0, MAX_RESULTS_SHOWN);
|
||||
queryResults = res.entries.map((e) => e.path).slice(0, SEARCH.MAX_RESULTS_SHOWN);
|
||||
|
||||
if (queryResults.length > 0) {
|
||||
nav.reset(0);
|
||||
@@ -223,8 +214,8 @@
|
||||
try {
|
||||
const res = await ToolsService.executeToolRaw(BuiltInTool.FILE_GLOB_SEARCH, {
|
||||
include: buildCaseInsensitiveGlob(name),
|
||||
limit: NATIVE_LIMIT,
|
||||
max_depth: NATIVE_MAX_DEPTH,
|
||||
limit: SEARCH.NATIVE_LIMIT,
|
||||
max_depth: SEARCH.NATIVE_MAX_DEPTH,
|
||||
path: homeBase ?? HOME_TILDE,
|
||||
type: GlobSearchType.DIR
|
||||
});
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
ChatMessageSystem,
|
||||
ChatMessageUser
|
||||
} from '$lib/components/app/chat';
|
||||
import { SYSTEM_MESSAGE_PLACEHOLDER } from '$lib/constants';
|
||||
import { REASONING_TAGS } from '$lib/constants/agentic';
|
||||
import { ROUTES } from '$lib/constants/routes';
|
||||
import { REASONING_TAGS, ROUTES, SYSTEM_MESSAGE_PLACEHOLDER } from '$lib/constants';
|
||||
import { getChatActionsContext, setMessageEditContext } from '$lib/contexts';
|
||||
import { AgenticSectionType, AttachmentType, MessageRole } from '$lib/enums';
|
||||
import { DatabaseService } from '$lib/services/database.service';
|
||||
|
||||
+1
-1
@@ -7,13 +7,13 @@
|
||||
import { Loader2 } from '@lucide/svelte';
|
||||
import { MarkdownContent, SyntaxHighlightedCode } from '$lib/components/app';
|
||||
import { MAX_HEIGHT_CODE_BLOCK } from '$lib/constants';
|
||||
import { getBuiltinToolUi } from '$lib/constants/built-in-tools';
|
||||
import { AttachmentType, FileTypeText, MimeTypeAudio, ToolResultKind } from '$lib/enums';
|
||||
import type { DatabaseMessageExtra } from '$lib/types';
|
||||
import {
|
||||
type AgenticSection,
|
||||
classifyToolResult,
|
||||
formatJsonPretty,
|
||||
getBuiltinToolUi,
|
||||
parseToolResultWithMedia,
|
||||
type ToolResultLine
|
||||
} from '$lib/utils';
|
||||
|
||||
+1
-2
@@ -10,8 +10,7 @@
|
||||
import ToolCallBlock from './ToolCallBlock.svelte';
|
||||
import { AlertTriangle, Check, Loader2, XCircle } from '@lucide/svelte';
|
||||
import { CollapsibleTerminalBlock } from '$lib/components/app';
|
||||
import { SETTINGS_KEYS } from '$lib/constants';
|
||||
import { TOOL_RUNTIME_SCROLL_AT_BOTTOM_THRESHOLD_PX } from '$lib/constants/auto-scroll';
|
||||
import { SETTINGS_KEYS, TOOL_RUNTIME_SCROLL_AT_BOTTOM_THRESHOLD_PX } from '$lib/constants';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { toolsStore } from '$lib/stores/tools.svelte';
|
||||
import type { DatabaseMessageExtra } from '$lib/types';
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
import { parseReadFileMeta } from './parsers/read-file';
|
||||
import ToolCallBlock from './ToolCallBlock.svelte';
|
||||
import { SyntaxHighlightedCode } from '$lib/components/app';
|
||||
import { DEFAULT_LANGUAGE, MAX_HEIGHT_CODE_BLOCK } from '$lib/constants';
|
||||
import { CODE_BLOCK, MAX_HEIGHT_CODE_BLOCK } from '$lib/constants';
|
||||
import { type AgenticSection } from '$lib/utils';
|
||||
|
||||
interface Props {
|
||||
@@ -32,7 +32,7 @@
|
||||
{#if section.toolResult}
|
||||
<SyntaxHighlightedCode
|
||||
code={section.toolResult}
|
||||
language={readFileMeta?.language ?? DEFAULT_LANGUAGE}
|
||||
language={readFileMeta?.language ?? CODE_BLOCK.DEFAULT_LANGUAGE}
|
||||
maxHeight={MAX_HEIGHT_CODE_BLOCK}
|
||||
/>
|
||||
{:else}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { parseReadMediaMeta } from './parsers/read-media';
|
||||
import ToolCallBlock from './ToolCallBlock.svelte';
|
||||
import { ATTACHMENT_SAVED_REGEX } from '$lib/constants/agentic';
|
||||
import { ATTACHMENT_SAVED_REGEX } from '$lib/constants/agentic.constants';
|
||||
import { AttachmentType, MimeTypeAudio } from '$lib/enums';
|
||||
import type { DatabaseMessageExtraAudioFile, DatabaseMessageExtraImageFile } from '$lib/types';
|
||||
import { type AgenticSection } from '$lib/utils';
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
import { Globe, Loader2 } from '@lucide/svelte';
|
||||
import { CollapsibleContentBlock } from '$lib/components/app';
|
||||
import * as HoverCard from '$lib/components/ui/hover-card';
|
||||
import { ICON_CLASS_DEFAULT, ICON_CLASS_SPIN } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT, ICON_CLASS_SPIN } from '$lib/constants';
|
||||
import { AgenticSectionType } from '$lib/enums';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import {
|
||||
|
||||
+2
-3
@@ -11,11 +11,10 @@
|
||||
|
||||
import { Loader2, Wrench } from '@lucide/svelte';
|
||||
import { CollapsibleContentBlock } from '$lib/components/app';
|
||||
import { getBuiltinToolUi } from '$lib/constants/built-in-tools';
|
||||
import { ICON_CLASS_DEFAULT, ICON_CLASS_SPIN } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT, ICON_CLASS_SPIN } from '$lib/constants';
|
||||
import { AgenticSectionType } from '$lib/enums';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import type { AgenticSection, BuiltinToolUiEntry } from '$lib/utils';
|
||||
import { type AgenticSection, type BuiltinToolUiEntry, getBuiltinToolUi } from '$lib/utils';
|
||||
import type { Component, Snippet } from 'svelte';
|
||||
|
||||
type ToolCallBlockMetaWithError = TMeta & { errorMessage?: string };
|
||||
|
||||
+4
-6
@@ -4,11 +4,7 @@
|
||||
// can render incrementally as the file path streams in.
|
||||
|
||||
import { parseToolArgs } from './_shared';
|
||||
import {
|
||||
DEFAULT_LANGUAGE,
|
||||
FILE_PATH_SEPARATOR_REGEX,
|
||||
TEXT_LANGUAGE_PREFIX_REGEX
|
||||
} from '$lib/constants';
|
||||
import { CODE_BLOCK, FILE_PATH_SEPARATOR_REGEX } from '$lib/constants';
|
||||
import { BuiltInTool } from '$lib/enums';
|
||||
import { type AgenticSection, getFileTypeByExtension } from '$lib/utils';
|
||||
|
||||
@@ -50,7 +46,9 @@ export function parseReadFileMeta(section: AgenticSection): ReadFileMeta | null
|
||||
}
|
||||
|
||||
const fileType = getFileTypeByExtension(fileName);
|
||||
const language = fileType ? fileType.replace(TEXT_LANGUAGE_PREFIX_REGEX, '') : DEFAULT_LANGUAGE;
|
||||
const language = fileType
|
||||
? fileType.replace(CODE_BLOCK.TEXT_LANGUAGE_PREFIX_REGEX, '')
|
||||
: CODE_BLOCK.DEFAULT_LANGUAGE;
|
||||
|
||||
return { fileName, language, lineRange };
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { FILE_PATH_SEPARATOR_REGEX, NEWLINE } from '$lib/constants/code';
|
||||
import { FILE_PATH_SEPARATOR_REGEX, NEWLINE } from '$lib/constants';
|
||||
import {
|
||||
PREFIX_FILE,
|
||||
PREFIX_MIME,
|
||||
|
||||
+3
-6
@@ -4,11 +4,7 @@
|
||||
// result blob.
|
||||
|
||||
import { parseToolArgs } from './_shared';
|
||||
import {
|
||||
DEFAULT_LANGUAGE,
|
||||
FILE_PATH_SEPARATOR_REGEX,
|
||||
TEXT_LANGUAGE_PREFIX_REGEX
|
||||
} from '$lib/constants';
|
||||
import { CODE_BLOCK, FILE_PATH_SEPARATOR_REGEX } from '$lib/constants';
|
||||
import { BuiltInTool } from '$lib/enums';
|
||||
import { type AgenticSection, getFileTypeByExtension, tryParseToolResultObject } from '$lib/utils';
|
||||
|
||||
@@ -36,7 +32,8 @@ export function parseWriteFileMeta(section: AgenticSection): WriteFileMeta | nul
|
||||
const fileName = rawPath.split(FILE_PATH_SEPARATOR_REGEX).pop() || rawPath;
|
||||
const content = typeof args.content === 'string' ? args.content : '';
|
||||
const language =
|
||||
getFileTypeByExtension(rawPath)?.replace(TEXT_LANGUAGE_PREFIX_REGEX, '') ?? DEFAULT_LANGUAGE;
|
||||
getFileTypeByExtension(rawPath)?.replace(CODE_BLOCK.TEXT_LANGUAGE_PREFIX_REGEX, '') ??
|
||||
CODE_BLOCK.DEFAULT_LANGUAGE;
|
||||
const resultObj = tryParseToolResultObject(section.toolResult);
|
||||
const bytesWritten =
|
||||
resultObj && Number.isFinite(Number(resultObj.bytes)) ? Number(resultObj.bytes) : undefined;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import type { Component, Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Lightbulb } from '@lucide/svelte';
|
||||
import { CollapsibleContentBlock, MarkdownContent } from '$lib/components/app';
|
||||
import { REASONING_SCROLL_AT_BOTTOM_THRESHOLD_PX } from '$lib/constants/auto-scroll';
|
||||
import { REASONING_SCROLL_AT_BOTTOM_THRESHOLD_PX } from '$lib/constants';
|
||||
import { AgenticSectionType } from '$lib/enums';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import type { DatabaseMessageExtra } from '$lib/types';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { ArrowDown } from '@lucide/svelte';
|
||||
import ActionIcon from '$lib/components/app/actions/ActionIcon.svelte';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
|
||||
let { onclick }: { onclick: (e?: MouseEvent) => void } = $props();
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { AlertTriangle, Loader2, RefreshCw } from '@lucide/svelte';
|
||||
import * as Alert from '$lib/components/ui/alert';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { serverError, serverLoading, serverStatus, serverStore } from '$lib/stores/server.svelte';
|
||||
|
||||
let hasError = $derived(!!serverError());
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
||||
import * as Collapsible from '$lib/components/ui/collapsible/index.js';
|
||||
import { cn } from '$lib/components/ui/utils';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { Component } from 'svelte';
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
} from '$lib/components/app';
|
||||
import {
|
||||
BOOL_TRUE_STRING,
|
||||
CODE_BLOCK_HEADER_CLASS,
|
||||
CODE_BLOCK_CLASS,
|
||||
DATA_ERROR_BOUND_ATTR,
|
||||
DATA_ERROR_HANDLED_ATTR,
|
||||
DIAGRAM_VIEW_MODE_ATTR,
|
||||
@@ -39,15 +39,8 @@
|
||||
MERMAID_SYNTAX_ATTR,
|
||||
MERMAID_WRAPPER_CLASS,
|
||||
SETTINGS_KEYS,
|
||||
SVG_BLOCK_CLASS,
|
||||
SVG_INLINE_SHADOW_STYLE,
|
||||
SVG_LANGUAGE,
|
||||
SVG_RENDERED_ATTR,
|
||||
SVG_SOURCE_ATTR,
|
||||
SVG_TAG_PREFIX,
|
||||
SVG_WRAPPER_CLASS,
|
||||
TOGGLE_SOURCE_BTN_CLASS,
|
||||
XML_LANGUAGE
|
||||
SVG,
|
||||
TOGGLE_SOURCE_BTN_CLASS
|
||||
} from '$lib/constants';
|
||||
import { ColorMode, UrlProtocol } from '$lib/enums';
|
||||
import { FileTypeText } from '$lib/enums/files.enums';
|
||||
@@ -105,9 +98,9 @@
|
||||
|
||||
if (!block) return null;
|
||||
|
||||
if (block.language === SVG_LANGUAGE) return block.code;
|
||||
if (block.language === SVG.LANGUAGE) return block.code;
|
||||
|
||||
if (block.language === XML_LANGUAGE && block.code.trimStart().startsWith(SVG_TAG_PREFIX))
|
||||
if (block.language === SVG.XML_LANGUAGE && block.code.trimStart().startsWith(SVG.TAG_PREFIX))
|
||||
return block.code;
|
||||
|
||||
return null;
|
||||
@@ -137,7 +130,7 @@
|
||||
|
||||
// Mount the streaming svg into its shadow host on every chunk so it renders live
|
||||
$effect(() => {
|
||||
if (streamingSvgHost) mountSvgShadow(streamingSvgHost, liveSvgHtml, SVG_INLINE_SHADOW_STYLE);
|
||||
if (streamingSvgHost) mountSvgShadow(streamingSvgHost, liveSvgHtml, SVG.INLINE_SHADOW_STYLE);
|
||||
});
|
||||
|
||||
let streamingCodeScrollContainer = $state<HTMLDivElement>();
|
||||
@@ -535,7 +528,7 @@
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const wrapper = toggleBtn.closest(`.${MERMAID_WRAPPER_CLASS}, .${SVG_WRAPPER_CLASS}`);
|
||||
const wrapper = toggleBtn.closest(`.${MERMAID_WRAPPER_CLASS}, .${SVG.WRAPPER_CLASS}`);
|
||||
|
||||
if (!wrapper) return;
|
||||
|
||||
@@ -593,16 +586,16 @@
|
||||
}
|
||||
|
||||
// Check if clicking on copy or preview button in svg block
|
||||
const svgCopyBtn = target.closest(`.${SVG_WRAPPER_CLASS} .copy-code-btn`);
|
||||
const svgPreviewBtn = target.closest(`.${SVG_WRAPPER_CLASS} .preview-code-btn`);
|
||||
const svgCopyBtn = target.closest(`.${SVG.WRAPPER_CLASS} .copy-code-btn`);
|
||||
const svgPreviewBtn = target.closest(`.${SVG.WRAPPER_CLASS} .preview-code-btn`);
|
||||
|
||||
if (svgCopyBtn || svgPreviewBtn) {
|
||||
const wrapper = target.closest(`.${SVG_WRAPPER_CLASS}`);
|
||||
const wrapper = target.closest(`.${SVG.WRAPPER_CLASS}`);
|
||||
|
||||
if (!wrapper) return;
|
||||
|
||||
const preElement = wrapper.querySelector<HTMLElement>(
|
||||
`pre.${SVG_BLOCK_CLASS}[${SVG_SOURCE_ATTR}]`
|
||||
`pre.${SVG.BLOCK_CLASS}[${SVG.SOURCE_ATTR}]`
|
||||
);
|
||||
|
||||
if (!preElement) return;
|
||||
@@ -611,7 +604,7 @@
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
try {
|
||||
await copyToClipboard(preElement.getAttribute(SVG_SOURCE_ATTR) ?? '');
|
||||
await copyToClipboard(preElement.getAttribute(SVG.SOURCE_ATTR) ?? '');
|
||||
} catch (error) {
|
||||
console.error('Failed to copy svg source:', error);
|
||||
}
|
||||
@@ -622,7 +615,7 @@
|
||||
if (svgPreviewBtn) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
mermaidPreviewSvgHtml = sanitizeSvg(preElement.getAttribute(SVG_SOURCE_ATTR) ?? '');
|
||||
mermaidPreviewSvgHtml = sanitizeSvg(preElement.getAttribute(SVG.SOURCE_ATTR) ?? '');
|
||||
svgPreviewLive = false;
|
||||
mermaidPreviewOpen = true;
|
||||
|
||||
@@ -633,14 +626,14 @@
|
||||
// A click on the header chrome targets the action buttons, never the
|
||||
// diagram. Guard so a header click can not fall through to the click to
|
||||
// zoom branches below, whatever the scroll position or stacking.
|
||||
if (target.closest(`.${CODE_BLOCK_HEADER_CLASS}`)) return;
|
||||
if (target.closest(`.${CODE_BLOCK_CLASS.HEADER}`)) return;
|
||||
|
||||
// Open preview when clicking the svg block itself. A final block carries its
|
||||
// source, a streaming block does not and is mirrored live into the dialog.
|
||||
const svgEl = target.closest(`.${SVG_BLOCK_CLASS}`);
|
||||
const svgEl = target.closest(`.${SVG.BLOCK_CLASS}`);
|
||||
|
||||
if (svgEl) {
|
||||
const source = svgEl.getAttribute(SVG_SOURCE_ATTR);
|
||||
const source = svgEl.getAttribute(SVG.SOURCE_ATTR);
|
||||
|
||||
if (source !== null) {
|
||||
mermaidPreviewSvgHtml = sanitizeSvg(source);
|
||||
@@ -739,15 +732,15 @@
|
||||
if (!containerRef) return;
|
||||
|
||||
const nodes = containerRef.querySelectorAll<HTMLElement>(
|
||||
`pre.${SVG_BLOCK_CLASS}:not([${SVG_RENDERED_ATTR}])`
|
||||
`pre.${SVG.BLOCK_CLASS}:not([${SVG.RENDERED_ATTR}])`
|
||||
);
|
||||
|
||||
if (nodes.length === 0) return;
|
||||
|
||||
nodes.forEach((node) => {
|
||||
node.setAttribute(SVG_RENDERED_ATTR, 'true');
|
||||
node.setAttribute(SVG.RENDERED_ATTR, 'true');
|
||||
|
||||
const source = node.getAttribute(SVG_SOURCE_ATTR) ?? node.textContent ?? '';
|
||||
const source = node.getAttribute(SVG.SOURCE_ATTR) ?? node.textContent ?? '';
|
||||
const clean = sanitizeSvg(source);
|
||||
|
||||
if (clean) {
|
||||
@@ -755,7 +748,7 @@
|
||||
const host = document.createElement('div');
|
||||
|
||||
node.appendChild(host);
|
||||
mountSvgShadow(host, clean, SVG_INLINE_SHADOW_STYLE);
|
||||
mountSvgShadow(host, clean, SVG.INLINE_SHADOW_STYLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -919,7 +912,7 @@
|
||||
</div>
|
||||
{#if liveSvgHtml}
|
||||
<div class="svg-scroll-container">
|
||||
<div class={SVG_BLOCK_CLASS}>
|
||||
<div class={SVG.BLOCK_CLASS}>
|
||||
<div bind:this={streamingSvgHost}></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+8
-14
@@ -4,17 +4,11 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
CODE_BLOCK_ACTIONS_CLASS,
|
||||
CODE_BLOCK_HEADER_CLASS,
|
||||
CODE_BLOCK_SCROLL_CONTAINER_CLASS,
|
||||
CODE_BLOCK_CLASS,
|
||||
CODE_ICON_SVG,
|
||||
CODE_LANGUAGE_CLASS,
|
||||
COPY_CODE_BTN_CLASS,
|
||||
COPY_ICON_SVG,
|
||||
DIAGRAM_SOURCE_CLASS,
|
||||
PREVIEW_CODE_BTN_CLASS,
|
||||
PREVIEW_ICON_SVG,
|
||||
RELATIVE_CLASS,
|
||||
TOGGLE_SOURCE_BTN_CLASS
|
||||
} from '$lib/constants';
|
||||
import type { Element, ElementContent } from 'hast';
|
||||
@@ -65,7 +59,7 @@ export function createButton(
|
||||
* Creates a copy button element.
|
||||
*/
|
||||
export function createCopyButton(id: string, idAttribute: string, title: string = 'Copy'): Element {
|
||||
return createButton(COPY_CODE_BTN_CLASS, title, COPY_ICON_SVG, id, idAttribute);
|
||||
return createButton(CODE_BLOCK_CLASS.COPY_BTN, title, COPY_ICON_SVG, id, idAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +70,7 @@ export function createPreviewButton(
|
||||
idAttribute: string,
|
||||
title: string = 'Preview'
|
||||
): Element {
|
||||
return createButton(PREVIEW_CODE_BTN_CLASS, title, PREVIEW_ICON_SVG, id, idAttribute);
|
||||
return createButton(CODE_BLOCK_CLASS.PREVIEW_BTN, title, PREVIEW_ICON_SVG, id, idAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +114,7 @@ export function createSourceView(
|
||||
type: 'element'
|
||||
}
|
||||
],
|
||||
properties: { className: [DIAGRAM_SOURCE_CLASS, CODE_BLOCK_SCROLL_CONTAINER_CLASS] },
|
||||
properties: { className: [DIAGRAM_SOURCE_CLASS, CODE_BLOCK_CLASS.SCROLL_CONTAINER] },
|
||||
tagName: 'div',
|
||||
type: 'element'
|
||||
};
|
||||
@@ -134,7 +128,7 @@ export function createBlockHeader(
|
||||
id: string,
|
||||
idAttribute: string,
|
||||
actions: Element[],
|
||||
languageClassName: string = CODE_LANGUAGE_CLASS
|
||||
languageClassName: string = CODE_BLOCK_CLASS.LANGUAGE
|
||||
): Element {
|
||||
return {
|
||||
children: [
|
||||
@@ -146,12 +140,12 @@ export function createBlockHeader(
|
||||
},
|
||||
{
|
||||
children: actions,
|
||||
properties: { className: [CODE_BLOCK_ACTIONS_CLASS] },
|
||||
properties: { className: [CODE_BLOCK_CLASS.ACTIONS] },
|
||||
tagName: 'div',
|
||||
type: 'element'
|
||||
}
|
||||
],
|
||||
properties: { className: [CODE_BLOCK_HEADER_CLASS] },
|
||||
properties: { className: [CODE_BLOCK_CLASS.HEADER] },
|
||||
tagName: 'div',
|
||||
type: 'element'
|
||||
};
|
||||
@@ -185,7 +179,7 @@ export function createWrapper(
|
||||
return {
|
||||
children: [header, createScrollContainer(preElement, scrollContainerClass), ...extraChildren],
|
||||
properties: {
|
||||
className: [wrapperClass, RELATIVE_CLASS],
|
||||
className: [wrapperClass, CODE_BLOCK_CLASS.RELATIVE],
|
||||
...additionalAttributes
|
||||
} as Element['properties'],
|
||||
tagName: 'div',
|
||||
|
||||
+3
-3
@@ -17,7 +17,7 @@ import {
|
||||
createWrapper,
|
||||
generateBlockId
|
||||
} from './code-block-utils';
|
||||
import { CODE_BLOCK_SCROLL_CONTAINER_CLASS, CODE_BLOCK_WRAPPER_CLASS } from '$lib/constants';
|
||||
import { CODE_BLOCK_CLASS } from '$lib/constants';
|
||||
import type { Element, ElementContent, Root } from 'hast';
|
||||
import type { Plugin } from 'unified';
|
||||
import { visit } from 'unist-util-visit';
|
||||
@@ -78,8 +78,8 @@ export const rehypeEnhanceCodeBlocks: Plugin<[], Root> = () => {
|
||||
const wrapper = createWrapper(
|
||||
header,
|
||||
node,
|
||||
CODE_BLOCK_WRAPPER_CLASS,
|
||||
CODE_BLOCK_SCROLL_CONTAINER_CLASS
|
||||
CODE_BLOCK_CLASS.WRAPPER,
|
||||
CODE_BLOCK_CLASS.SCROLL_CONTAINER
|
||||
);
|
||||
|
||||
// Replace pre with wrapper in parent
|
||||
|
||||
+13
-22
@@ -19,16 +19,7 @@ import {
|
||||
generateBlockId
|
||||
} from './code-block-utils';
|
||||
import type { DiagramPreData } from './pre-transform';
|
||||
import {
|
||||
DIAGRAM_VIEW_MODE_ATTR,
|
||||
DIAGRAM_VIEW_RENDERED,
|
||||
SVG_BLOCK_CLASS,
|
||||
SVG_ID_ATTR,
|
||||
SVG_LANGUAGE,
|
||||
SVG_SCROLL_CONTAINER_CLASS,
|
||||
SVG_SOURCE_ATTR,
|
||||
SVG_WRAPPER_CLASS
|
||||
} from '$lib/constants';
|
||||
import { DIAGRAM_VIEW_MODE_ATTR, DIAGRAM_VIEW_RENDERED, SVG } from '$lib/constants';
|
||||
import type { Element, ElementContent, Root } from 'hast';
|
||||
import type { Plugin } from 'unified';
|
||||
import { visit } from 'unist-util-visit';
|
||||
@@ -48,11 +39,11 @@ export const rehypeEnhanceSvgBlocks: Plugin<[], Root> = () => {
|
||||
|
||||
if (!Array.isArray(className)) return;
|
||||
|
||||
const isSvg = className.some((cls) => typeof cls === 'string' && cls === SVG_BLOCK_CLASS);
|
||||
const isSvg = className.some((cls) => typeof cls === 'string' && cls === SVG.BLOCK_CLASS);
|
||||
|
||||
if (!isSvg) return;
|
||||
|
||||
const svgId = generateBlockId(SVG_LANGUAGE, 'idxSvgBlock');
|
||||
const svgId = generateBlockId(SVG.LANGUAGE, 'idxSvgBlock');
|
||||
// Extract the svg source (text content of the pre element)
|
||||
const svgSource = node.children
|
||||
.map((child) => {
|
||||
@@ -65,26 +56,26 @@ export const rehypeEnhanceSvgBlocks: Plugin<[], Root> = () => {
|
||||
// Store the svg source in data attribute for copy and render
|
||||
node.properties = {
|
||||
...node.properties,
|
||||
[SVG_ID_ATTR]: svgId,
|
||||
[SVG_SOURCE_ATTR]: svgSource
|
||||
[SVG.ID_ATTR]: svgId,
|
||||
[SVG.SOURCE_ATTR]: svgSource
|
||||
};
|
||||
|
||||
const actions = [
|
||||
createCopyButton(svgId, SVG_ID_ATTR, 'Copy svg source'),
|
||||
createToggleSourceButton(svgId, SVG_ID_ATTR, 'Toggle svg source'),
|
||||
createPreviewButton(svgId, SVG_ID_ATTR, 'Preview svg')
|
||||
createCopyButton(svgId, SVG.ID_ATTR, 'Copy svg source'),
|
||||
createToggleSourceButton(svgId, SVG.ID_ATTR, 'Toggle svg source'),
|
||||
createPreviewButton(svgId, SVG.ID_ATTR, 'Preview svg')
|
||||
];
|
||||
const header = createBlockHeader(SVG_LANGUAGE, svgId, SVG_ID_ATTR, actions);
|
||||
const header = createBlockHeader(SVG.LANGUAGE, svgId, SVG.ID_ATTR, actions);
|
||||
const preservedCode = (node.data as DiagramPreData | undefined)?.sourceCode;
|
||||
const sourceView = createSourceView(preservedCode, svgSource, SVG_LANGUAGE);
|
||||
const sourceView = createSourceView(preservedCode, svgSource, SVG.LANGUAGE);
|
||||
const wrapper = createWrapper(
|
||||
header,
|
||||
node,
|
||||
SVG_WRAPPER_CLASS,
|
||||
SVG_SCROLL_CONTAINER_CLASS,
|
||||
SVG.WRAPPER_CLASS,
|
||||
SVG.SCROLL_CONTAINER_CLASS,
|
||||
{
|
||||
[DIAGRAM_VIEW_MODE_ATTR]: DIAGRAM_VIEW_RENDERED,
|
||||
[SVG_ID_ATTR]: svgId
|
||||
[SVG.ID_ATTR]: svgId
|
||||
},
|
||||
[sourceView]
|
||||
);
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Rehype plugin that rewrites `file://` markdown anchors into the inline
|
||||
* mention chip, sharing the class string with the contenteditable
|
||||
* tokenizer via `$lib/constants/mention-badge`.
|
||||
* tokenizer via `$lib/constants`.
|
||||
*
|
||||
* The chip is presentational: `file://` navigation is blocked from
|
||||
* http(s) pages, so the anchor becomes a plain `<span>` (no link role,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createPreTransform } from './pre-transform';
|
||||
import { SVG_BLOCK_CLASS, SVG_LANGUAGE, SVG_TAG_PREFIX, XML_LANGUAGE } from '$lib/constants';
|
||||
import { SVG } from '$lib/constants';
|
||||
|
||||
/**
|
||||
* Converts svg code blocks to <pre class="svg-block"> for client-side rendering.
|
||||
@@ -7,7 +7,7 @@ import { SVG_BLOCK_CLASS, SVG_LANGUAGE, SVG_TAG_PREFIX, XML_LANGUAGE } from '$li
|
||||
* svg inside an xml fence.
|
||||
*/
|
||||
export const rehypeSvgPre = createPreTransform(
|
||||
[SVG_LANGUAGE, XML_LANGUAGE],
|
||||
SVG_BLOCK_CLASS,
|
||||
(text) => text.startsWith(SVG_TAG_PREFIX)
|
||||
[SVG.LANGUAGE, SVG.XML_LANGUAGE],
|
||||
SVG.BLOCK_CLASS,
|
||||
(text) => text.startsWith(SVG.TAG_PREFIX)
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import MermaidPreviewControls from './MermaidPreviewControls.svelte';
|
||||
import { SVG_DIALOG_SHADOW_STYLE } from '$lib/constants';
|
||||
import { SVG } from '$lib/constants';
|
||||
import { mountSvgShadow } from '$lib/utils/svg-shadow';
|
||||
|
||||
interface Props {
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
// Re-mount on every svgHtml change so a live streaming svg keeps rendering while zoomed
|
||||
$effect(() => {
|
||||
if (svgHost) mountSvgShadow(svgHost, svgHtml, SVG_DIALOG_SHADOW_STYLE);
|
||||
if (svgHost) mountSvgShadow(svgHost, svgHtml, SVG.DIALOG_SHADOW_STYLE);
|
||||
});
|
||||
|
||||
// Zoom and pan state
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import RotateCcwIcon from '@lucide/svelte/icons/rotate-ccw';
|
||||
import ZoomInIcon from '@lucide/svelte/icons/zoom-in';
|
||||
import ZoomOutIcon from '@lucide/svelte/icons/zoom-out';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
|
||||
interface Props {
|
||||
scale: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { SYNTAX_CODE_SCROLL_AT_BOTTOM_THRESHOLD_PX } from '$lib/constants/auto-scroll';
|
||||
import { SYNTAX_CODE_SCROLL_AT_BOTTOM_THRESHOLD_PX } from '$lib/constants';
|
||||
import { ColorMode } from '$lib/enums';
|
||||
import { highlightCode } from '$lib/utils';
|
||||
import githubLightCss from 'highlight.js/styles/github.css?inline';
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import { ActionIconCopyToClipboard, SyntaxHighlightedCode } from '$lib/components/app';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { DEFAULT_RESOURCE_FILENAME } from '$lib/constants';
|
||||
import { MimeTypeIncludes, MimeTypeText } from '$lib/enums';
|
||||
import { DEFAULT_RESOURCE_FILENAME, MIME_TYPE_SUBSTRINGS } from '$lib/constants';
|
||||
import { MimeTypeText } from '$lib/enums';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import type { DatabaseMessageExtraMcpResource } from '$lib/types';
|
||||
import {
|
||||
@@ -26,11 +26,13 @@
|
||||
const favicon = $derived(mcpStore.getServerFavicon(extra.serverName));
|
||||
|
||||
function getLanguage(): string {
|
||||
if (extra.mimeType?.includes(MimeTypeIncludes.JSON)) return MimeTypeIncludes.JSON;
|
||||
if (extra.mimeType?.includes(MIME_TYPE_SUBSTRINGS.JSON)) return MIME_TYPE_SUBSTRINGS.JSON;
|
||||
|
||||
if (extra.mimeType?.includes(MimeTypeIncludes.JAVASCRIPT)) return MimeTypeIncludes.JAVASCRIPT;
|
||||
if (extra.mimeType?.includes(MIME_TYPE_SUBSTRINGS.JAVASCRIPT))
|
||||
return MIME_TYPE_SUBSTRINGS.JAVASCRIPT;
|
||||
|
||||
if (extra.mimeType?.includes(MimeTypeIncludes.TYPESCRIPT)) return MimeTypeIncludes.TYPESCRIPT;
|
||||
if (extra.mimeType?.includes(MIME_TYPE_SUBSTRINGS.TYPESCRIPT))
|
||||
return MIME_TYPE_SUBSTRINGS.TYPESCRIPT;
|
||||
|
||||
const name = extra.name || extra.uri || '';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
} from '$lib/components/app';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import {
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import {
|
||||
BEARER_PREFIX,
|
||||
BOOL_FALSE_STRING,
|
||||
BOOL_TRUE_STRING,
|
||||
DISMISSED_RECOMMENDED_MCP_SERVERS_LOCALSTORAGE_KEY,
|
||||
HEADERS,
|
||||
MCP_SERVER_ID_PREFIX,
|
||||
RECOMMENDED_MCP_SERVERS,
|
||||
REDACTED_HEADERS
|
||||
RECOMMENDED_MCP_SERVERS
|
||||
} from '$lib/constants';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
@@ -61,10 +60,10 @@
|
||||
|
||||
let bearerTokenFilled = $derived.by(() => {
|
||||
const pairs = parseHeadersToArray(newServerHeaders);
|
||||
const bearerPrefix = BEARER_PREFIX.toLowerCase();
|
||||
const bearerPrefix = HEADERS.BEARER.toLowerCase();
|
||||
const bearer = pairs.find(
|
||||
(p) =>
|
||||
REDACTED_HEADERS.has(p.key.trim().toLowerCase()) &&
|
||||
HEADERS.REDACTED.has(p.key.trim().toLowerCase()) &&
|
||||
p.value.trim().toLowerCase().startsWith(bearerPrefix)
|
||||
);
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import { URL_PARAMS } from '$lib/constants';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT, URL_PARAMS } from '$lib/constants';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Search, X } from '@lucide/svelte';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
|
||||
interface Props {
|
||||
autofocus?: boolean;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import McpLogo from './McpLogo.svelte';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { MAX_DISPLAYED_MCP_AVATARS } from '$lib/constants';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT, MAX_DISPLAYED_MCP_AVATARS } from '$lib/constants';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { AlertCircle, Download, FileText, Loader2 } from '@lucide/svelte';
|
||||
import { ActionIconCopyToClipboard } from '$lib/components/app';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { MimeTypeApplication, MimeTypeText } from '$lib/enums';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import type { MCPResourceContent, MCPResourceInfo } from '$lib/types';
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
import { Loader2, RefreshCw } from '@lucide/svelte';
|
||||
import { SearchInput } from '$lib/components/app/forms';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
|
||||
interface Props {
|
||||
isLoading: boolean;
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import { McpServerIdentity } from '$lib/components/app/mcp';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import * as Collapsible from '$lib/components/ui/collapsible';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import type { MCPResourceInfo, MCPResourceTemplateInfo, MCPServerResources } from '$lib/types';
|
||||
import { getDisplayName, getResourceIcon } from '$lib/utils';
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
} from '$lib/components/app/mcp';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import type { HealthCheckState, MCPServerSettingsEntry } from '$lib/types';
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
import { KeyValuePairs } from '$lib/components/app';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import {
|
||||
AUTHORIZATION_HEADER,
|
||||
BEARER_PREFIX,
|
||||
CLI_FLAGS,
|
||||
MCP_SERVER_URL_PLACEHOLDER,
|
||||
REDACTED_HEADERS
|
||||
} from '$lib/constants';
|
||||
import { CLI_FLAGS, HEADERS, MCP_SERVER_URL_PLACEHOLDER } from '$lib/constants';
|
||||
import { UrlProtocol } from '$lib/enums';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import type { KeyValuePair } from '$lib/types';
|
||||
@@ -72,10 +66,10 @@
|
||||
// carry a Bearer scheme. Anything else (e.g. Basic, raw tokens) stays in the
|
||||
// KV section so the user can still edit those values verbatim.
|
||||
const matchesAuthorizationKey = (key: string): boolean =>
|
||||
REDACTED_HEADERS.has(key.trim().toLowerCase());
|
||||
HEADERS.REDACTED.has(key.trim().toLowerCase());
|
||||
|
||||
const isBearerScheme = (value: string): boolean =>
|
||||
value.trim().toLowerCase().startsWith(BEARER_PREFIX.toLowerCase());
|
||||
value.trim().toLowerCase().startsWith(HEADERS.BEARER.toLowerCase());
|
||||
|
||||
const ownedByBearerUi = (p: KeyValuePair): boolean =>
|
||||
matchesAuthorizationKey(p.key) && isBearerScheme(p.value);
|
||||
@@ -102,7 +96,7 @@
|
||||
|
||||
if (!auth) return '';
|
||||
|
||||
return auth.value.trim().slice(BEARER_PREFIX.length).trim();
|
||||
return auth.value.trim().slice(HEADERS.BEARER.length).trim();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
@@ -125,7 +119,7 @@
|
||||
const trimmed = token.trim();
|
||||
|
||||
if (trimmed) {
|
||||
filtered.push({ key: AUTHORIZATION_HEADER, value: `${BEARER_PREFIX}${trimmed}` });
|
||||
filtered.push({ key: HEADERS.AUTHORIZATION, value: `${HEADERS.BEARER}${trimmed}` });
|
||||
}
|
||||
|
||||
updateHeaderPairs(filtered);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { ChevronLeft, ChevronRight } from '@lucide/svelte';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
RotateCw
|
||||
} from '@lucide/svelte';
|
||||
import { ActionIcon, ModelId } from '$lib/components/app';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { ServerModelStatus } from '$lib/enums';
|
||||
import { modelsStore, routerModels } from '$lib/stores/models.svelte';
|
||||
import type { ModelOption } from '$lib/types/models';
|
||||
|
||||
+1
-1
@@ -5,12 +5,12 @@
|
||||
import { ActionIcon, KeyboardShortcutInfo, SearchInput } from '$lib/components/app';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import {
|
||||
ICON_CLASS_DEFAULT,
|
||||
ICON_STRIP_TRANSITION_DELAY_MULTIPLIER,
|
||||
ICON_STRIP_TRANSITION_DURATION,
|
||||
ROUTES,
|
||||
SIDEBAR_ACTIONS_ITEMS
|
||||
} from '$lib/constants';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { TooltipSide } from '$lib/enums';
|
||||
import { isMobile } from '$lib/stores/viewport.svelte';
|
||||
import type { Component } from 'svelte';
|
||||
|
||||
+1
-2
@@ -15,8 +15,7 @@
|
||||
import { TruncatedText } from '$lib/components/app';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { FORK_TREE_DEPTH_PADDING } from '$lib/constants';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { FORK_TREE_DEPTH_PADDING, ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { RouterService } from '$lib/services/router.service';
|
||||
import { getAllLoadingChats } from '$lib/stores/chat.svelte';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import { AUTHORIZATION_HEADER, BEARER_PREFIX, SETTINGS_KEYS } from '$lib/constants';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ROUTES } from '$lib/constants/routes';
|
||||
import { HEADERS, ICON_CLASS_DEFAULT, ROUTES, SETTINGS_KEYS } from '$lib/constants';
|
||||
import { KeyboardKey } from '$lib/enums';
|
||||
import { serverLoading, serverStore } from '$lib/stores/server.svelte';
|
||||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||
@@ -72,8 +70,8 @@
|
||||
// Test the API key by making a real request to the server
|
||||
const response = await fetch(`${base}/props`, {
|
||||
headers: {
|
||||
[AUTHORIZATION_HEADER]: `${BEARER_PREFIX}${apiKeyInput.trim()}`,
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
[HEADERS.AUTHORIZATION]: `${HEADERS.BEARER}${apiKeyInput.trim()}`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { AlertTriangle, Server } from '@lucide/svelte';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { singleModelName } from '$lib/stores/models.svelte';
|
||||
import { serverError, serverLoading, serverProps } from '$lib/stores/server.svelte';
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
import * as RadioGroup from '$lib/components/ui/radio-group';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { SETTING_CONFIG_INFO, SETTINGS_KEYS } from '$lib/constants';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT, SETTING_CONFIG_INFO, SETTINGS_KEYS } from '$lib/constants';
|
||||
import { SettingsFieldType } from '$lib/enums/settings.enums';
|
||||
import { modelsStore, propsCacheVersion, selectedModelName } from '$lib/stores/models.svelte';
|
||||
import { serverStore } from '$lib/stores/server.svelte';
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Button, type ButtonVariant } from '$lib/components/ui/button';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import type { Component } from 'svelte';
|
||||
|
||||
let {
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
import { McpServerIdentity, TruncatedText } from '$lib/components/app';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import * as Collapsible from '$lib/components/ui/collapsible';
|
||||
import { getBuiltinToolUi } from '$lib/constants/built-in-tools';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { ToolSource } from '$lib/enums/tools.enums';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import { permissionsStore } from '$lib/stores/permissions.svelte';
|
||||
import { toolsStore } from '$lib/stores/tools.svelte';
|
||||
import { getBuiltinToolUi } from '$lib/utils';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
let expandedGroups = new SvelteSet<string>();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Settings } from '@lucide/svelte';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import type { SettingsSection, SettingsSectionTitle } from '$lib/types';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { ChevronLeft, ChevronRight, Settings } from '@lucide/svelte';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { useScrollCarousel } from '$lib/hooks/use-scroll-carousel.svelte';
|
||||
import type { SettingsSection, SettingsSectionTitle } from '$lib/types';
|
||||
import { onMount, tick } from 'svelte';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { APP_NAME } from '$lib/constants';
|
||||
import { APPLE_META_TAGS, MEDIA_QUERIES, THEME_COLORS } from '$lib/constants/pwa';
|
||||
import { APP_NAME, APPLE_META_TAGS, MEDIA_QUERIES, THEME_COLORS } from '$lib/constants';
|
||||
|
||||
let { appName = APP_NAME } = $props();
|
||||
</script>
|
||||
|
||||
+4
-12
@@ -5,22 +5,14 @@ export const ATTACHMENT_SAVED_REGEX = /\[Attachment saved: ([^\]]+)\]/;
|
||||
// JSON detection: trimmed content opens with an object or array literal.
|
||||
export const TOOL_RESULT_JSON_OPEN_REGEX = /^[[{]/;
|
||||
|
||||
// Markdown structural markers used by `looksLikeMarkdown`. Inline / line-level.
|
||||
export const MARKDOWN_CODE_FENCE_REGEX = /^(```|~~~)/m;
|
||||
export const MARKDOWN_ATX_HEADING_REGEX = /^#{1,6}\s+\S/;
|
||||
export const MARKDOWN_BLOCKQUOTE_REGEX = /^>\s+\S/;
|
||||
export const MARKDOWN_LIST_BULLET_REGEX = /^\s*[-*+]\s+\S/;
|
||||
export const MARKDOWN_LIST_NUMBERED_REGEX = /^\s*\d+[.)]\s+\S/;
|
||||
export const MARKDOWN_LINK_REGEX = /\[[^\]\n]+\]\([^)\s]+\)/;
|
||||
export const MARKDOWN_BOLD_REGEX = /\*\*[^*\n]+\*\*|__[^_\n]+__/;
|
||||
export const MARKDOWN_TABLE_SEPARATOR_REGEX = /^\s*\|?[\s:|-]+\|?\s*$/;
|
||||
|
||||
// Search-summary wire format used by file-glob and grep tools:
|
||||
// <matches>
|
||||
// ---
|
||||
// Total matches: N
|
||||
export const SEARCH_SUMMARY_SEPARATOR = '---\n';
|
||||
export const SEARCH_SUMMARY_TOTAL_REGEX = /Total matches:\s*(\d+)/;
|
||||
export const SEARCH_SUMMARY = {
|
||||
SEPARATOR: '---\n',
|
||||
TOTAL_REGEX: /Total matches:\s*(\d+)/
|
||||
} as const;
|
||||
|
||||
// Separator rendered between stats in the tool-result footer (e.g. between a
|
||||
// result message and the byte/edit count). Plain ASCII spaces bracket a hyphen
|
||||
@@ -1,4 +0,0 @@
|
||||
export const ATTACHMENT_LABEL_FILE = 'File';
|
||||
export const ATTACHMENT_LABEL_PDF_FILE = 'PDF File';
|
||||
export const ATTACHMENT_LABEL_MCP_PROMPT = 'MCP Prompt';
|
||||
export const ATTACHMENT_LABEL_MCP_RESOURCE = 'MCP Resource';
|
||||
+2
-23
@@ -1,33 +1,12 @@
|
||||
import { FolderOpen, MessageSquare, Zap } from '@lucide/svelte';
|
||||
import { FILE_TYPE_ICONS } from '$lib/constants/icons';
|
||||
import { FILE_TYPE_ICONS } from '$lib/constants';
|
||||
import {
|
||||
AttachmentAction,
|
||||
AttachmentItemEnabledWhen,
|
||||
AttachmentItemVisibleWhen,
|
||||
AttachmentMenuItemId
|
||||
} from '$lib/enums';
|
||||
import type { Component } from 'svelte';
|
||||
|
||||
export interface AttachmentMenuItem {
|
||||
/** Unique identifier for the item */
|
||||
id: AttachmentMenuItemId;
|
||||
/** Display label */
|
||||
label: string;
|
||||
/** Lucide icon component */
|
||||
icon: Component;
|
||||
/** Extra CSS class applied to the item (e.g. for test selectors) */
|
||||
class?: string;
|
||||
/** Whether the item requires a specific modality to be enabled */
|
||||
enabledWhen?: AttachmentItemEnabledWhen;
|
||||
/** Tooltip shown when the item is disabled */
|
||||
disabledTooltip?: string;
|
||||
/** Callback key on the Props interface to invoke when clicked */
|
||||
action: AttachmentAction;
|
||||
/** Whether the item is only shown when a specific capability is present */
|
||||
visibleWhen?: AttachmentItemVisibleWhen;
|
||||
/** Whether this item has a tooltip even when enabled (uses dynamic text) */
|
||||
hasEnabledTooltip?: boolean;
|
||||
}
|
||||
import type { AttachmentMenuItem } from '$lib/types';
|
||||
|
||||
/**
|
||||
* File attachment menu items shown in both the desktop dropdown and mobile sheet.
|
||||
+1
-13
@@ -20,13 +20,7 @@ import {
|
||||
Terminal
|
||||
} from '@lucide/svelte';
|
||||
import { BuiltInTool, ToolSource } from '$lib/enums';
|
||||
import type { Component } from 'svelte';
|
||||
|
||||
export interface BuiltinToolUiEntry {
|
||||
icon: Component;
|
||||
label: string;
|
||||
source: ToolSource.BUILTIN | ToolSource.FRONTEND;
|
||||
}
|
||||
import type { BuiltinToolUiEntry } from '$lib/types';
|
||||
|
||||
export const BUILTIN_TOOL_UI: Readonly<Record<BuiltInTool, BuiltinToolUiEntry>> = {
|
||||
[BuiltInTool.EDIT_FILE]: { icon: FilePen, label: 'Edit file', source: ToolSource.BUILTIN },
|
||||
@@ -56,9 +50,3 @@ export const BUILTIN_TOOL_UI: Readonly<Record<BuiltInTool, BuiltinToolUiEntry>>
|
||||
},
|
||||
[BuiltInTool.WRITE_FILE]: { icon: FilePlus, label: 'Write file', source: ToolSource.BUILTIN }
|
||||
} as const;
|
||||
|
||||
export function getBuiltinToolUi(toolName: string | undefined): BuiltinToolUiEntry | null {
|
||||
if (!toolName) return null;
|
||||
|
||||
return (BUILTIN_TOOL_UI as Record<string, BuiltinToolUiEntry>)[toolName] ?? null;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Cache configuration constants
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default cache limits when no per-cache overrides are given.
|
||||
*/
|
||||
export const CACHE = {
|
||||
/** Default maximum number of entries in a cache */
|
||||
DEFAULT_MAX_ENTRIES: 100,
|
||||
/** Default TTL (Time-To-Live) for cache entries in milliseconds (5 minutes) */
|
||||
DEFAULT_TTL_MS: 5 * 60 * 1000
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* TTL and size for the model props cache.
|
||||
* Props don't change frequently, so we can cache them longer.
|
||||
*/
|
||||
export const MODEL_PROPS_CACHE = {
|
||||
/** Maximum number of model props to cache */
|
||||
MAX_ENTRIES: 50,
|
||||
/** TTL for model props cache entries in milliseconds (10 minutes) */
|
||||
TTL_MS: 10 * 60 * 1000
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* TTL and size for the MCP resource cache.
|
||||
*/
|
||||
export const MCP_RESOURCE_CACHE = {
|
||||
/** Maximum number of MCP resources to cache */
|
||||
MAX_ENTRIES: 50,
|
||||
/** TTL for MCP resource cache entries in milliseconds (5 minutes) */
|
||||
TTL_MS: 5 * 60 * 1000
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Limits for pruning inactive conversation states held in memory.
|
||||
*/
|
||||
export const INACTIVE_CONVERSATION = {
|
||||
/** Maximum age (in ms) for inactive conversation states before cleanup (30 minutes) */
|
||||
MAX_AGE_MS: 30 * 60 * 1000,
|
||||
/** Maximum number of inactive conversation states to keep in memory */
|
||||
MAX_STATES: 10
|
||||
} as const;
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Cache configuration constants
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default TTL (Time-To-Live) for cache entries in milliseconds
|
||||
* @default 5 minutes
|
||||
*/
|
||||
export const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Default maximum number of entries in a cache
|
||||
* @default 100
|
||||
*/
|
||||
export const DEFAULT_CACHE_MAX_ENTRIES = 100;
|
||||
|
||||
/**
|
||||
* TTL for model props cache in milliseconds
|
||||
* Props don't change frequently, so we can cache them longer
|
||||
* @default 10 minutes
|
||||
*/
|
||||
export const MODEL_PROPS_CACHE_TTL_MS = 10 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Maximum number of model props to cache
|
||||
* @default 50
|
||||
*/
|
||||
export const MODEL_PROPS_CACHE_MAX_ENTRIES = 50;
|
||||
|
||||
/**
|
||||
* Maximum number of MCP resources to cache
|
||||
* @default 50
|
||||
*/
|
||||
export const MCP_RESOURCE_CACHE_MAX_ENTRIES = 50;
|
||||
|
||||
/**
|
||||
* TTL for MCP resource cache entries in milliseconds
|
||||
* @default 5 minutes
|
||||
*/
|
||||
export const MCP_RESOURCE_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Maximum number of inactive conversation states to keep in memory
|
||||
* States for conversations beyond this limit will be cleaned up
|
||||
* @default 10
|
||||
*/
|
||||
export const MAX_INACTIVE_CONVERSATION_STATES = 10;
|
||||
|
||||
/**
|
||||
* Maximum age (in ms) for inactive conversation states before cleanup
|
||||
* States older than this will be removed during cleanup
|
||||
* @default 30 minutes
|
||||
*/
|
||||
export const INACTIVE_CONVERSATION_STATE_MAX_AGE_MS = 30 * 60 * 1000;
|
||||
@@ -0,0 +1,50 @@
|
||||
// Constants for the markdown code-block renderer: language/fence handling and CSS classes.
|
||||
|
||||
/** Parsing and escaping helpers for the markdown code-block renderer. */
|
||||
export const CODE_BLOCK = {
|
||||
AMPERSAND_REGEX: /&/g,
|
||||
/** Language fallback used when no language is specified. */
|
||||
DEFAULT_LANGUAGE: 'text',
|
||||
/** Matches opening/closing markdown code fences. */
|
||||
FENCE_PATTERN: /^```|\n```/g,
|
||||
GT_REGEX: />/g,
|
||||
/** Matches the language specifier at the start of a code fence. */
|
||||
LANG_PATTERN: /^(\w*)\n?/,
|
||||
LT_REGEX: /</g,
|
||||
|
||||
// Matches the `text:` prefix that file-type identifiers use to denote a
|
||||
// plain-text language (e.g. `text:typescript`). Used by tool-call renderers
|
||||
// to recover the underlying highlight.js language.
|
||||
TEXT_LANGUAGE_PREFIX_REGEX: /^text:/,
|
||||
// Whitespace-only empty lines (between start of string and first non-empty line).
|
||||
// Used by trimCodePadding to drop leading/trailing phantom blank rows from LLM
|
||||
// payload wrappers without touching internal blank lines.
|
||||
TRIM_LEADING_PADDING_REGEX: /^(?:[ \t]*\n)+/,
|
||||
|
||||
TRIM_TRAILING_PADDING_REGEX: /(?:\n[ \t]*)+$/
|
||||
} as const;
|
||||
|
||||
// Matches either Unix or Windows path separators so `String.split(REGEX)` can
|
||||
// recover the trailing file-name segment from either `/foo/bar.txt` or
|
||||
// `C:\foo\bar.txt`. Used wherever a parameter accepts a user-supplied path.
|
||||
export const FILE_PATH_SEPARATOR_REGEX = /[\\/]/;
|
||||
|
||||
// Separates a file name from its extension, e.g. the '.' in `cover.png`.
|
||||
export const FILE_EXTENSION_SEPARATOR = '.';
|
||||
|
||||
// Matches the `text:` prefix that file-type identifiers use to denote a
|
||||
// plain-text language (e.g. `text:typescript`). Used by tool-call renderers
|
||||
// to recover the underlying highlight.js language.
|
||||
export const TEXT_LANGUAGE_PREFIX_REGEX = /^text:/;
|
||||
|
||||
/** CSS classes applied by the markdown code-block renderer. */
|
||||
export const CODE_BLOCK_CLASS = {
|
||||
ACTIONS: 'code-block-actions',
|
||||
COPY_BTN: 'copy-code-btn',
|
||||
HEADER: 'code-block-header',
|
||||
LANGUAGE: 'code-language',
|
||||
PREVIEW_BTN: 'preview-code-btn',
|
||||
RELATIVE: 'relative',
|
||||
SCROLL_CONTAINER: 'code-block-scroll-container',
|
||||
WRAPPER: 'code-block-wrapper'
|
||||
} as const;
|
||||
@@ -1,8 +0,0 @@
|
||||
export const CODE_BLOCK_SCROLL_CONTAINER_CLASS = 'code-block-scroll-container';
|
||||
export const CODE_BLOCK_WRAPPER_CLASS = 'code-block-wrapper';
|
||||
export const CODE_BLOCK_HEADER_CLASS = 'code-block-header';
|
||||
export const CODE_BLOCK_ACTIONS_CLASS = 'code-block-actions';
|
||||
export const CODE_LANGUAGE_CLASS = 'code-language';
|
||||
export const COPY_CODE_BTN_CLASS = 'copy-code-btn';
|
||||
export const PREVIEW_CODE_BTN_CLASS = 'preview-code-btn';
|
||||
export const RELATIVE_CLASS = 'relative';
|
||||
@@ -1,27 +0,0 @@
|
||||
export const NEWLINE = '\n';
|
||||
export const TAB = '\t';
|
||||
export const DEFAULT_LANGUAGE = 'text';
|
||||
export const LANG_PATTERN = /^(\w*)\n?/;
|
||||
export const AMPERSAND_REGEX = /&/g;
|
||||
export const LT_REGEX = /</g;
|
||||
export const GT_REGEX = />/g;
|
||||
export const FENCE_PATTERN = /^```|\n```/g;
|
||||
|
||||
// Whitespace-only empty lines (between start of string and first non-empty line).
|
||||
// Used by trimCodePadding to drop leading/trailing phantom blank rows from LLM
|
||||
// payload wrappers without touching internal blank lines.
|
||||
export const TRIM_LEADING_PADDING_REGEX = /^(?:[ \t]*\n)+/;
|
||||
export const TRIM_TRAILING_PADDING_REGEX = /(?:\n[ \t]*)+$/;
|
||||
|
||||
// Matches either Unix or Windows path separators so `String.split(REGEX)` can
|
||||
// recover the trailing file-name segment from either `/foo/bar.txt` or
|
||||
// `C:\foo\bar.txt`. Used wherever a parameter accepts a user-supplied path.
|
||||
export const FILE_PATH_SEPARATOR_REGEX = /[\\/]/;
|
||||
|
||||
// Separates a file name from its extension, e.g. the '.' in `cover.png`.
|
||||
export const FILE_EXTENSION_SEPARATOR = '.';
|
||||
|
||||
// Matches the `text:` prefix that file-type identifiers use to denote a
|
||||
// plain-text language (e.g. `text:typescript`). Used by tool-call renderers
|
||||
// to recover the underlying highlight.js language.
|
||||
export const TEXT_LANGUAGE_PREFIX_REGEX = /^text:/;
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* String patterns for detecting content kind from MIME types and URIs.
|
||||
* Used with startsWith/includes checks, not as discriminated values.
|
||||
*/
|
||||
|
||||
export const MIME_TYPE_PREFIXES = {
|
||||
IMAGE: 'image/',
|
||||
TEXT: 'text'
|
||||
} as const;
|
||||
|
||||
export const MIME_TYPE_SUBSTRINGS = {
|
||||
JAVASCRIPT: 'javascript',
|
||||
JSON: 'json',
|
||||
TYPESCRIPT: 'typescript'
|
||||
} as const;
|
||||
|
||||
export const URI_PATTERNS = {
|
||||
DATABASE_KEYWORD: 'database',
|
||||
DATABASE_SCHEME: 'db://'
|
||||
} as const;
|
||||
-2
@@ -3,5 +3,3 @@
|
||||
export const CONTROL_ACTION = {
|
||||
END_REASONING: 'reasoning_end'
|
||||
} as const;
|
||||
|
||||
export type ControlAction = (typeof CONTROL_ACTION)[keyof typeof CONTROL_ACTION];
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
* naming changes.
|
||||
*/
|
||||
|
||||
import { STORAGE_APP_NAME } from './storage';
|
||||
import { STORAGE_APP_NAME } from './storage.constants';
|
||||
|
||||
/** IndexedDB database name */
|
||||
export const DB_NAME = STORAGE_APP_NAME;
|
||||
@@ -1,2 +0,0 @@
|
||||
export const VIEWPORT_GUTTER = 8;
|
||||
export const MENU_OFFSET = 6;
|
||||
@@ -0,0 +1,35 @@
|
||||
/** HTTP header handling for API and MCP requests. */
|
||||
export const HEADERS = {
|
||||
/** Canonical casing for the Authorization header (RFC 7235) */
|
||||
AUTHORIZATION: 'Authorization',
|
||||
/** Bearer scheme prefix used for Authorization headers (RFC 6750) */
|
||||
BEARER: 'Bearer ',
|
||||
/** Content-Type HTTP header name */
|
||||
CONTENT_TYPE: 'Content-Type',
|
||||
/** Partial-redaction rules for MCP headers: header name -> visible trailing chars */
|
||||
PARTIAL_REDACT: new Map<string, number>([['mcp-session-id', 5]]),
|
||||
|
||||
/** Header names whose values should be redacted in diagnostic logs */
|
||||
REDACTED: new Set([
|
||||
'authorization',
|
||||
'api-key',
|
||||
'cookie',
|
||||
'mcp-session-id',
|
||||
'proxy-authorization',
|
||||
'set-cookie',
|
||||
'x-auth-token',
|
||||
'x-api-key'
|
||||
]),
|
||||
|
||||
/** Header carrying the stream-session identity (conversation id, optionally with a model suffix) */
|
||||
X_CONVERSATION_ID_HEADER: 'X-Conversation-Id',
|
||||
|
||||
/** Header asking the server to encode a tool's output differently, e.g. read_file returning base64. */
|
||||
X_RESP_TYPE_HEADER: 'x-resp-type',
|
||||
|
||||
/** Header carrying the working directory a tool call runs in; the model cannot override it */
|
||||
X_TOOL_CWD_HEADER: 'x-tool-cwd'
|
||||
};
|
||||
|
||||
/** `X_RESP_TYPE_HEADER` value that makes read_file return raw bytes as base64 instead of text. */
|
||||
export const RESP_TYPE_BASE64 = 'base64';
|
||||
@@ -1,3 +0,0 @@
|
||||
export const MEGAPIXELS_TO_PIXELS = 1_000_000;
|
||||
|
||||
export const HEIC_JPEG_QUALITY = 0.85;
|
||||
@@ -0,0 +1,32 @@
|
||||
/** Image handling constants */
|
||||
|
||||
export const IMAGE = {
|
||||
/** JPEG quality used when transcoding HEIC images. */
|
||||
HEIC_JPEG_QUALITY: 0.85,
|
||||
/** Unit conversion: pixels per megapixel. */
|
||||
MEGAPIXELS_TO_PIXELS: 1_000_000
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* JPEG and EXIF binary format constants for orientation parsing.
|
||||
*/
|
||||
export const EXIF = {
|
||||
/** APP1 segment marker byte, carries the EXIF payload */
|
||||
APP1_MARKER: 0xe1,
|
||||
/** "Exif" signature opening the APP1 payload, big endian uint32 */
|
||||
EXIF_SIGNATURE: 0x45786966,
|
||||
/** Size in bytes of one IFD directory entry */
|
||||
IFD_ENTRY_SIZE: 12,
|
||||
/** JPEG start of image marker */
|
||||
JPEG_SOI_MARKER: 0xffd8,
|
||||
/** EXIF tag id holding the orientation value */
|
||||
ORIENTATION_TAG: 0x0112,
|
||||
/** Bytes of file prefix to scan, the APP1 EXIF segment sits near the start */
|
||||
SCAN_BYTE_LIMIT: 128 * 1024,
|
||||
/** Start of scan marker byte, compressed data begins and no EXIF follows */
|
||||
SOS_MARKER: 0xda,
|
||||
/** TIFF byte order mark for little endian ("II") */
|
||||
TIFF_LITTLE_ENDIAN: 0x4949,
|
||||
/** TIFF magic number following the byte order mark */
|
||||
TIFF_MAGIC: 42
|
||||
} as const;
|
||||
@@ -1,67 +1,61 @@
|
||||
// Central constants export file
|
||||
// All constants should be imported from '$lib/constants'
|
||||
|
||||
export * from './agentic';
|
||||
export * from './api-endpoints';
|
||||
export * from './app';
|
||||
export * from './attachment-labels';
|
||||
export * from './database';
|
||||
export * from './reasoning-effort';
|
||||
export * from './reasoning-effort-tokens';
|
||||
export * from './recommended-mcp-servers';
|
||||
export * from './storage';
|
||||
export * from './attachment-menu';
|
||||
export * from './auto-scroll';
|
||||
export * from './context-gauge-popup';
|
||||
export * from './conversation-import';
|
||||
export * from './binary-detection';
|
||||
export * from './built-in-tools';
|
||||
export * from './cache';
|
||||
export * from './chat-form';
|
||||
export * from './chat-commands';
|
||||
export * from './cli-flags';
|
||||
export * from './code-blocks';
|
||||
export * from './icons';
|
||||
export * from './code';
|
||||
export * from './context-keys';
|
||||
export * from './control-actions';
|
||||
export * from './css-classes';
|
||||
export * from './floating-ui-constraints';
|
||||
export * from './formatters';
|
||||
export * from './key-value-pairs';
|
||||
export * from './icons';
|
||||
export * from './latex-protection';
|
||||
export * from './literal-html';
|
||||
export * from './markdown';
|
||||
export * from './mermaid-blocks';
|
||||
export * from './svg-blocks';
|
||||
export * from './diagram-blocks';
|
||||
export * from './max-bundle-size';
|
||||
export * from './mcp';
|
||||
export * from './mcp-form';
|
||||
export * from './mcp-resource';
|
||||
export * from './mention-badge';
|
||||
export * from './message-export';
|
||||
export * from './path-display';
|
||||
export * from './model-id';
|
||||
export * from './model-loading';
|
||||
export * from './sse';
|
||||
export * from './precision';
|
||||
export * from './processing-info';
|
||||
export * from './pwa';
|
||||
export * from './agentic.constants';
|
||||
export * from './api-endpoints.constants';
|
||||
export * from './app.constants';
|
||||
export * from './database.constants';
|
||||
export * from './reasoning-effort.constants';
|
||||
export * from './recommended-mcp-servers.constants';
|
||||
export * from './storage.constants';
|
||||
export * from './icons.constants';
|
||||
export * from './attachment-menu.constants';
|
||||
export * from './auto-scroll.constants';
|
||||
export * from './context-gauge-popup.constants';
|
||||
export * from './conversation-import.constants';
|
||||
export * from './binary-detection.constants';
|
||||
export * from './content-detection.constants';
|
||||
export * from './built-in-tools.constants';
|
||||
export * from './cache.constants';
|
||||
export * from './chat-form.constants';
|
||||
export * from './cli-flags.constants';
|
||||
export * from './code-block.constants';
|
||||
export * from './context-keys.constants';
|
||||
export * from './control-actions.constants';
|
||||
export * from './css-classes.constants';
|
||||
export * from './formatters.constants';
|
||||
export * from './headers.constants';
|
||||
export * from './key-value-pairs.constants';
|
||||
export * from './latex-protection.constants';
|
||||
export * from './literal-html.constants';
|
||||
export * from './markdown.constants';
|
||||
export * from './mermaid-blocks.constants';
|
||||
export * from './svg-blocks.constants';
|
||||
export * from './diagram-blocks.constants';
|
||||
export * from './max-bundle-size.constants';
|
||||
export * from './error.constants';
|
||||
export * from './image.constants';
|
||||
export * from './mcp.constants';
|
||||
export * from './mcp-form.constants';
|
||||
export * from './mcp-resource.constants';
|
||||
export * from './mention-badge.constants';
|
||||
export * from './message-export.constants';
|
||||
export * from './path-display.constants';
|
||||
export * from './model-id.constants';
|
||||
export * from './model-loading.constants';
|
||||
export * from './precision.constants';
|
||||
export * from './pwa.constants';
|
||||
export * from './routes.constants';
|
||||
export * from './sandbox.constants';
|
||||
export * from './settings-keys.constants';
|
||||
export * from './settings-registry.constants';
|
||||
export * from './special-characters.constants';
|
||||
export * from './stream.constants';
|
||||
export * from './supported-file-types.constants';
|
||||
export * from './table-html-restorer.constants';
|
||||
export * from './title-generation.constants';
|
||||
export * from './ui.constants';
|
||||
export * from './uri-template.constants';
|
||||
export * from './url.constants';
|
||||
export * from './working-directory.constants';
|
||||
export * from './read-media';
|
||||
export * from './routes';
|
||||
export * from './sandbox';
|
||||
export * from './settings-keys';
|
||||
export * from './settings-registry';
|
||||
export * from './stream';
|
||||
export * from './supported-file-types';
|
||||
export * from './table-html-restorer';
|
||||
export * from './title-generation';
|
||||
export * from './tools';
|
||||
export * from './tooltip-config';
|
||||
export * from './ui';
|
||||
export * from './uri-template';
|
||||
export * from './url';
|
||||
export * from './viewport';
|
||||
export * from './working-directory';
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* JPEG and EXIF binary format constants for orientation parsing.
|
||||
*/
|
||||
|
||||
/** Bytes of file prefix to scan, the APP1 EXIF segment sits near the start */
|
||||
export const EXIF_SCAN_BYTE_LIMIT = 128 * 1024;
|
||||
|
||||
/** JPEG start of image marker */
|
||||
export const JPEG_SOI_MARKER = 0xffd8;
|
||||
|
||||
/** APP1 segment marker byte, carries the EXIF payload */
|
||||
export const APP1_MARKER = 0xe1;
|
||||
|
||||
/** Start of scan marker byte, compressed data begins and no EXIF follows */
|
||||
export const SOS_MARKER = 0xda;
|
||||
|
||||
/** "Exif" signature opening the APP1 payload, big endian uint32 */
|
||||
export const EXIF_SIGNATURE = 0x45786966;
|
||||
|
||||
/** TIFF byte order mark for little endian ("II") */
|
||||
export const TIFF_LITTLE_ENDIAN = 0x4949;
|
||||
|
||||
/** TIFF magic number following the byte order mark */
|
||||
export const TIFF_MAGIC = 42;
|
||||
|
||||
/** EXIF tag id holding the orientation value */
|
||||
export const EXIF_ORIENTATION_TAG = 0x0112;
|
||||
|
||||
/** Size in bytes of one IFD directory entry */
|
||||
export const IFD_ENTRY_SIZE = 12;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user