ki integration

This commit is contained in:
2026-08-15 13:43:52 +02:00
parent 82d98bd8cf
commit 9ecb292c26
78 changed files with 12055 additions and 55 deletions

101
client/src/lib/session.tsx Normal file
View File

@@ -0,0 +1,101 @@
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react';
import {
abmelden as abmeldenApi,
aktuellesKonto,
brandSicherstellen,
eigeneBrand,
type Brand,
type Konto,
} from './auth';
import { ordnerListe, type Ordner } from './folders';
type Session = {
/** true, solange noch nicht feststeht, ob jemand angemeldet ist. */
laedt: boolean;
konto: Konto | null;
brand: Brand | null;
/**
* Der aktive Ordner bestimmt, welches Wissen in den Prompt wandert. Er ist
* deshalb App-Zustand und kein Bildschirm-lokaler Wert (app-aufbau.md §2.2)
* sonst generiert der Nutzer im falschen Scope und versteht das Ergebnis nicht.
*/
aktiverOrdner: Ordner | null;
ordner: Ordner[];
setzeAktivenOrdner: (o: Ordner | null) => void;
ordnerNeuLaden: () => Promise<void>;
neuLaden: () => Promise<void>;
ausloggen: () => Promise<void>;
};
const Ctx = createContext<Session | null>(null);
export function SessionProvider({ children }: { children: ReactNode }) {
const [laedt, setLaedt] = useState(true);
const [konto, setKonto] = useState<Konto | null>(null);
const [brand, setBrand] = useState<Brand | null>(null);
const [ordner, setOrdner] = useState<Ordner[]>([]);
const [aktiverOrdner, setzeAktivenOrdner] = useState<Ordner | null>(null);
const ordnerFuer = useCallback(async (b: Brand | null) => {
if (!b) {
setOrdner([]);
setzeAktivenOrdner(null);
return;
}
const liste = await ordnerListe(b.$id);
setOrdner(liste);
setzeAktivenOrdner((bisher) => {
if (bisher) return liste.find((o) => o.$id === bisher.$id) ?? liste[0] ?? null;
return liste.find((o) => o.ist_default) ?? liste[0] ?? null;
});
}, []);
const neuLaden = useCallback(async () => {
const k = await aktuellesKonto();
setKonto(k);
if (!k) {
setBrand(null);
await ordnerFuer(null);
setLaedt(false);
return;
}
// Konto ohne Brand kann entstehen, wenn die Registrierung mittendrin
// abgebrochen ist hier wird das stillschweigend nachgeholt.
let b = await eigeneBrand();
if (!b) b = await brandSicherstellen(k.name || k.email);
setBrand(b);
await ordnerFuer(b);
setLaedt(false);
}, [ordnerFuer]);
const ordnerNeuLaden = useCallback(() => ordnerFuer(brand), [brand, ordnerFuer]);
const ausloggen = useCallback(async () => {
await abmeldenApi();
setKonto(null);
setBrand(null);
setOrdner([]);
setzeAktivenOrdner(null);
}, []);
useEffect(() => {
void neuLaden();
}, [neuLaden]);
return (
<Ctx.Provider
value={{
laedt, konto, brand, ordner, aktiverOrdner,
setzeAktivenOrdner, ordnerNeuLaden, neuLaden, ausloggen,
}}>
{children}
</Ctx.Provider>
);
}
export function useSession(): Session {
const v = useContext(Ctx);
if (!v) throw new Error('useSession muss innerhalb von <SessionProvider> stehen.');
return v;
}