refac
This commit is contained in:
@@ -15,6 +15,17 @@ export type TerminalFeatures = {
|
|||||||
terminal?: boolean;
|
terminal?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TerminalFileRoot = {
|
||||||
|
path: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TerminalCwd = {
|
||||||
|
cwd: string | null;
|
||||||
|
home?: string;
|
||||||
|
root?: TerminalFileRoot;
|
||||||
|
};
|
||||||
|
|
||||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
export type TerminalServer = {
|
export type TerminalServer = {
|
||||||
@@ -49,14 +60,19 @@ export const getCwd = async (
|
|||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
sessionId?: string
|
sessionId?: string
|
||||||
): Promise<string | null> => {
|
): Promise<TerminalCwd | null> => {
|
||||||
const url = `${baseUrl.replace(/\/$/, '')}/files/cwd`;
|
const url = `${baseUrl.replace(/\/$/, '')}/files/cwd`;
|
||||||
const headers: Record<string, string> = { Authorization: `Bearer ${apiKey}` };
|
const headers: Record<string, string> = { Authorization: `Bearer ${apiKey}` };
|
||||||
if (sessionId) headers['X-Session-Id'] = sessionId;
|
if (sessionId) headers['X-Session-Id'] = sessionId;
|
||||||
const res = await fetch(url, { headers }).catch(() => null);
|
const res = await fetch(url, { headers }).catch(() => null);
|
||||||
if (!res || !res.ok) return null;
|
if (!res || !res.ok) return null;
|
||||||
const json = await res.json().catch(() => null);
|
const json = await res.json().catch(() => null);
|
||||||
return json?.cwd ?? null;
|
if (!json) return null;
|
||||||
|
return {
|
||||||
|
cwd: json?.cwd ?? null,
|
||||||
|
home: json?.home,
|
||||||
|
root: json?.root
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listFiles = async (
|
export const listFiles = async (
|
||||||
|
|||||||
@@ -25,7 +25,9 @@
|
|||||||
deleteEntry,
|
deleteEntry,
|
||||||
moveEntry,
|
moveEntry,
|
||||||
setCwd,
|
setCwd,
|
||||||
type FileEntry
|
type FileEntry,
|
||||||
|
type TerminalFileRoot,
|
||||||
|
type TerminalCwd
|
||||||
} from '$lib/apis/terminal';
|
} from '$lib/apis/terminal';
|
||||||
import { isCodeFile } from '$lib/utils/codeHighlight';
|
import { isCodeFile } from '$lib/utils/codeHighlight';
|
||||||
import Folder from '../icons/Folder.svelte';
|
import Folder from '../icons/Folder.svelte';
|
||||||
@@ -88,6 +90,7 @@
|
|||||||
|
|
||||||
// ── Directory state ──────────────────────────────────────────────────
|
// ── Directory state ──────────────────────────────────────────────────
|
||||||
let currentPath = savedPath;
|
let currentPath = savedPath;
|
||||||
|
let fileRoot: TerminalFileRoot | null = null;
|
||||||
let entries: FileEntry[] = [];
|
let entries: FileEntry[] = [];
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let error: string | null = null;
|
let error: string | null = null;
|
||||||
@@ -280,11 +283,8 @@
|
|||||||
terminalEnabled = config?.features?.terminal !== false;
|
terminalEnabled = config?.features?.terminal !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawCwd = await getCwd(terminal.url, terminal.key, chatId ?? undefined);
|
savedPath = applyCwd(await getCwd(terminal.url, terminal.key, chatId ?? undefined));
|
||||||
const cwd = rawCwd ? normalizePath(rawCwd) : null;
|
loadDir(savedPath);
|
||||||
const dir = cwd ? (cwd.endsWith('/') ? cwd : cwd + '/') : '/';
|
|
||||||
savedPath = dir;
|
|
||||||
loadDir(dir);
|
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,7 +305,52 @@
|
|||||||
/** Normalize Windows backslashes to forward slashes. */
|
/** Normalize Windows backslashes to forward slashes. */
|
||||||
const normalizePath = (p: string) => p.replace(/\\/g, '/');
|
const normalizePath = (p: string) => p.replace(/\\/g, '/');
|
||||||
|
|
||||||
|
const asDirectoryPath = (path: string) => {
|
||||||
|
const normalized = normalizePath(path || '/');
|
||||||
|
return normalized.endsWith('/') ? normalized : `${normalized}/`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setFileRoot = (root?: TerminalFileRoot) => {
|
||||||
|
fileRoot = root?.path
|
||||||
|
? {
|
||||||
|
path: asDirectoryPath(root.path),
|
||||||
|
label: root.label || 'Home'
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isInsideFileRoot = (path: string) => {
|
||||||
|
if (!fileRoot) return true;
|
||||||
|
const directory = asDirectoryPath(path);
|
||||||
|
return directory === fileRoot.path || directory.startsWith(fileRoot.path);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clampToFileRoot = (path: string) => {
|
||||||
|
if (!fileRoot) return asDirectoryPath(path);
|
||||||
|
return isInsideFileRoot(path) ? asDirectoryPath(path) : fileRoot.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyCwd = (cwd: TerminalCwd | null) => {
|
||||||
|
setFileRoot(cwd?.root);
|
||||||
|
const path = cwd?.cwd ? asDirectoryPath(cwd.cwd) : (fileRoot?.path ?? '/');
|
||||||
|
return clampToFileRoot(path);
|
||||||
|
};
|
||||||
|
|
||||||
const buildBreadcrumbs = (path: string) => {
|
const buildBreadcrumbs = (path: string) => {
|
||||||
|
if (fileRoot) {
|
||||||
|
const directory = clampToFileRoot(path);
|
||||||
|
const relative = directory === fileRoot.path ? '' : directory.slice(fileRoot.path.length);
|
||||||
|
const parts = relative.split('/').filter(Boolean);
|
||||||
|
return parts.reduce(
|
||||||
|
(acc, part) => {
|
||||||
|
const prev = acc[acc.length - 1];
|
||||||
|
acc.push({ label: part, path: `${prev.path}${part}/` });
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[{ label: fileRoot.label, path: fileRoot.path }]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const parts = path.split('/').filter(Boolean);
|
const parts = path.split('/').filter(Boolean);
|
||||||
const isDrive = /^[A-Za-z]:$/.test(parts[0] ?? '');
|
const isDrive = /^[A-Za-z]:$/.test(parts[0] ?? '');
|
||||||
const root = isDrive ? { label: parts[0], path: `${parts[0]}/` } : { label: '/', path: '/' };
|
const root = isDrive ? { label: parts[0], path: `${parts[0]}/` } : { label: '/', path: '/' };
|
||||||
@@ -348,6 +393,7 @@
|
|||||||
const loadDir = async (path: string) => {
|
const loadDir = async (path: string) => {
|
||||||
const terminal = selectedTerminal;
|
const terminal = selectedTerminal;
|
||||||
if (!terminal) return;
|
if (!terminal) return;
|
||||||
|
const directory = clampToFileRoot(path);
|
||||||
|
|
||||||
loading = true;
|
loading = true;
|
||||||
error = null;
|
error = null;
|
||||||
@@ -355,15 +401,15 @@
|
|||||||
previewPort = null;
|
previewPort = null;
|
||||||
clearFilePreview();
|
clearFilePreview();
|
||||||
clearSelection();
|
clearSelection();
|
||||||
currentPath = path;
|
currentPath = directory;
|
||||||
savedPath = path;
|
savedPath = directory;
|
||||||
pushNavHistory(path);
|
pushNavHistory(directory);
|
||||||
|
|
||||||
const result = await listFiles(terminal.url, terminal.key, path, chatId ?? undefined);
|
const result = await listFiles(terminal.url, terminal.key, directory, chatId ?? undefined);
|
||||||
loading = false;
|
loading = false;
|
||||||
|
|
||||||
// Set working directory on the terminal server (fire-and-forget)
|
// Set working directory on the terminal server (fire-and-forget)
|
||||||
setCwd(terminal.url, terminal.key, path, chatId ?? undefined);
|
setCwd(terminal.url, terminal.key, directory, chatId ?? undefined);
|
||||||
|
|
||||||
if (result === null) {
|
if (result === null) {
|
||||||
error =
|
error =
|
||||||
@@ -788,6 +834,10 @@
|
|||||||
handledDisplayFile = true;
|
handledDisplayFile = true;
|
||||||
showFileNavPath.set(null);
|
showFileNavPath.set(null);
|
||||||
filePath = normalizePath(filePath);
|
filePath = normalizePath(filePath);
|
||||||
|
if (!isInsideFileRoot(filePath)) {
|
||||||
|
await loadDir(fileRoot?.path ?? '/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const lastSlash = filePath.lastIndexOf('/');
|
const lastSlash = filePath.lastIndexOf('/');
|
||||||
const dir = lastSlash > 0 ? filePath.substring(0, lastSlash + 1) : '/';
|
const dir = lastSlash > 0 ? filePath.substring(0, lastSlash + 1) : '/';
|
||||||
@@ -810,6 +860,10 @@
|
|||||||
if (!filePath || !selectedTerminal) return;
|
if (!filePath || !selectedTerminal) return;
|
||||||
showFileNavDir.set(null);
|
showFileNavDir.set(null);
|
||||||
filePath = normalizePath(filePath);
|
filePath = normalizePath(filePath);
|
||||||
|
if (!isInsideFileRoot(filePath)) {
|
||||||
|
await loadDir(fileRoot?.path ?? '/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const lastSlash = filePath.lastIndexOf('/');
|
const lastSlash = filePath.lastIndexOf('/');
|
||||||
const dir = lastSlash > 0 ? filePath.substring(0, lastSlash + 1) : '/';
|
const dir = lastSlash > 0 ? filePath.substring(0, lastSlash + 1) : '/';
|
||||||
@@ -835,10 +889,9 @@
|
|||||||
|
|
||||||
if (chatId || savedPath === '/') {
|
if (chatId || savedPath === '/') {
|
||||||
// Fetch session-specific cwd from the server (or global default for new chats)
|
// Fetch session-specific cwd from the server (or global default for new chats)
|
||||||
const rawCwd = await getCwd(terminal.url, terminal.key, chatId ?? undefined);
|
savedPath = applyCwd(await getCwd(terminal.url, terminal.key, chatId ?? undefined));
|
||||||
const cwd = rawCwd ? normalizePath(rawCwd) : null;
|
|
||||||
if (cwd) savedPath = cwd.endsWith('/') ? cwd : cwd + '/';
|
|
||||||
}
|
}
|
||||||
|
savedPath = clampToFileRoot(savedPath);
|
||||||
loadDir(savedPath);
|
loadDir(savedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user