Skip to content
This repository was archived by the owner on Apr 24, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions app/pictogram/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,16 @@ function IconPlay() {
);
}

function PictogramImage({ keyword, size = 56 }: { keyword: string; size?: number }) {
const { url, loading } = useArasaacImage(keyword);
function PictogramImage({
keyword,
preferredId,
size = 56,
}: {
keyword: string;
preferredId?: number;
size?: number;
}) {
const { url, loading } = useArasaacImage(keyword, { preferredId });

if (loading) {
return (
Expand Down Expand Up @@ -111,7 +119,7 @@ function PictogramTile({
aria-label={node.label}
>
<div className="relative flex h-21 w-full items-center justify-center rounded-[18px] bg-[radial-gradient(circle_at_30%_20%,#e4f4ce,#d8efe1)]">
<PictogramImage keyword={node.arasaacKeyword} size={64} />
<PictogramImage keyword={node.arasaacKeyword} preferredId={node.arasaacId} size={64} />
</div>
<div className="mt-2 text-center text-[12px] font-semibold text-(--green-800)">{node.label}</div>
</button>
Expand Down Expand Up @@ -154,7 +162,7 @@ function TrailChips({
isLarge ? "gap-2 px-4 py-2.5" : "gap-1.5 px-3 py-2"
}`}
>
<PictogramImage keyword={node.arasaacKeyword} size={isLarge ? 40 : 28} />
<PictogramImage keyword={node.arasaacKeyword} preferredId={node.arasaacId} size={isLarge ? 40 : 28} />
<span className={isLarge ? "text-[13px] font-semibold text-(--green-800)" : "text-[12px] font-semibold text-(--green-800)"}>
{node.label}
</span>
Expand Down Expand Up @@ -658,7 +666,7 @@ export default function PictogramPage() {
)}

<div className="relative z-10 mt-auto border-t border-(--line-soft) bg-white/90 p-3 backdrop-blur-sm">
{trail.length > 0 && !isLeaf ? <TrailChips trail={trail} /> : trail.length === 0 ? <div className="h-[48px]" /> : null}
{trail.length > 0 && !isLeaf ? <TrailChips trail={trail} /> : trail.length === 0 ? <div className="h-12" /> : null}
<button
onClick={() => setShowOutput(true)}
disabled={!canGenerate}
Expand Down
21 changes: 11 additions & 10 deletions lib/pictogramTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type PictogramNode = {
id: string;
label: string;
arasaacKeyword: string;
arasaacId?: number; // Optional explicit ARASAAC pictogram id override for this node
llmContext?: string; // Extra descriptive context to help the LLM generate a full, accurate sentence
children?: PictogramNode[];
};
Expand All @@ -20,8 +21,9 @@ const whoIsThis: PictogramNode[] = [
// ─── SEVERITY (routes to Who) ────────────────────────────────────────────────

const severity: PictogramNode[] = [
{ id: "sev-little", label: "A little", arasaacKeyword: "little", llmContext: "The symptom severity is mild.", children: whoIsThis },
{ id: "sev-lot", label: "A lot", arasaacKeyword: "very much", llmContext: "The symptom severity is severe or intense.", children: whoIsThis },
{ id: "sev-little", label: "A little", arasaacKeyword: "little", arasaacId: 4716, llmContext: "The symptom severity is mild.", children: whoIsThis },
{ id: "sev-medium", label: "Medium", arasaacKeyword: "medium", arasaacId: 4693, llmContext: "The symptom severity is moderate.", children: whoIsThis },
{ id: "sev-lot", label: "A lot", arasaacKeyword: "very much", arasaacId: 4658, llmContext: "The symptom severity is severe or intense.", children: whoIsThis },
{ id: "sev-since", label: "Since yesterday",arasaacKeyword: "yesterday", llmContext: "The symptom has been ongoing since yesterday.", children: whoIsThis },
{ id: "sev-days", label: "Many days", arasaacKeyword: "calendar", llmContext: "The symptom has been ongoing for multiple days.", children: whoIsThis },
];
Expand Down Expand Up @@ -71,7 +73,7 @@ const chestSymptoms: PictogramNode[] = [
const stomachSymptoms: PictogramNode[] = [
symptom("stom-pain", "Stomach pain", "stomach", "The patient is experiencing stomach pain."),
symptom("stom-nausea", "Nausea", "nausea", "The patient is feeling nauseous."),
symptom("stom-vomit", "Vomiting", "vomiting", "The patient has been vomiting."),
{ id: "stom-vomit", label: "Vomiting", arasaacKeyword: "vomiting", arasaacId: 7303, llmContext: "The patient has been vomiting.", children: severity },
symptom("stom-diarrhea", "Diarrhea", "diarrhea", "The patient is experiencing diarrhea."),
symptom("stom-noeat", "Cannot eat", "eat", "The patient is unable to eat."),
];
Expand All @@ -90,7 +92,6 @@ const appointmentReasons: PictogramNode[] = [

const pharmacyNeeds: PictogramNode[] = [
logisticWithWho("pharm-getmed", "Pick up medicine", "medicine", "The user needs to pick up a new prescription medicine."),
logisticWithWho("pharm-refill", "Refill medicine", "medicine", "The user needs to refill an existing prescription."),
logisticWithWho("pharm-ask", "Ask about medicine", "pharmacist", "The user has a question for the pharmacist about a medication."),
logisticWithWho("pharm-allergy", "Medicine allergy", "allergy", "The user is reporting a medication allergy."),
];
Expand All @@ -110,7 +111,7 @@ const hospitalReasons: PictogramNode[] = [
const emergencyProblems: PictogramNode[] = [
directAction("emg-breathe", "Cannot breathe", "breathe", "CRITICAL MEDICAL EMERGENCY: The person is unable to breathe."),
directAction("emg-bleeding", "Bleeding badly", "bleeding", "CRITICAL MEDICAL EMERGENCY: The person is experiencing severe bleeding."),
directAction("emg-conscious", "Unconscious", "unconscious", "CRITICAL MEDICAL EMERGENCY: The person has lost consciousness."),
{ id: "emg-conscious", label: "Unconscious", arasaacKeyword: "unconscious", arasaacId: 38054, llmContext: "CRITICAL MEDICAL EMERGENCY: The person has lost consciousness." },
directAction("emg-chest", "Chest pain", "chest pain", "CRITICAL MEDICAL EMERGENCY: The person is having severe chest pain, potentially a heart attack."),
];

Expand All @@ -127,7 +128,7 @@ const emergencyChildren: PictogramNode[] = [
// ─── 🍞 FOOD ──────────────────────────────────────────────────────────────────

const groceryShopping: PictogramNode[] = [
directAction("groc-where", "Where is this?", "where", "The user is at a shop and needs help finding a specific item."),
{ id: "groc-where", label: "Where is this?", arasaacKeyword: "where", arasaacId: 32872, llmContext: "The user is at a shop and needs help finding a specific item." },
directAction("groc-price", "How much?", "price", "The user wants to know the price of an item."),
directAction("groc-halal", "Is this halal?", "question", "The user is asking if a specific food item is certified Halal."),
];
Expand Down Expand Up @@ -163,7 +164,7 @@ const homeChildren: PictogramNode[] = [
// ─── 🚌 TRANSPORT (All Direct Actions) ────────────────────────────────────────

const howToRideTransit: PictogramNode[] = [
directAction("ride-where", "Where does this go?", "where", "The user is asking for the destination of the current bus or train."),
{ id: "ride-where", label: "Where does this go?", arasaacKeyword: "where", arasaacId: 32872, llmContext: "The user is asking for the destination of the current bus or train." },
directAction("ride-pay", "How do I pay?", "pay", "The user is asking how to pay the fare for public transit."),
];

Expand Down Expand Up @@ -192,9 +193,9 @@ const workChildren: PictogramNode[] = [
// ─── 🏥 HEALTH (top-level children) ──────────────────────────────────────────

const healthChildren: PictogramNode[] = [
{ id: "health-hospital", label: "Hospital / Clinic", arasaacKeyword: "hospital", llmContext: "The user is at or needs to go to a hospital or clinic.", children: hospitalReasons },
{ id: "health-hospital", label: "Hospital / Clinic", arasaacKeyword: "hospital", arasaacId: 3116, llmContext: "The user is at or needs to go to a hospital or clinic.", children: hospitalReasons },
{ id: "health-pharmacy", label: "Pharmacy", arasaacKeyword: "pharmacy", llmContext: "The user is at or needs to go to a pharmacy.", children: pharmacyNeeds },
{ id: "health-body", label: "Body pain", arasaacKeyword: "pain", llmContext: "The user is experiencing physical pain or discomfort.", children: bodyParts },
{ id: "health-body", label: "Body pain", arasaacKeyword: "pain", arasaacId: 38423, llmContext: "The user is experiencing physical pain or discomfort.", children: bodyParts },
{ id: "health-feelings", label: "Feelings", arasaacKeyword: "feelings", llmContext: "The user wants to express emotional feelings.", children: feelings },
];

Expand All @@ -205,6 +206,6 @@ export const pictogramTree: PictogramNode[] = [
{ id: "cat-emergency", label: "Emergency", arasaacKeyword: "emergency", llmContext: "The overarching context is an urgent emergency requiring immediate response.", children: emergencyChildren },
{ id: "cat-food", label: "Food", arasaacKeyword: "food", llmContext: "The overarching context is food, groceries, or dietary needs.", children: foodChildren },
{ id: "cat-home", label: "Home", arasaacKeyword: "house", llmContext: "The overarching context is housing, rent, and home maintenance.", children: homeChildren },
{ id: "cat-transport", label: "Transport", arasaacKeyword: "bus stop", llmContext: "The overarching context is transportation and navigation.", children: transportChildren },
{ id: "cat-transport", label: "Transport", arasaacKeyword: "bus stop", arasaacId: 10351, llmContext: "The overarching context is transportation and navigation.", children: transportChildren },
{ id: "cat-work", label: "Work", arasaacKeyword: "work", llmContext: "The overarching context is employment, jobs, and labor rights.", children: workChildren },
];
31 changes: 24 additions & 7 deletions lib/useArasaacImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,30 @@ import { useState, useEffect } from "react";

const cache = new Map<string, string>();

export function useArasaacImage(keyword: string): { url: string | null; loading: boolean } {
const [url, setUrl] = useState<string | null>(cache.get(keyword) ?? null);
const [loading, setLoading] = useState(!cache.has(keyword));
type UseArasaacImageOptions = {
preferredId?: number;
};

export function useArasaacImage(
keyword: string,
options?: UseArasaacImageOptions,
): { url: string | null; loading: boolean } {
const cacheKey = `${keyword}::${options?.preferredId ?? ""}`;
const [url, setUrl] = useState<string | null>(cache.get(cacheKey) ?? null);
const [loading, setLoading] = useState(!cache.has(cacheKey));

useEffect(() => {
if (cache.has(keyword)) {
setUrl(cache.get(keyword)!);
if (cache.has(cacheKey)) {
setUrl(cache.get(cacheKey)!);
setLoading(false);
return;
}

// Explicit override should not depend on search result ordering or matching.
if (typeof options?.preferredId === "number") {
const forcedUrl = `https://static.arasaac.org/pictograms/${options.preferredId}/${options.preferredId}_2500.png`;
cache.set(cacheKey, forcedUrl);
setUrl(forcedUrl);
setLoading(false);
return;
}
Expand All @@ -25,7 +42,7 @@ export function useArasaacImage(keyword: string): { url: string | null; loading:
if (Array.isArray(data) && data.length > 0) {
const id = data[0]._id;
const imageUrl = `https://static.arasaac.org/pictograms/${id}/${id}_2500.png`;
cache.set(keyword, imageUrl);
cache.set(cacheKey, imageUrl);
setUrl(imageUrl);
}
setLoading(false);
Expand All @@ -37,7 +54,7 @@ export function useArasaacImage(keyword: string): { url: string | null; loading:
return () => {
cancelled = true;
};
}, [keyword]);
}, [cacheKey, keyword, options?.preferredId]);

return { url, loading };
}
Loading