diff --git a/src/lib/apis/terminal/index.ts b/src/lib/apis/terminal/index.ts index 5ffb8f51d..34920e2d5 100644 --- a/src/lib/apis/terminal/index.ts +++ b/src/lib/apis/terminal/index.ts @@ -112,3 +112,74 @@ export const uploadToTerminal = async ( }); return res; }; + +export const createDirectory = async ( + baseUrl: string, + apiKey: string, + path: string +): Promise<{ path: string } | null> => { + const url = `${baseUrl.replace(/\/$/, '')}/files/mkdir`; + const res = await fetch(url, { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ path }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.error('open-terminal createDirectory error:', err); + return null; + }); + return res; +}; + +export const deleteEntry = async ( + baseUrl: string, + apiKey: string, + path: string +): Promise<{ path: string; type: string } | null> => { + const url = `${baseUrl.replace(/\/$/, '')}/files/delete?path=${encodeURIComponent(path)}`; + const res = await fetch(url, { + method: 'DELETE', + headers: { Authorization: `Bearer ${apiKey}` } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.error('open-terminal deleteEntry error:', err); + return null; + }); + return res; +}; + +export const setCwd = async ( + baseUrl: string, + apiKey: string, + path: string +): Promise<{ cwd: string } | null> => { + const url = `${baseUrl.replace(/\/$/, '')}/files/cwd`; + const res = await fetch(url, { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ path }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.error('open-terminal setCwd error:', err); + return null; + }); + return res; +}; diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index c59c7de47..0d67858d9 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -46,6 +46,7 @@ showEmbeds } from '$lib/stores'; + import { getCwd } from '$lib/apis/terminal'; import { convertMessagesToHistory, copyToClipboard, @@ -2126,6 +2127,22 @@ }); } + // Build terminal servers with current CWD injected into run_command descriptions + const terminalServersWithCwd = await (async () => { + const terminals = JSON.parse(JSON.stringify($terminalServers ?? [])); + const configs = ($settings?.terminalServers ?? []).filter((s) => s.enabled); + await Promise.all( + configs.map(async (t) => { + const cwd = await getCwd(t.url, t.key ?? '').catch(() => null); + if (!cwd) return; + const server = terminals.find((s) => s.url === t.url); + const spec = server?.specs?.find((s) => s.name === 'run_command'); + if (spec) spec.description += `\n\nThe current working directory is: ${cwd}`; + }) + ); + return terminals; + })(); + const res = await generateOpenAIChatCompletion( localStorage.token, { @@ -2152,7 +2169,7 @@ ...($toolServers ?? []).filter( (server, idx) => toolServerIds.includes(idx) || toolServerIds.includes(server?.id) ), - ...($terminalServers ?? []) + ...terminalServersWithCwd ], features: getFeatures(), variables: { diff --git a/src/lib/components/chat/FileNav.svelte b/src/lib/components/chat/FileNav.svelte index 6dde3cf90..c0c8c2c87 100644 --- a/src/lib/components/chat/FileNav.svelte +++ b/src/lib/components/chat/FileNav.svelte @@ -4,6 +4,7 @@ + { + if (deleteTarget) { + handleDelete(deleteTarget.path, deleteTarget.name); + deleteTarget = null; + } + }} +/> + {#if !configured}
@@ -224,33 +304,49 @@
{/if} - -
- {#each breadcrumbs as crumb, i} - {#if i > 1} + +
+
+ {#each breadcrumbs as crumb, i} + {#if i > 1} + / + {/if} + + {/each} + {#if selectedFile} / + + {selectedFile.split('/').pop()} + {/if} - - {/each} - {#if selectedFile} - / - - {selectedFile.split('/').pop()} - +
+ + {#if !selectedFile} + + + {/if}
@@ -308,59 +404,150 @@
{:else if error}
{error}
- {:else if entries.length === 0} + {:else if entries.length === 0 && !creatingFolder}
{$i18n.t('Empty — drop files here to upload')}
- {:else} -
+ {/if} + + {#if entries.length > 0 || creatingFolder} + + {#if entry.type === 'directory'} + + {:else} + + + + {/if} + + {entry.name} + + {#if entry.type === 'file' && entry.size !== undefined} + {formatSize(entry.size)} + {/if} + + + + e.stopPropagation()} + aria-label={$i18n.t('More')} + > + + + + + {#if entry.type !== "directory"} + { + e.stopPropagation(); + downloadFile(`${currentPath}${entry.name}`); + }} + > + + + + +
{$i18n.t('Download')}
+
+ {/if} + + { + e.stopPropagation(); + deleteTarget = { + path: `${currentPath}${entry.name}`, + name: entry.name + }; + showDeleteConfirm = true; + }} + > + +
{$i18n.t('Delete')}
+
+
+
+ + + {/each} + + {/if} {/if} {/if} diff --git a/src/lib/components/icons/NewFolderAlt.svelte b/src/lib/components/icons/NewFolderAlt.svelte new file mode 100644 index 000000000..fe02b0767 --- /dev/null +++ b/src/lib/components/icons/NewFolderAlt.svelte @@ -0,0 +1,16 @@ + + + + + + + diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 34bc6f1bd..db3623e6a 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -153,6 +153,7 @@ } return true; }); + terminalServers.set(terminalServersData); } else { terminalServers.set([]);