feat(frontend): English UI + 'Datamart Builder with Human In The Loop' tagline
The interface is now fully English regardless of the chat language (session titles stay in whatever language the user typed). Changed the right-rail subtitle to 'Datamart Builder with Human In The Loop'. Translated all chrome: new-question/settings dialogs, workflow phase strip (F1-F8), session rail, steer input, widgets (select/multiselect/freetext/fallback/reserved controls) and viewers (SQL, results, schema linking). Test matchers updated in lockstep. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,6 @@ test("reserved control responds with control field", async () => {
|
||||
onRespond={onRespond}
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByRole("button", { name: /indietro/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /back/i }));
|
||||
expect(onRespond).toHaveBeenCalledWith({ id: "u1", control: "back" });
|
||||
});
|
||||
|
||||
@@ -5,10 +5,10 @@ export function FallbackWidget({ descriptor, onRespond }: WidgetProps) {
|
||||
const [text, setText] = useState("");
|
||||
return (
|
||||
<div className="border rounded p-3 space-y-2">
|
||||
<p className="text-sm text-amber-600">Widget non supportato (kind: {descriptor.widget}) — rispondi manualmente</p>
|
||||
<p className="text-sm text-amber-600">Unsupported widget (kind: {descriptor.widget}) — respond manually</p>
|
||||
<pre className="text-xs overflow-auto max-h-48 bg-muted p-2">{JSON.stringify(descriptor, null, 2)}</pre>
|
||||
<textarea className="w-full border rounded p-1" value={text} onChange={(e) => setText(e.target.value)} />
|
||||
<button className="border rounded px-2 py-1" onClick={() => onRespond({ id: descriptor.id, control: "freetext", text })}>Invia</button>
|
||||
<button className="border rounded px-2 py-1" onClick={() => onRespond({ id: descriptor.id, control: "freetext", text })}>Send</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ test("submits typed text", async () => {
|
||||
const onRespond = vi.fn();
|
||||
render(<FreetextWidget descriptor={{ id: "u1", widget: "freetext" }} onRespond={onRespond} />);
|
||||
await userEvent.type(screen.getByRole("textbox"), "ciao");
|
||||
await userEvent.click(screen.getByRole("button", { name: /invia/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /send/i }));
|
||||
expect(onRespond).toHaveBeenCalledWith({ id: "u1", kind: "freetext", text: "ciao" });
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ export function FreetextWidget({ descriptor, onRespond }: WidgetProps) {
|
||||
<div className="space-y-2">
|
||||
{descriptor.title && <p className="font-medium">{descriptor.title}</p>}
|
||||
<textarea className="w-full border rounded p-2" value={text} onChange={(e) => setText(e.target.value)} />
|
||||
<button className="border rounded px-3 py-1" onClick={() => onRespond({ id: descriptor.id, kind: "freetext", text })}>Invia</button>
|
||||
<button className="border rounded px-3 py-1" onClick={() => onRespond({ id: descriptor.id, kind: "freetext", text })}>Send</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ test("confirms the checked ids including initially selected", async () => {
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByRole("checkbox", { name: /t2/ }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /conferma/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /confirm/i }));
|
||||
expect(onRespond).toHaveBeenCalledWith({ id: "u1", kind: "multiselect", choices: ["t1", "t2"] });
|
||||
});
|
||||
|
||||
@@ -37,8 +37,8 @@ test("select-all checks all options", async () => {
|
||||
onRespond={onRespond}
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByRole("button", { name: /seleziona tutti/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /conferma/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /select all/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /confirm/i }));
|
||||
expect(onRespond).toHaveBeenCalledWith({ id: "u1", kind: "multiselect", choices: ["a", "b"] });
|
||||
});
|
||||
|
||||
@@ -57,8 +57,8 @@ test("deselect-all unchecks all options", async () => {
|
||||
onRespond={onRespond}
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByRole("button", { name: /deseleziona tutti/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /conferma/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /deselect all/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /confirm/i }));
|
||||
expect(onRespond).toHaveBeenCalledWith({ id: "u1", kind: "multiselect", choices: [] });
|
||||
});
|
||||
|
||||
@@ -75,7 +75,7 @@ test("confirm is disabled when nothing checked and allow_empty is false", async
|
||||
onRespond={onRespond}
|
||||
/>
|
||||
);
|
||||
const btn = screen.getByRole("button", { name: /conferma/i });
|
||||
const btn = screen.getByRole("button", { name: /confirm/i });
|
||||
expect(btn).toBeDisabled();
|
||||
await userEvent.click(btn);
|
||||
expect(onRespond).not.toHaveBeenCalled();
|
||||
@@ -97,9 +97,9 @@ test("deselect-all with allow_empty false disables Conferma (empty confirm impos
|
||||
onRespond={onRespond}
|
||||
/>
|
||||
);
|
||||
const btn = screen.getByRole("button", { name: /conferma/i });
|
||||
const btn = screen.getByRole("button", { name: /confirm/i });
|
||||
expect(btn).not.toBeDisabled();
|
||||
await userEvent.click(screen.getByRole("button", { name: /deseleziona tutti/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /deselect all/i }));
|
||||
expect(btn).toBeDisabled();
|
||||
await userEvent.click(btn);
|
||||
expect(onRespond).not.toHaveBeenCalled();
|
||||
@@ -118,7 +118,7 @@ test("confirm is enabled when allow_empty is false but something is checked", as
|
||||
onRespond={onRespond}
|
||||
/>
|
||||
);
|
||||
const btn = screen.getByRole("button", { name: /conferma/i });
|
||||
const btn = screen.getByRole("button", { name: /confirm/i });
|
||||
expect(btn).not.toBeDisabled();
|
||||
await userEvent.click(btn);
|
||||
expect(onRespond).toHaveBeenCalledWith({ id: "u1", kind: "multiselect", choices: ["x"] });
|
||||
|
||||
@@ -32,7 +32,7 @@ export function MultiselectWidget({ descriptor, onRespond }: WidgetProps) {
|
||||
{descriptor.title && <p className="font-medium">{descriptor.title}</p>}
|
||||
<div className="flex gap-2">
|
||||
<button className="text-sm border rounded px-2 py-1" onClick={allChecked ? deselectAll : selectAll}>
|
||||
{allChecked ? "Deseleziona tutti" : "Seleziona tutti"}
|
||||
{allChecked ? "Deselect all" : "Select all"}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -52,7 +52,7 @@ export function MultiselectWidget({ descriptor, onRespond }: WidgetProps) {
|
||||
disabled={isDisabled}
|
||||
onClick={() => onRespond({ id: descriptor.id, kind: "multiselect", choices: Array.from(checked) })}
|
||||
>
|
||||
Conferma
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const LABELS: Record<string, string> = { back: "Torna indietro", exit: "Esci", other: "Altro — specifica" };
|
||||
const LABELS: Record<string, string> = { back: "Go back", exit: "Exit", other: "Other — specify" };
|
||||
export function ReservedControls({ reserved, onControl }: { reserved?: string[]; onControl: (c: string) => void }) {
|
||||
if (!reserved?.length) return null;
|
||||
return <div className="flex gap-2 pt-2">{reserved.map((c) => (
|
||||
|
||||
@@ -12,6 +12,6 @@ test("picking an option responds with its id", async () => {
|
||||
test("a reserved control responds with control, not a choice", async () => {
|
||||
const onRespond = vi.fn();
|
||||
render(<SelectWidget descriptor={{ id: "u1", widget: "select", options: [{ id: "a", label: "A" }], reserved: ["back"] }} onRespond={onRespond} />);
|
||||
await userEvent.click(screen.getByRole("button", { name: /indietro/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /back/i }));
|
||||
expect(onRespond).toHaveBeenCalledWith({ id: "u1", control: "back" });
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ test("reject opens a freetext and combines the reason", async () => {
|
||||
);
|
||||
await userEvent.click(screen.getByRole("button", { name: /Rifiuta/ }));
|
||||
await userEvent.type(screen.getByRole("textbox"), "join sbagliata");
|
||||
await userEvent.click(screen.getByRole("button", { name: /invia/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: /send/i }));
|
||||
expect(onRespond).toHaveBeenCalledWith({
|
||||
id: "u1",
|
||||
kind: "artifact-gate",
|
||||
|
||||
@@ -11,5 +11,5 @@ test("resolve returns the registered renderer", () => {
|
||||
test("resolve falls back for unknown kind and shows the JSON", () => {
|
||||
const Comp = resolve("totally-unknown");
|
||||
render(<Comp descriptor={{ id: "u1", widget: "totally-unknown", title: "X" } as any} onRespond={() => {}} />);
|
||||
expect(screen.getByText(/Widget non supportato/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Unsupported widget/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user