* fix: show resolved file paths in agent file-link tooltips Bare filenames in agent messages now resolve to their full workspace path on hover. Also raises the daemon's directory-suggestion scan depth so files in deeper package layouts are reachable. * fix(app): keep file-link wrapper stable to avoid layout shift on resolve * fix(app): resolve file links on hover instead of at render time useQuery was firing the daemon RPC for every ambiguous file reference the moment a message rendered, fanning out a wave of requests on chat scroll. Switch to enabled: false + prefetchQuery on hover so RPCs are driven by user intent. Sync-resolvable refs (directFile, external) still seed via initialData and render with no RPC. Also memo on source primitives rather than identity so identical- content sources constructed inline upstream don't bust the memo every render. * Redesign assistant file link resolution * Stabilize assistant file link handlers
88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useMemo,
|
|
useRef,
|
|
type MutableRefObject,
|
|
type ReactNode,
|
|
} from "react";
|
|
import React from "react";
|
|
import type { ToastApi } from "@/components/toast-host";
|
|
import type { OpenFileDisposition } from "@/workspace/file-open";
|
|
import type { InlinePathTarget } from "./parse";
|
|
import type { AssistantFileLinkContext, GetDirectorySuggestions } from "./resolver";
|
|
|
|
export interface AssistantFileLinkDaemonClient {
|
|
getDirectorySuggestions: GetDirectorySuggestions;
|
|
}
|
|
|
|
export interface AssistantFileLinkResolverConfig {
|
|
client?: AssistantFileLinkDaemonClient | null;
|
|
serverId?: string;
|
|
workspaceRoot?: string;
|
|
onOpenWorkspaceFile?: (target: InlinePathTarget, disposition: OpenFileDisposition) => void;
|
|
toast?: ToastApi | null;
|
|
}
|
|
|
|
export interface AssistantFileLinkResolverProviderProps extends AssistantFileLinkResolverConfig {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export interface AssistantFileLinkResolverContextValue {
|
|
configRef: MutableRefObject<AssistantFileLinkResolverConfig>;
|
|
getDirectorySuggestions: GetDirectorySuggestions;
|
|
}
|
|
|
|
const AssistantFileLinkResolverContext =
|
|
createContext<AssistantFileLinkResolverContextValue | null>(null);
|
|
|
|
export function AssistantFileLinkResolverProvider({
|
|
client,
|
|
serverId,
|
|
workspaceRoot,
|
|
onOpenWorkspaceFile,
|
|
toast,
|
|
children,
|
|
}: AssistantFileLinkResolverProviderProps) {
|
|
const configRef = useRef<AssistantFileLinkResolverConfig>({
|
|
client,
|
|
serverId,
|
|
workspaceRoot,
|
|
onOpenWorkspaceFile,
|
|
toast,
|
|
});
|
|
configRef.current = { client, serverId, workspaceRoot, onOpenWorkspaceFile, toast };
|
|
|
|
const getDirectorySuggestions = useCallback<GetDirectorySuggestions>(async (input) => {
|
|
const activeClient = configRef.current.client;
|
|
if (!activeClient) {
|
|
return { entries: [], error: null };
|
|
}
|
|
|
|
const result = await activeClient.getDirectorySuggestions(input);
|
|
return { entries: result.entries, error: result.error };
|
|
}, []);
|
|
|
|
const value = useMemo<AssistantFileLinkResolverContextValue>(
|
|
() => ({ configRef, getDirectorySuggestions }),
|
|
[getDirectorySuggestions],
|
|
);
|
|
|
|
return (
|
|
<AssistantFileLinkResolverContext.Provider value={value}>
|
|
{children}
|
|
</AssistantFileLinkResolverContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAssistantFileLinkResolverContext(): AssistantFileLinkResolverContextValue {
|
|
const context = useContext(AssistantFileLinkResolverContext);
|
|
if (!context) {
|
|
throw new Error("AssistantFileLinkResolverProvider is required for assistant file links.");
|
|
}
|
|
return context;
|
|
}
|
|
|
|
export type { AssistantFileLinkContext };
|