Files
PTSRaiAgent/web/src/components/Icon.tsx
T
ElebobaandClaude Opus 5.5 e20da1f21c
CI / ruff · pytest (pull_request) Successful in 2m24s
feat(web): voice input — record, check the recognised text, send
Microphone button in the chat composer and in free-text steps. The browser
records 16 kHz mono WAV (no server-side ffmpeg), /me/{token}/stt transcribes,
the text lands in the field for the patient to check and edit — never sent
automatically, so safety and scrub() apply on send. Hidden when the server has
voice input off or the browser lacks a microphone API. nginx: 4 MB body limit.

Refs #117

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:45:36 +03:00

177 lines
5.0 KiB
TypeScript

// Line icon set (24px grid, 1.8 stroke). Decorative by default: pass `label` to expose to screen readers.
const paths = {
home: <path d="M4 10.5L12 4l8 6.5V19a1 1 0 0 1-1 1h-4.5v-6h-5v6H5a1 1 0 0 1-1-1v-8.5z" />,
pill: (
<>
<rect x="3" y="8.5" width="18" height="7" rx="3.5" transform="rotate(-45 12 12)" />
<path d="M9.5 9.5l5 5" />
</>
),
doctor: (
<>
<circle cx="12" cy="7.5" r="3.5" />
<path d="M5 20.5c0-3.9 3.1-6.5 7-6.5s7 2.6 7 6.5" />
<path d="M12 15.5v3m-1.5-1.5h3" />
</>
),
user: (
<>
<circle cx="12" cy="8" r="4" />
<path d="M4.5 20.5c.8-3.7 3.8-6 7.5-6s6.7 2.3 7.5 6" />
</>
),
bell: (
<>
<path d="M6 16.5V11a6 6 0 0 1 12 0v5.5l1.5 2h-15L6 16.5z" />
<path d="M10 20.5a2 2 0 0 0 4 0" />
</>
),
chevronRight: <path d="M9.5 6l6 6-6 6" />,
chevronDown: <path d="M6 9.5l6 6 6-6" />,
arrowLeft: <path d="M19 12H5m6-6l-6 6 6 6" />,
close: <path d="M6 6l12 12M18 6L6 18" />,
check: <path d="M5 12.5l4.5 4.5L19 7.5" />,
mic: (
<>
<rect x="9" y="3" width="6" height="11" rx="3" />
<path d="M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5V21" />
</>
),
x: <path d="M7 7l10 10M17 7L7 17" />,
info: (
<>
<circle cx="12" cy="12" r="9" />
<path d="M12 11v5m0-8.5v.5" />
</>
),
alert: (
<>
<path d="M12 3.5l9.5 16.5h-19L12 3.5z" />
<path d="M12 10v4.5m0 2.5v.5" />
</>
),
phone: (
<path d="M6.6 3.8h2.6l1.4 4-2 1.4a12 12 0 0 0 5.9 5.9l1.4-2 4 1.4v2.6c0 1-.8 1.8-1.8 1.8A15.8 15.8 0 0 1 4.8 5.6c0-1 .8-1.8 1.8-1.8z" />
),
headset: (
<>
<path d="M4 14v-2a8 8 0 0 1 16 0v2" />
<rect x="3" y="13.5" width="4" height="6" rx="1.5" />
<rect x="17" y="13.5" width="4" height="6" rx="1.5" />
</>
),
calendar: (
<>
<rect x="3.5" y="5" width="17" height="15.5" rx="2" />
<path d="M3.5 9.5h17M8 3v4m8-4v4M8 13h1m3 0h1m3 0h1M8 16.5h1m3 0h1" />
</>
),
clock: (
<>
<circle cx="12" cy="12" r="8.5" />
<path d="M12 7.5V12l3 2" />
</>
),
pin: (
<>
<path d="M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11z" />
<circle cx="12" cy="10" r="2.5" />
</>
),
smile: (
<>
<circle cx="12" cy="12" r="8.5" />
<path d="M8.5 14c.9 1.3 2.1 2 3.5 2s2.6-.7 3.5-2M9 9.8v.4m6-.4v.4" />
</>
),
doc: (
<>
<path d="M6 3.5h8l4 4V20a.5.5 0 0 1-.5.5h-11A.5.5 0 0 1 6 20V3.5z" />
<path d="M14 3.5V8h4M9 12h6m-6 3.5h6" />
</>
),
docPlus: (
<>
<path d="M6 3.5h8l4 4V20a.5.5 0 0 1-.5.5h-11A.5.5 0 0 1 6 20V3.5z" />
<path d="M14 3.5V8h4M12 11v6m-3-3h6" />
</>
),
shield: (
<>
<path d="M12 3l7.5 3v5.5c0 4.6-3.2 8.2-7.5 9.5-4.3-1.3-7.5-4.9-7.5-9.5V6L12 3z" />
<path d="M9 12l2 2 4-4" />
</>
),
lock: (
<>
<rect x="5" y="10.5" width="14" height="10" rx="2" />
<path d="M8.5 10.5V7.5a3.5 3.5 0 0 1 7 0v3" />
</>
),
edit: <path d="M5 19l1-4L15.5 5.5a2 2 0 0 1 3 3L9 18l-4 1zM14 7l3 3" />,
trash: <path d="M5 7h14M10 7V4.5h4V7m-7 0l1 12.5h8L17 7M10.5 11v5m3-5v5" />,
logout: <path d="M14 4h4.5A1.5 1.5 0 0 1 20 5.5v13a1.5 1.5 0 0 1-1.5 1.5H14M10 8l-4 4 4 4M6 12h10" />,
send: <path d="M4 12l16-8-6 16-2.5-6.5L4 12z" />,
heart: <path d="M12 19.5s-7.5-4.4-7.5-10a4.2 4.2 0 0 1 7.5-2.6 4.2 4.2 0 0 1 7.5 2.6c0 5.6-7.5 10-7.5 10z" />,
lungs: (
<>
<path d="M12 4v8m0 0c-1 1.2-2.3 2-3.5 2M12 12c1 1.2 2.3 2 3.5 2" />
<path d="M8.5 7.5C6 8 4 11.5 4 16c0 2 1 3.5 3 3.5s3-1.5 3-4V9M15.5 7.5C18 8 20 11.5 20 16c0 2-1 3.5-3 3.5s-3-1.5-3-4V9" />
</>
),
question: (
<>
<circle cx="12" cy="12" r="8.5" />
<path d="M9.6 9.5a2.4 2.4 0 0 1 4.7.7c0 1.6-2.3 2-2.3 3.6m0 2.7v.3" />
</>
),
message: <path d="M4 5.5h16v11H9.5L5 20v-3.5H4z" />,
plus: <path d="M12 5v14M5 12h14" />,
clipboard: (
<>
<rect x="5" y="5" width="14" height="16" rx="2" />
<path d="M9 3.5h6v3H9zM8.5 11h7M8.5 14.5h7M8.5 18h4" />
</>
),
book: (
<>
<path d="M5 4.5h10.5A2.5 2.5 0 0 1 18 7v13H7.5A2.5 2.5 0 0 1 5 17.5z" />
<path d="M5 17.5A2.5 2.5 0 0 1 7.5 15H18M9 8.5h5" />
</>
),
pause: <path d="M9 6v12M15 6v12" />,
} as const;
export type IconName = keyof typeof paths;
export function Icon({ name, size = 22, label }: { name: IconName; size?: number; label?: string }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.8}
strokeLinecap="round"
strokeLinejoin="round"
role={label ? "img" : undefined}
aria-label={label}
aria-hidden={label ? undefined : true}
focusable="false"
>
{paths[name]}
</svg>
);
}
export function LogoMark({ size = 32 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 32 32" aria-hidden="true" focusable="false">
<rect width="32" height="32" rx="9" fill="var(--c-primary)" />
<path d="M16 8v16M8 16h16" stroke="#fff" strokeWidth="2" strokeLinecap="round" />
</svg>
);
}