7b6619133f
Dokumentation: - MASTER-PROMPT.md uppdaterad till v2.0 (SaaS/AI-wrapper, roller, multi-tenant, onboarding, beta-fokus). - Nya moduler: kommunikationsmodell (tal in/text ut, Push-to-Talk, redigering före skick), Live Share (behörighetsstyrd realtidslänk, versionsmärkta exporter), verifierade checklistor (bevis och kontext per kontrolltyp). Implementation i MVP:t: - Push-to-Talk-röstinmatning via webbläsarens taligenkänning (sv-SE): lyssnar endast efter aktivt tryck, röd indikator med realtids- transkript, texten hamnar i redigerbart fält och skickas aldrig automatiskt. Döljs i webbläsare utan talstöd. - Verifierade checklistor: varje kontroll i metodikerna har minimikrav (foto/mätvärde/kommentar); fotokontroller verifieras med bild och mätkontroller kan inte markeras verifierade utan värde. - Versionsmärkt JSON-export ur händelseloggen; exporten loggas själv som händelse med format, version och användare. Verifierat: 12 vitest-tester gröna, produktionsbygge ok, utökat Playwright-röktest genom hela flödet inkl. fotokravet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
// Tal-till-text enligt kommunikationsmodellen: rösten är ett inmatningssätt,
|
|
// inte ett separat gränssnitt. All logik bygger på text — transkriberingen
|
|
// hamnar i ett redigerbart fält och ingenting skickas utan bekräftelse.
|
|
//
|
|
// MVP:t använder webbläsarens SpeechRecognition (sv-SE). I produktions-
|
|
// versionen ersätts motorn av leverantörens Voice-to-Text-API bakom samma
|
|
// gränssnitt.
|
|
|
|
interface IgenkanningsResultat {
|
|
transkript: string;
|
|
slutgiltigt: boolean;
|
|
}
|
|
|
|
interface Igenkanning {
|
|
start(): void;
|
|
stop(): void;
|
|
abort(): void;
|
|
}
|
|
|
|
type SpeechRecognitionCtor = new () => {
|
|
lang: string;
|
|
continuous: boolean;
|
|
interimResults: boolean;
|
|
onresult: ((event: { resultIndex: number; results: ArrayLike<{ 0: { transcript: string }; isFinal: boolean }> }) => void) | null;
|
|
onend: (() => void) | null;
|
|
onerror: ((event: { error: string }) => void) | null;
|
|
start(): void;
|
|
stop(): void;
|
|
abort(): void;
|
|
};
|
|
|
|
function ctor(): SpeechRecognitionCtor | undefined {
|
|
if (typeof window === "undefined") return undefined;
|
|
const w = window as unknown as {
|
|
SpeechRecognition?: SpeechRecognitionCtor;
|
|
webkitSpeechRecognition?: SpeechRecognitionCtor;
|
|
};
|
|
return w.SpeechRecognition ?? w.webkitSpeechRecognition;
|
|
}
|
|
|
|
export function stodRost(): boolean {
|
|
return ctor() !== undefined;
|
|
}
|
|
|
|
export function startaIgenkanning(
|
|
paResultat: (r: IgenkanningsResultat) => void,
|
|
paSlut: () => void,
|
|
): Igenkanning | undefined {
|
|
const Ctor = ctor();
|
|
if (!Ctor) return undefined;
|
|
const igenkanning = new Ctor();
|
|
igenkanning.lang = "sv-SE";
|
|
igenkanning.continuous = true;
|
|
igenkanning.interimResults = true;
|
|
igenkanning.onresult = (event) => {
|
|
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
const resultat = event.results[i];
|
|
paResultat({ transkript: resultat[0].transcript, slutgiltigt: resultat.isFinal });
|
|
}
|
|
};
|
|
igenkanning.onend = paSlut;
|
|
igenkanning.onerror = paSlut;
|
|
igenkanning.start();
|
|
return igenkanning;
|
|
}
|