CI / ruff · pytest (pull_request) Successful in 2m24s
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>
177 lines
5.0 KiB
TypeScript
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>
|
|
);
|
|
}
|