From 9bb2fb892abe681277e3921cb00b96c6157b007e Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 6 Jun 2025 17:36:34 -0700
Subject: [PATCH 1/4] Add file type override for multimodal file input
---
.../components/files/FileTypeSelect.svelte | 58 ++++++++++++++++++
.../files/MultimodalFileInput.svelte | 30 ++++++----
web/src/lib/components/files/index.svelte.ts | 59 +++++++++++++++++--
.../ShikiThemeSelector.svelte | 4 +-
web/src/lib/diff-viewer-multi-file.svelte.ts | 4 +-
web/src/lib/util.ts | 6 ++
web/src/routes/LoadDiffDialog.svelte | 4 +-
7 files changed, 142 insertions(+), 23 deletions(-)
create mode 100644 web/src/lib/components/files/FileTypeSelect.svelte
diff --git a/web/src/lib/components/files/FileTypeSelect.svelte b/web/src/lib/components/files/FileTypeSelect.svelte
new file mode 100644
index 0000000..30d623b
--- /dev/null
+++ b/web/src/lib/components/files/FileTypeSelect.svelte
@@ -0,0 +1,58 @@
+
+
+
+
+
+ File Type
+
+
+ {#if value === "auto"}
+ Infer Type
+ {:else}
+ {value}
+ {/if}
+
+
+
+
+ {#if allowAuto}
+
+
+ Infer Type
+
+
+ {/if}
+
+ {#each languageKeys as langKey (langKey)}
+
+ {langKey}
+
+ {/each}
+
+
+
+
+
diff --git a/web/src/lib/components/files/MultimodalFileInput.svelte b/web/src/lib/components/files/MultimodalFileInput.svelte
index fed02b5..219eb5d 100644
--- a/web/src/lib/components/files/MultimodalFileInput.svelte
+++ b/web/src/lib/components/files/MultimodalFileInput.svelte
@@ -3,13 +3,15 @@
import { box } from "svelte-toolbelt";
import { RadioGroup } from "bits-ui";
import SingleFileInput from "$lib/components/files/SingleFileInput.svelte";
+ import FileTypeSelect from "$lib/components/files/FileTypeSelect.svelte";
- let { state = $bindable(), label = "File", required = false }: MultimodalFileInputProps = $props();
+ let { state = $bindable(), label = "File", required = false, fileTypeOverride = true }: MultimodalFileInputProps = $props();
const instance = new MultimodalFileInputState({
state,
label: box.with(() => label),
required: box.with(() => required),
+ fileTypeOverride: box.with(() => fileTypeOverride),
});
state = instance;
@@ -74,12 +76,11 @@
{#snippet radioItem(name: string)}
-
- {#snippet children({ checked })}
-
- {name}
-
- {/snippet}
+
+ {name}
{/snippet}
@@ -91,11 +92,16 @@
ondrop={handleDrop}
ondragleavecapture={handleDragLeave}
>
-
- {@render radioItem("File")}
- {@render radioItem("URL")}
- {@render radioItem("Text")}
-
+
+
+ {@render radioItem("File")}
+ {@render radioItem("URL")}
+ {@render radioItem("Text")}
+
+ {#if fileTypeOverride}
+ instance.getFileType(), (v) => instance.setFileType(v)} />
+ {/if}
+
{#if instance.mode === "file"}
{@render fileInput()}
{:else if instance.mode === "url"}
diff --git a/web/src/lib/components/files/index.svelte.ts b/web/src/lib/components/files/index.svelte.ts
index 0aceded..946e102 100644
--- a/web/src/lib/components/files/index.svelte.ts
+++ b/web/src/lib/components/files/index.svelte.ts
@@ -1,5 +1,6 @@
import { type ReadableBoxedValues } from "svelte-toolbelt";
-import { lazyPromise } from "$lib/util";
+import { getExtensionForLanguage, lazyPromise } from "$lib/util";
+import type { BundledLanguage, SpecialLanguage } from "shiki";
export interface FileSystemEntry {
fileName: string;
@@ -132,6 +133,8 @@ function filesToDirectory(files: FileList): DirectoryEntry {
return ret;
}
+export type FileType = SpecialLanguage | BundledLanguage | "auto";
+
export type FileInputMode = "file" | "url" | "text";
export type MultimodalFileInputValueMetadata = {
@@ -144,6 +147,7 @@ export type MultimodalFileInputProps = {
label?: string | undefined;
required?: boolean | undefined;
+ fileTypeOverride?: boolean | undefined;
};
export type MultimodalFileInputStateProps = {
@@ -151,14 +155,18 @@ export type MultimodalFileInputStateProps = {
} & ReadableBoxedValues<{
label: string;
required: boolean;
+ fileTypeOverride: boolean;
}>;
export class MultimodalFileInputState {
private readonly opts: MultimodalFileInputStateProps;
mode: FileInputMode = $state("file");
text: string = $state("");
+ textType: FileType = $state("plaintext");
file: File | undefined = $state(undefined);
+ fileType: FileType = $state("auto");
url: string = $state("");
+ urlType: FileType = $state("auto");
private urlResolver = $derived.by(() => {
const url = this.url;
return lazyPromise(async () => {
@@ -185,22 +193,56 @@ export class MultimodalFileInputState {
if (this.opts.state) {
this.mode = this.opts.state.mode;
this.text = this.opts.state.text;
+ this.textType = this.opts.state.textType;
this.file = this.opts.state.file;
+ this.fileType = this.opts.state.fileType;
this.url = this.opts.state.url;
+ this.urlType = this.opts.state.urlType;
this.urlResolver = this.opts.state.urlResolver;
}
}
+ getFileType(): FileType {
+ if (this.mode === "file") {
+ return this.fileType;
+ } else if (this.mode === "url") {
+ return this.urlType;
+ } else if (this.mode === "text") {
+ return this.textType;
+ }
+ throw new Error("Invalid mode");
+ }
+
+ setFileType(fileType: FileType) {
+ if (this.mode === "file") {
+ this.fileType = fileType;
+ } else if (this.mode === "url") {
+ this.urlType = fileType;
+ } else if (this.mode === "text") {
+ this.textType = fileType;
+ } else {
+ throw new Error("Invalid mode");
+ }
+ }
+
+ private getExtensionOrBlank() {
+ const fileType = this.getFileType();
+ if (fileType === "auto") {
+ return "";
+ }
+ return getExtensionForLanguage(fileType);
+ }
+
get metadata(): MultimodalFileInputValueMetadata | null {
const mode = this.mode;
const label = this.opts.label.current;
if (mode === "file" && this.file !== undefined) {
const file = this.file;
- return { type: "file", name: file.name };
+ return { type: "file", name: `${file.name}${this.getExtensionOrBlank()}` };
} else if (mode === "url" && this.url !== "") {
- return { type: "url", name: this.url };
+ return { type: "url", name: `${this.url}${this.getExtensionOrBlank()}` };
} else if (mode === "text" && this.text !== "") {
- return { type: "text", name: `${label} (Text Input)` };
+ return { type: "text", name: `${label}${this.getExtensionOrBlank()}` };
} else {
return null;
}
@@ -228,20 +270,29 @@ export class MultimodalFileInputState {
swapState(other: MultimodalFileInputState) {
const mode = this.mode;
const text = this.text;
+ const textType = this.textType;
const file = this.file;
+ const fileType = this.fileType;
const url = this.url;
+ const urlType = this.urlType;
const urlResolver = this.urlResolver;
this.mode = other.mode;
this.text = other.text;
+ this.textType = other.textType;
this.file = other.file;
+ this.fileType = other.fileType;
this.url = other.url;
+ this.urlType = other.urlType;
this.urlResolver = other.urlResolver;
other.mode = mode;
other.text = text;
+ other.textType = textType;
other.file = file;
+ other.fileType = fileType;
other.url = url;
+ other.urlType = urlType;
other.urlResolver = urlResolver;
}
}
diff --git a/web/src/lib/components/settings-popover/ShikiThemeSelector.svelte b/web/src/lib/components/settings-popover/ShikiThemeSelector.svelte
index 40e945a..099b34c 100644
--- a/web/src/lib/components/settings-popover/ShikiThemeSelector.svelte
+++ b/web/src/lib/components/settings-popover/ShikiThemeSelector.svelte
@@ -22,14 +22,14 @@
{capitalizeFirstLetter(mode)} theme
(triggerLabelW = e[0].target.scrollWidth)}
aria-label="Current {mode} syntax highlighting theme"
- class="scrolling-text grow text-center text-nowrap"
+ class="scrolling-text grow text-left text-nowrap"
style="--scroll-distance: -{scrollDistance}px;"
>
{value}
diff --git a/web/src/lib/diff-viewer-multi-file.svelte.ts b/web/src/lib/diff-viewer-multi-file.svelte.ts
index ca980b0..78afdf4 100644
--- a/web/src/lib/diff-viewer-multi-file.svelte.ts
+++ b/web/src/lib/diff-viewer-multi-file.svelte.ts
@@ -282,9 +282,7 @@ export class MultiFileDiffViewerState {
private static readonly context = new Context
("MultiFileDiffViewerState");
static init() {
- const state = new MultiFileDiffViewerState();
- MultiFileDiffViewerState.context.set(state);
- return state;
+ return MultiFileDiffViewerState.context.set(new MultiFileDiffViewerState());
}
static get() {
diff --git a/web/src/lib/util.ts b/web/src/lib/util.ts
index 59635b1..a20c9ea 100644
--- a/web/src/lib/util.ts
+++ b/web/src/lib/util.ts
@@ -373,6 +373,12 @@ const languageMap: { [key: string]: BundledLanguage | SpecialLanguage } = {
".yml": "yaml",
};
+const reverseLanguageMap = Object.fromEntries(Object.entries(languageMap).map(([ext, lang]) => [lang, ext]));
+
+export function getExtensionForLanguage(language: BundledLanguage | SpecialLanguage): string {
+ return reverseLanguageMap[language] || ".txt";
+}
+
export function guessLanguageFromExtension(fileName: string): BundledLanguage | SpecialLanguage {
const lowerFileName = fileName.toLowerCase();
const extensionIndex = lowerFileName.lastIndexOf(".");
diff --git a/web/src/routes/LoadDiffDialog.svelte b/web/src/routes/LoadDiffDialog.svelte
index 5928a05..ba6b78d 100644
--- a/web/src/routes/LoadDiffDialog.svelte
+++ b/web/src/routes/LoadDiffDialog.svelte
@@ -382,7 +382,7 @@
Load a diff
@@ -464,7 +464,7 @@
From Patch File
-
+
Go
From a08681a3e806bdea7a9457628affa0908205d65b Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Sat, 7 Jun 2025 14:16:21 -0700
Subject: [PATCH 2/4] Add missing type to directory input buttons
---
web/src/lib/components/files/DirectoryInput.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/src/lib/components/files/DirectoryInput.svelte b/web/src/lib/components/files/DirectoryInput.svelte
index 556e32d..087dd12 100644
--- a/web/src/lib/components/files/DirectoryInput.svelte
+++ b/web/src/lib/components/files/DirectoryInput.svelte
@@ -26,6 +26,6 @@
const mergedProps = mergeProps({ onclick }, restProps);
-
+
{@render children?.({ directory })}
From 4866ca96e99b948b444cad87db5a1c00217eec64 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Sat, 7 Jun 2025 14:16:37 -0700
Subject: [PATCH 3/4] Add margin to blacklist popover
---
web/src/routes/LoadDiffDialog.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/src/routes/LoadDiffDialog.svelte b/web/src/routes/LoadDiffDialog.svelte
index ba6b78d..04f2334 100644
--- a/web/src/routes/LoadDiffDialog.svelte
+++ b/web/src/routes/LoadDiffDialog.svelte
@@ -533,7 +533,7 @@
-
+
{@render blacklistPopoverContent()}
From 0b9c3f9319ca70af2644e520de74a312af62623c Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Tue, 10 Jun 2025 13:08:14 -0700
Subject: [PATCH 4/4] Add todo
---
web/src/lib/components/files/FileTypeSelect.svelte | 1 +
1 file changed, 1 insertion(+)
diff --git a/web/src/lib/components/files/FileTypeSelect.svelte b/web/src/lib/components/files/FileTypeSelect.svelte
index 30d623b..8589e0a 100644
--- a/web/src/lib/components/files/FileTypeSelect.svelte
+++ b/web/src/lib/components/files/FileTypeSelect.svelte
@@ -20,6 +20,7 @@
+
File Type