public
anord
read
Ledger
Why work hard when you can work easier?
Languages
Repository composition by tracked source files.
TypeScript
86%
CSS
10%
SQL
3%
Shell
1%
HTML
0%
Trace
apps/web/src/components/AskAiPage.tsx
Trace helps you understand code history line by line. See who changed each line, when it changed, and which commit introduced it.
Author
Date
Commit
Line
Code
1
import { useState } from "react";
2
import { Link } from "react-router-dom";
3
import type { AiCitation } from "@ledger/shared";
4
import { api } from "../lib/api";
5
import { EmptyState } from "./EmptyState";
6
import { PageHeader } from "./PageHeader";
8
export function AskAiPage() {
9
const [question, setQuestion] = useState("");
10
const [answer, setAnswer] = useState<{ answer: string; citations: AiCitation[]; disabled?: boolean } | null>(null);
11
const [loading, setLoading] = useState(false);
13
async function submit(event: React.FormEvent) {
14
event.preventDefault();
15
if (!question.trim()) return;
17
setLoading(true);
18
try {
19
const response = await api.post<{ answer: string; citations: AiCitation[]; disabled?: boolean }>("/api/ai/answers", {
20
question
21
});
22
setAnswer(response);
23
} catch (error) {
24
setAnswer({
25
answer: error instanceof Error ? error.message : "Could not answer this question.",
26
citations: []
27
});
28
} finally {
29
setLoading(false);
30
}
31
}
33
return (
34
<div className="stack-page">
35
<PageHeader
36
eyebrow="Ask AI"
37
title="Ask Ledger"
38
description="Ledger answers using only pages you are allowed to access, and every answer includes citations back to source documents."
39
/>
41
<section className="panel">
42
<form className="stack" onSubmit={submit}>
43
<label className="field">
44
Question
45
<textarea
46
value={question}
47
onChange={(event) => setQuestion(event.target.value)}
48
placeholder="Ask a product, process, or onboarding question"
49
/>
50
</label>
51
<div className="panel__footer">
52
<button type="submit" disabled={loading || !question.trim()}>
53
{loading ? "Answering..." : "Ask AI"}
54
</button>
55
<p className="muted">If no permitted content is relevant, Ledger will say so instead of guessing.</p>
56
</div>
57
</form>
58
</section>
60
{answer ? (
61
<section className="panel">
62
<div className="panel__header">
63
<div>
64
<p className="eyebrow">Answer</p>
65
<h3>Response</h3>
66
</div>
67
</div>
68
{answer.disabled ? (
69
<EmptyState
70
title="AI provider is not configured"
71
description="An admin can enable AI in Admin > AI Settings. Until then, Ask AI is intentionally unavailable."
72
action={<Link to="/admin/ai" className="button-secondary">Open AI Settings</Link>}
73
/>
74
) : (
75
<>
76
<p>{answer.answer}</p>
77
<div className="citation-list">
78
{answer.citations.length === 0 ? (
79
<p className="muted">No citations were returned for this answer.</p>
80
) : (
81
answer.citations.map((citation) => (
82
<Link key={citation.slug} to={`/page/${citation.slug}`} className="citation-chip">
83
{citation.title}
84
</Link>
85
))
86
)}
87
</div>
88
</>
89
)}
90
</section>
91
) : null}
92
</div>
93
);
94
}