Files
khoj/src/interface/web/app/components/chatMessage/useFileContent.ts
Debanjum e2f377c27b Render file reference as link with file preview on hover/click in web app
Overview
- Khoj references files it used in its response as markdown links.
  For example [1](file://path/to/file.txt#line=121)
- Previously these file links were just shown as raw text
- This change renders khoj's inline file references as a proper links
  and shows file content preview (around specified line if deeplink)
  on hover or click in the web app

Details
- Render inline file references as links in chat message on web app.
  Previously references like [1](file://path/to/file.txt#line=120)
  would be shown as plain text. Now they are rendered as links
- Preview file content of referenced files on click or hover.
  If reference uses a deeplink with line number, the file content
  around that line is shown on hover, click. Click allows viewing file
  preview on mobile, unlike hover. Hover is easier with mouse.
2025-08-22 18:24:27 -07:00

44 lines
1.4 KiB
TypeScript

import { useEffect, useState } from "react";
export interface UseFileContentResult {
content: string;
loading: boolean;
error: string | null;
}
// Fetch file content for a given path when `enabled` is true.
export function useFileContent(path: string | undefined, enabled: boolean): UseFileContentResult {
const [content, setContent] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
async function run() {
if (!enabled || !path) return;
setLoading(true);
setError(null);
setContent("");
try {
const resp = await fetch(`/api/content/file?file_name=${encodeURIComponent(path)}`);
if (!resp.ok) {
throw new Error(`Failed to fetch file content (${resp.status})`);
}
const data = await resp.json();
if (!cancelled) setContent(data.raw_text || "");
} catch (err) {
if (!cancelled)
setError(err instanceof Error ? err.message : "Failed to load file content");
} finally {
if (!cancelled) setLoading(false);
}
}
run();
return () => {
cancelled = true;
};
}, [path, enabled]);
return { content, loading, error };
}