Commit 7689fd33 authored by Kantz's avatar Kantz
Browse files

retival wird in app angezeigt und lässt sich inspecten auf seperater seite.

parent c2121652
This diff is collapsed.
......@@ -11,7 +11,8 @@
},
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0"
"react-dom": "^19.2.0",
"react-markdown": "^10.1.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
......
import { useEffect, useRef } from "react";
import ReactMarkdown from "react-markdown";
declare global {
interface Window {
MathJax?: {
typesetPromise?: (elements?: Element[]) => Promise<void>;
};
}
}
type DocCardProps = {
title: string;
snippet: string;
subtitle?: string;
snippetMarkdown: string;
score?: number;
onCite?: () => void;
onInspect?: () => void;
defaultOpen?: boolean;
};
export default function DocCard({
title,
snippet,
subtitle,
snippetMarkdown,
score,
onCite,
defaultOpen = true,
onInspect,
defaultOpen = false,
}: DocCardProps) {
const contentRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (window.MathJax?.typesetPromise && contentRef.current) {
window.MathJax.typesetPromise([contentRef.current]).catch(() => undefined);
}
}, [snippetMarkdown]);
return (
<details className="doc-card" open={defaultOpen}>
<summary className="doc-title">{title}</summary>
<div className="doc-snippet">{snippet}</div>
<summary className="doc-title">
<span>{title}</span>
{typeof score === "number" ? (
<span className="doc-score">{score.toFixed(3)}</span>
) : null}
</summary>
{subtitle ? <div className="doc-subtitle">{subtitle}</div> : null}
<div className="doc-snippet" ref={contentRef}>
<ReactMarkdown>{snippetMarkdown}</ReactMarkdown>
</div>
<div className="doc-actions">
<button className="btn small" type="button" onClick={onInspect}>
Inspect
</button>
<button className="btn small" type="button" onClick={onCite}>
Cite
</button>
</div>
</details>
);
}
import DocCard from "./DocCard";
export type RetrievedDoc = {
id: string;
title: string;
snippet: string;
uid: string;
doc_type: string;
score?: number;
metadata: {
section_index?: number | null;
subsection_index?: number | null;
child_index?: number | null;
section_title?: string | null;
subsection_title?: string | null;
title?: string | null;
type?: string | null;
box_hint?: string | null;
path?: string | null;
};
markdown: string;
};
type DocPanelProps = {
docs: RetrievedDoc[];
onCiteDoc?: (id: string) => void;
isLoading?: boolean;
error?: string | null;
onCiteDoc?: (uid: string) => void;
onInspectDoc?: (doc: RetrievedDoc) => void;
};
export default function DocPanel({ docs, onCiteDoc }: DocPanelProps) {
const buildTitle = (doc: RetrievedDoc) => {
const meta = doc.metadata;
return (
meta.title ||
meta.subsection_title ||
meta.section_title ||
meta.path ||
"Untitled"
);
};
const buildSubtitle = (doc: RetrievedDoc) => {
const meta = doc.metadata;
const parts: string[] = [];
if (meta.section_index !== null && meta.section_index !== undefined) {
parts.push(`s${meta.section_index}`);
}
if (meta.subsection_index !== null && meta.subsection_index !== undefined) {
parts.push(`ss${meta.subsection_index}`);
}
if (meta.child_index !== null && meta.child_index !== undefined) {
parts.push(`c${meta.child_index}`);
}
if (doc.doc_type) {
parts.push(doc.doc_type);
}
return parts.length ? parts.join(" - ") : undefined;
};
const buildSnippet = (markdown: string) => {
const trimmed = markdown.trim();
if (trimmed.length <= 240) {
return trimmed;
}
return `${trimmed.slice(0, 240)}...`;
};
export default function DocPanel({
docs,
isLoading,
error,
onCiteDoc,
onInspectDoc,
}: DocPanelProps) {
return (
<div className="retrieval-panel">
<div className="retrieval-title">Retrieved Notes</div>
{isLoading ? <div className="retrieval-state">Loading...</div> : null}
{error ? <div className="retrieval-state error">{error}</div> : null}
{!isLoading && !error && docs.length === 0 ? (
<div className="retrieval-state">No results yet.</div>
) : null}
{docs.map((doc) => (
<DocCard
key={doc.id}
title={doc.title}
snippet={doc.snippet}
onCite={onCiteDoc ? () => onCiteDoc(doc.id) : undefined}
key={doc.uid}
title={buildTitle(doc)}
subtitle={buildSubtitle(doc)}
snippetMarkdown={buildSnippet(doc.markdown)}
score={doc.score}
onCite={onCiteDoc ? () => onCiteDoc(doc.uid) : undefined}
onInspect={onInspectDoc ? () => onInspectDoc(doc) : undefined}
/>
))}
</div>
......
......@@ -15,23 +15,13 @@ const initialMessages: ChatMessage[] = [
},
];
const mockDocs: RetrievedDoc[] = [
{
id: "d1",
title: "Quadratic Factoring Basics",
snippet: "For x^2 + bx + c, find p*q = c and p+q = b.",
},
{
id: "d2",
title: "Worked Example: x^2 - 5x + 6",
snippet: "Numbers are -2 and -3, so (x-2)(x-3).",
},
];
export default function App() {
const [messages, setMessages] = useState<ChatMessage[]>(initialMessages);
const [draft, setDraft] = useState("");
const [isCanvasVisible, setIsCanvasVisible] = useState(false);
const [retrievedDocs, setRetrievedDocs] = useState<RetrievedDoc[]>([]);
const [retrievalLoading, setRetrievalLoading] = useState(false);
const [retrievalError, setRetrievalError] = useState<string | null>(null);
const handleSend = async () => {
const trimmed = draft.trim();
......@@ -49,6 +39,26 @@ export default function App() {
setDraft("");
try {
setRetrievalLoading(true);
setRetrievalError(null);
const retrievalRequest = fetch("http://localhost:8000/api/retrieval/query", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: trimmed,
k: 8,
expand_links: true,
}),
}).then(async (res) => {
if (!res.ok) {
throw new Error(`Retrieval failed: ${res.status}`);
}
const payload = await res.json();
const docs: RetrievedDoc[] = payload.children || [];
setRetrievedDocs(docs);
});
const response = await fetch("http://localhost:8000/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
......@@ -75,6 +85,8 @@ export default function App() {
},
]);
}
await retrievalRequest;
} catch (error) {
setMessages((prev) => [
...prev,
......@@ -84,7 +96,10 @@ export default function App() {
text: "Chat request failed. Check the API logs.",
},
]);
setRetrievalError("Retrieval failed. Check the API logs.");
void error;
} finally {
setRetrievalLoading(false);
}
};
......@@ -137,6 +152,66 @@ export default function App() {
setDraft((prev) => (prev ? `${prev} [${docId}]` : `[${docId}]`));
};
const handleInspectDoc = (doc: RetrievedDoc) => {
const title =
doc.metadata.title ||
doc.metadata.subsection_title ||
doc.metadata.section_title ||
doc.metadata.path ||
"Document";
const html = `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>${title}</title>
<style>
body { font-family: "Space Grotesk", sans-serif; margin: 24px; color: #1d1b16; }
h1 { font-size: 22px; margin-bottom: 12px; }
.meta { color: #6f675d; font-size: 12px; margin-bottom: 16px; }
.content { max-width: 900px; }
pre, code { background: #f6f0e4; padding: 2px 4px; border-radius: 4px; }
</style>
</head>
<body>
<h1 id="title"></h1>
<div class="meta" id="meta"></div>
<div class="content" id="content"></div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
const markdown = ${JSON.stringify(doc.markdown)};
const title = ${JSON.stringify(title)};
const path = ${JSON.stringify(doc.metadata.path || "")};
document.getElementById("title").textContent = title;
document.getElementById("meta").textContent = path;
document.getElementById("content").innerHTML = marked.parse(markdown);
</script>
<script>
window.MathJax = {
tex: {
inlineMath: [["$", "$"], ["\\\\(", "\\\\)"]],
displayMath: [["$$", "$$"], ["\\\\[", "\\\\]"]],
}
};
</script>
<script
id="mathjax-script"
async
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"
></script>
</body>
</html>`;
const tab = window.open("", "_blank");
if (!tab) {
return;
}
tab.document.open();
tab.document.write(html);
tab.document.close();
};
return (
<div className="app-shell">
<header className="app-header">
......@@ -174,7 +249,13 @@ export default function App() {
</section>
<aside className="retrieval-column">
<DocPanel docs={mockDocs} onCiteDoc={handleCiteDoc} />
<DocPanel
docs={retrievedDocs}
isLoading={retrievalLoading}
error={retrievalError}
onCiteDoc={handleCiteDoc}
onInspectDoc={handleInspectDoc}
/>
</aside>
</main>
</div>
......
......@@ -13,12 +13,8 @@ body {
margin: 0;
}
#root {
min-height: 100vh;
}
.app-shell {
height: 100vh;
min-height: 100vh;
padding: 24px;
background: radial-gradient(circle at top left, #fff7e6, #f4f1ea);
display: flex;
......@@ -81,7 +77,6 @@ body {
gap: 24px;
flex: 1;
min-height: 0;
overflow: hidden;
}
.chat-column {
......@@ -247,6 +242,8 @@ body {
display: flex;
flex-direction: column;
gap: 12px;
max-height: 80vh;
overflow-y: auto;
}
.retrieval-title {
......@@ -263,6 +260,21 @@ body {
.doc-title {
font-weight: 600;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.doc-score {
font-size: 11px;
color: #6f675d;
margin-left: 8px;
}
.doc-subtitle {
font-size: 12px;
color: #6f675d;
margin: 6px 0 2px;
}
.doc-snippet {
......@@ -270,6 +282,25 @@ body {
color: #6f675d;
}
.doc-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.retrieval-state {
padding: 10px 12px;
border-radius: 10px;
background: #f6f0e4;
color: #6f675d;
font-size: 12px;
}
.retrieval-state.error {
background: #f3d9d6;
color: #8a3b2f;
}
@media (max-width: 900px) {
.app-main {
grid-template-columns: 1fr;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment