"use client" // The search form: one question, cited answers. The disclosure the EU AI Act Article 50 asks for // rides with the results, and every answer names its source and chunk — a person checks a passage // against the file, never a model's word. import { AiDisclosureBadge, type ProjectSlugs, useTransport } from "@conusai-kit/ui" import { type FormEvent, type ReactElement, useId, useState } from "react" import type { SearchResult } from "../contract/index.js" import type { SearchFormLabels } from "./labels.js" type State = | { kind: "idle" } | { kind: "busy" } | { kind: "done"; results: SearchResult[] } | { kind: "failed"; detail: string } export function SearchForm({ slugs, collectionName, labels, }: { slugs: ProjectSlugs collectionName: string labels: SearchFormLabels }): ReactElement { const { call, describe } = useTransport() const inputId = useId() const [state, setState] = useState({ kind: "idle" }) const search = async (event: FormEvent): Promise => { event.preventDefault() const query = String(new FormData(event.currentTarget).get("query") ?? "").trim() if (query === "") return setState({ kind: "busy" }) try { const answer = (await call("knowledge.search", { ...slugs, collectionName, query })) as { results: SearchResult[] } setState({ kind: "done", results: answer.results }) } catch (error) { setState({ kind: "failed", detail: describe(error) }) } } return (

{labels.heading}

void search(event)}>
{state.kind === "failed" ?

{labels.failed.replace("{detail}", state.detail)}

: null} {state.kind === "done" ? ( state.results.length === 0 ? (

{labels.empty}

) : (
    {state.results.map((result, at) => (
  1. {result.snippet}

    {labels.source}: {result.filename} · {labels.chunk.replace("{chunk}", String(result.chunk))}

  2. ))}
) ) : null}
) }