NobGit
public anord read

Ledger

Why work hard when you can work easier?

Languages

Repository composition by tracked source files.

TypeScript
TypeScript 86% CSS 10% SQL 3% Shell 1% HTML 0%
Create file Wiki Documentation
Clone
https://nobgit.com/orgs/anord/ledger.git
ssh://[email protected]:2222/orgs/anord/ledger.git
AskAiPage.tsx
import { useState } from "react";
import { Link } from "react-router-dom";
import type { AiCitation } from "@ledger/shared";
import { api } from "../lib/api";
import { EmptyState } from "./EmptyState";
import { PageHeader } from "./PageHeader";

export function AskAiPage() {
  const [question, setQuestion] = useState("");
  const [answer, setAnswer] = useState<{ answer: string; citations: AiCitation[]; disabled?: boolean } | null>(null);
  const [loading, setLoading] = useState(false);

  async function submit(event: React.FormEvent) {
    event.preventDefault();
    if (!question.trim()) return;

    setLoading(true);
    try {
      const response = await api.post<{ answer: string; citations: AiCitation[]; disabled?: boolean }>("/api/ai/answers", {
        question
      });
      setAnswer(response);
    } catch (error) {
      setAnswer({
        answer: error instanceof Error ? error.message : "Could not answer this question.",
        citations: []
      });
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="stack-page">
      <PageHeader
        eyebrow="Ask AI"
        title="Ask Ledger"
        description="Ledger answers using only pages you are allowed to access, and every answer includes citations back to source documents."
      />

      <section className="panel">
        <form className="stack" onSubmit={submit}>
          <label className="field">
            Question
            <textarea
              value={question}
              onChange={(event) => setQuestion(event.target.value)}
              placeholder="Ask a product, process, or onboarding question"
            />
          </label>
          <div className="panel__footer">
            <button type="submit" disabled={loading || !question.trim()}>
              {loading ? "Answering..." : "Ask AI"}
            </button>
            <p className="muted">If no permitted content is relevant, Ledger will say so instead of guessing.</p>
          </div>
        </form>
      </section>

      {answer ? (
        <section className="panel">
          <div className="panel__header">
            <div>
              <p className="eyebrow">Answer</p>
              <h3>Response</h3>
            </div>
          </div>
          {answer.disabled ? (
            <EmptyState
              title="AI provider is not configured"
              description="An admin can enable AI in Admin > AI Settings. Until then, Ask AI is intentionally unavailable."
              action={<Link to="/admin/ai" className="button-secondary">Open AI Settings</Link>}
            />
          ) : (
            <>
              <p>{answer.answer}</p>
              <div className="citation-list">
                {answer.citations.length === 0 ? (
                  <p className="muted">No citations were returned for this answer.</p>
                ) : (
                  answer.citations.map((citation) => (
                    <Link key={citation.slug} to={`/page/${citation.slug}`} className="citation-chip">
                      {citation.title}
                    </Link>
                  ))
                )}
              </div>
            </>
          )}
        </section>
      ) : null}
    </div>
  );
}