mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-08-05 01:20:45 -05:00
* server : extend file_glob_search for UI pickers * ui : add per-conversation working directory with picker * ui : add path navigation and search scope to cwd picker Treat path-like queries (starting with / or ~) as directory navigation instead of glob-matching the whole query: search the parent for the last segment, and descend into an exactly-typed directory by listing its children. Show the effective search scope in the footer and auto-search on open so the current directory and its siblings appear immediately. Assisted-by: Claude * db : persist per-call tool cwd on tool result messages * ui : abbreviate tool paths under home with a tilde * ui : show the per-call cwd on exec shell rows * ui : clarify the synthetic cwd message for the model * ui : reuse the trailing cwd row on a repeated pick * ui : don't jump when a cwd row is injected mid-chat * chore: Formatting * refactor: Cleanup comments * ui : unify working directory naming and add a synthetic-message flag * ui : render synthetic cwd rows without a scroll jump * ui : decouple the working directory picker into utils and sub-components * ui : add get_info tool call block * chore: Formatting * refactor: Cleanup * refactor: Cleanup * refactor: Cleanup * fix: UI * server : harden file_glob_search listing (kind enum, timeout, symlink guard, absolute base) * ui : use persisted isSynthetic flag for cwd rows, drop legacy formats * ui : cache picker search, fail visibly on native resolve * ui : escape glob metacharacters in picker search glob * ui : simplify auto-scroll pin * chore: Format * fix: Use `SvelteMap` * refactor: Post-review fixes * ui: accept Windows roots in the working directory picker recognize a drive root (C:) and a UNC share (//host/share) as path navigation, alongside the POSIX root and ~, so a query like D:\repos lists that directory instead of glob-matching it under the home dir split below the root, so a bare drive resolves to its root rather than to a drive-relative prefix rewrite backslashes into forward slashes only when the query carries a Windows root, since a backslash is a legal POSIX filename character paths keep travelling with forward slashes, which is what the server returns and what Windows accepts --------- Co-authored-by: Pascal <admin@serveurperso.com>
94 lines
3.2 KiB
TypeScript
94 lines
3.2 KiB
TypeScript
import { PATH_SEPARATOR } from '$lib/constants/mcp-resource';
|
|
import { TRAILING_SLASHES_REGEX } from '$lib/constants/url';
|
|
import {
|
|
CWD_CHANGED_PREFIX,
|
|
CWD_CLEARED_TEXT,
|
|
CWD_LINK_REGEX,
|
|
FILE_URI_PREFIX,
|
|
HOME_TILDE,
|
|
HOME_TILDE_PREFIX
|
|
} from '$lib/constants';
|
|
|
|
/**
|
|
* Last non-empty slash-delimited segment of `path`, with trailing
|
|
* slashes stripped. Returns the input unchanged when no `/` is present.
|
|
*/
|
|
export function lastPathSegment(p: string): string {
|
|
const trimmed = p.replace(TRAILING_SLASHES_REGEX, '');
|
|
const idx = trimmed.lastIndexOf(PATH_SEPARATOR);
|
|
return idx === -1 ? trimmed : trimmed.slice(idx + 1);
|
|
}
|
|
|
|
/**
|
|
* Abbreviate `path` to `~/...` when it sits under `home`, or to `~` when
|
|
* it equals `home`. Falls back to `lastPathSegment(path)` when home is
|
|
* unknown or the path is outside it. `~` semantics are reserved for the
|
|
* home directory, mirroring how shells render it.
|
|
*/
|
|
export function abbreviateWorkingDir(
|
|
path: string | null | undefined,
|
|
home: string | null | undefined
|
|
): string {
|
|
if (!path) return '';
|
|
if (!home) return lastPathSegment(path);
|
|
if (path === home) return HOME_TILDE;
|
|
if (path.startsWith(home + PATH_SEPARATOR))
|
|
return HOME_TILDE_PREFIX + path.slice(home.length + 1);
|
|
return lastPathSegment(path);
|
|
}
|
|
|
|
/**
|
|
* Replace a leading `home` prefix in `path` with `~`. Unlike
|
|
* abbreviateWorkingDir, paths outside `home` (or an unknown home) are
|
|
* returned unchanged - used for tool-call path displays where the full
|
|
* path matters.
|
|
*/
|
|
export function abbreviateHome(path: string, home: string | null | undefined): string {
|
|
if (!home) return path;
|
|
if (path === home) return HOME_TILDE;
|
|
if (path.startsWith(home + PATH_SEPARATOR))
|
|
return HOME_TILDE_PREFIX + path.slice(home.length + 1);
|
|
return path;
|
|
}
|
|
|
|
export { CWD_CHANGED_PREFIX, CWD_CLEARED_TEXT } from '$lib/constants';
|
|
|
|
export interface CwdMessageInfo {
|
|
// absolute server-side path, null when the cwd was cleared
|
|
path: string | null;
|
|
// display form shown in the UI (e.g. ~/Documents)
|
|
display: string;
|
|
}
|
|
|
|
/**
|
|
* Format a synthetic cwd-change message. The text mirrors what the UI
|
|
* renders for it; the path travels as `[file:///abs/path](display)` so
|
|
* both the absolute and the short form are visible to the model and
|
|
* parseable back by the UI.
|
|
*/
|
|
export function formatCwdMessage(cwd: string, home: string | null): string {
|
|
const display = abbreviateWorkingDir(cwd, home);
|
|
return `${CWD_CHANGED_PREFIX}[${FILE_URI_PREFIX}${cwd}](${display}).`;
|
|
}
|
|
|
|
/**
|
|
* Parse a synthetic cwd message back into its parts. The caller must already
|
|
* know the message is synthetic (via the persisted `isSynthetic` flag); this
|
|
* only extracts the path from the message text. Returns null when `content`
|
|
* is not a cwd message.
|
|
*/
|
|
export function parseCwdMessage(content: string): CwdMessageInfo | null {
|
|
const trimmed = content.trim();
|
|
if (trimmed === CWD_CLEARED_TEXT) {
|
|
return { path: null, display: '' };
|
|
}
|
|
if (trimmed.startsWith(CWD_CHANGED_PREFIX)) {
|
|
const rest = trimmed.slice(CWD_CHANGED_PREFIX.length);
|
|
// not anchored to the end: guidance may follow the link
|
|
const link = rest.match(CWD_LINK_REGEX);
|
|
if (link) return { path: link[1], display: link[2] };
|
|
return { path: rest, display: rest };
|
|
}
|
|
return null;
|
|
}
|