@qumra/fanar 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ system — each heavy group is behind its own path.
23
23
  | `@qumra/fanar/lib` | Pure helpers (`cn`, `toLatinDigits`) | server-safe |
24
24
  | `@qumra/fanar/tokens` | Token values (`TOKENS`, scales) | server-safe |
25
25
  | `@qumra/fanar/notifications` | Notification system | client |
26
- | `@qumra/fanar/ai` | Assistant surfaces | needs `jawab-ai` |
26
+ | `@qumra/fanar/ai` | Assistant surfaces | needs `@qumra/jawab-ai` |
27
27
  | `@qumra/fanar/orb` | Animated voice orb | needs `three` |
28
28
  | `@qumra/fanar/editor` | Rich-text editor | bundles TipTap |
29
29
 
@@ -65,7 +65,7 @@ Every component in this library follows them, and two are enforced by lint:
65
65
  ## Peer dependencies
66
66
 
67
67
  `react` and `react-dom` are required. `tailwindcss`, `three`,
68
- `@google/model-viewer`, and `jawab-ai` are **optional** — you only install
68
+ `@google/model-viewer`, and `@qumra/jawab-ai` are **optional** — you only install
69
69
  the ones whose entry point you actually import.
70
70
 
71
71
  ## Documentation
package/dist/ai.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentType, ReactNode } from 'react';
3
- import { Json, Reply, AskResult, AskBlock } from 'jawab-ai';
3
+ import { Json, Reply, AskResult, AskBlock } from '@qumra/jawab-ai';
4
4
 
5
5
  /** أيقونة المساعد — بتاخد حالته فبتوريك هو بيعمل إيه دلوقتي */
6
6
  declare function AiAvatar({ state,
@@ -176,7 +176,7 @@ type IntentMap = Record<string, (args: Json | undefined) => void>;
176
176
  /**
177
177
  * ردّ المساعد مرسوماً.
178
178
  *
179
- * ── ليه الرسّام هنا لا في `jawab-ai` ──────────────────────────────────
179
+ * ── ليه الرسّام هنا لا في `@qumra/jawab-ai` ──────────────────────────────────
180
180
  * العقد لازم يقف لوحده عشان الباك‑اند ياخده من غير React. الرسم لأ:
181
181
  * هو محتاج نظام تصميم كامل — جدول بترقيم، وكرت إحصائي بقاعدة «مافيش
182
182
  * نسبة على أساس صغير»، ورسم بمحاور مضبوطة، وعارض مبالغ بيعرف عملة
package/dist/ai.js CHANGED
@@ -1,2 +1,2 @@
1
1
  "use client";
2
- import{BarChart,ChoiceCards,DataTable,Money,TrendChart,isCurrency}from"./chunk-QJPINFN2.js";import{Button,Input,Textarea}from"./chunk-L3NDN5GB.js";import{Badge,Card,StatCard,formatTime}from"./chunk-JJCKT5OO.js";import{Alert,Modal,cn,useUIText}from"./chunk-DDRJTIIP.js";import{useEffect,useRef,useState}from"react";import{Bot,Check,Copy,ExternalLink,RotateCcw,ThumbsDown,ThumbsUp,TriangleAlert}from"lucide-react";import{Fragment,jsx,jsxs}from"react/jsx-runtime";function AiAvatar({state="idle",level,size="md",icon:Icon=Bot}){const ring=useRef(null);useEffect(()=>{if(state!=="speaking"||!level)return;let raf=0;const loop=()=>{const v=level.current??0;if(ring.current){ring.current.style.transform=`scale(${1+v**1.6*.9})`;ring.current.style.opacity=String(Math.min(.45,v))}raf=requestAnimationFrame(loop)};raf=requestAnimationFrame(loop);const el=ring.current;return()=>{cancelAnimationFrame(raf);if(el)el.style.opacity="0"}},[state,level]);const box={sm:"size-8",md:"size-9",lg:"size-12"}[size];const px={sm:16,md:18,lg:24}[size];return jsxs("span",{className:"relative inline-flex shrink-0",children:[jsx("span",{ref:ring,"aria-hidden":"true",className:cn("absolute inset-0 rounded-sm bg-brand opacity-0",box),style:{transition:"transform 90ms linear, opacity 90ms linear"}}),jsx("span",{"aria-hidden":"true",className:cn("relative rounded-sm bg-soft border border-soft2 flex items-center justify-center",box,state==="thinking"&&"animate-pulse"),children:jsx(Icon,{size:px,strokeWidth:2,className:"text-brand"})})]})}function AiThinking({label}){const ui=useUIText();return jsxs("span",{role:"status","aria-live":"polite",className:"flex items-center gap-2.5",children:[jsx(AiAvatar,{size:"sm",state:"thinking"}),jsxs("span",{className:"flex items-center gap-2 py-2.5 px-3.5 rounded-card bg-bg border border-line",children:[jsx("span",{"aria-hidden":"true",className:"flex items-center gap-1",children:[0,1,2].map(i=>jsx("span",{className:"size-1.5 rounded-full bg-muted2 animate-bounce",style:{animationDelay:`${i*140}ms`,animationDuration:"900ms"}},i))}),jsx("span",{className:"text-caption text-muted",children:label??ui.shehabThinking})]})]})}function AiToolCall({icon:Icon,title,detail,state="done",onUndo,undoLabel}){const ui=useUIText();const tone={running:"bg-bg border-line text-muted",done:"bg-green-bg2 border-green-line text-green",failed:"bg-red-bg border-red-line text-red",reverted:"bg-bg border-line text-muted2"}[state];const off=state==="reverted";return jsxs("div",{className:cn("flex items-start gap-2.5 p-3 rounded-card border",tone),children:[jsx("span",{"aria-hidden":"true",className:"shrink-0 mt-0.5",children:state==="failed"?jsx(TriangleAlert,{size:15}):off?jsx(RotateCcw,{size:15}):state==="done"?jsx(Check,{size:15}):Icon?jsx(Icon,{size:15}):jsx("span",{className:"block size-3.5 rounded-full border-2 border-current border-t-transparent animate-spin"})}),jsxs("span",{className:"flex flex-col gap-1 min-w-0 flex-1",children:[jsx("span",{className:cn("text-ui font-bold",off?"text-muted":"text-ink"),children:title}),detail&&jsx("span",{className:cn("text-caption leading-[1.7]",off?"text-muted2 line-through":"text-ink2"),children:detail})]}),onUndo&&state==="done"&&jsx("button",{type:"button",onClick:onUndo,className:"shrink-0 text-caption font-bold text-brand-ink cursor-pointer hover:underline underline-offset-4",children:undoLabel??ui.undo})]})}function AiSources({sources,label}){const ui=useUIText();if(sources.length===0)return null;return jsxs("span",{className:"flex items-center gap-1.5 flex-wrap",children:[jsx("span",{className:"text-micro text-muted2",children:label??ui.from}),sources.map(s=>{const inner=jsxs(Fragment,{children:[s.label,jsx(ExternalLink,{size:10,"aria-hidden":"true"})]});const cls="inline-flex items-center gap-1 h-6 px-2 rounded-full bg-soft text-brand-ink text-micro font-bold no-underline cursor-pointer transition-colors hover:bg-soft2";return s.href?jsx("a",{href:s.href,className:cls,children:inner},s.id):jsx("button",{type:"button",onClick:s.onOpen,className:cls,children:inner},s.id)})]})}function AiMessage({role,children,streaming,sources,copyText,onRetry,onFeedback,tools,fill}){const ui=useUIText();const[copied,setCopied]=useState(false);const[vote,setVote]=useState(null);const user=role==="user";const actionable=!user&&!streaming&&(copyText||onRetry||onFeedback);return jsxs("div",{className:cn("flex gap-2.5",fill?"w-full":"max-w-[88%]",user?"self-end":"self-start"),children:[!user&&jsx(AiAvatar,{size:"sm"}),jsxs("div",{className:cn("flex flex-col gap-2 min-w-0",fill&&"flex-1"),children:[jsxs("div",{dir:"auto",className:cn("py-2.5 px-3.5 rounded-card text-ui leading-[1.85] whitespace-pre-line",user?"bg-brand text-white":"bg-bg text-ink border border-line"),children:[children,streaming&&jsx("span",{"aria-hidden":"true",className:"inline-block w-[2px] h-[1em] align-[-2px] ms-0.5 bg-brand animate-pulse"})]}),tools,sources&&sources.length>0&&!streaming&&jsx(AiSources,{sources}),actionable&&jsxs("div",{className:"flex items-center gap-0.5",children:[copyText&&jsx(ActionBtn,{label:copied?"\u0627\u062A\u0646\u0633\u062E":"\u0646\u0633\u062E",onClick:()=>{navigator.clipboard?.writeText(copyText);setCopied(true);setTimeout(()=>setCopied(false),1400)},children:copied?jsx(Check,{size:13,className:"text-green"}):jsx(Copy,{size:13})}),onRetry&&jsx(ActionBtn,{label:ui.tryAnotherReply,onClick:onRetry,children:jsx(RotateCcw,{size:13})}),onFeedback&&jsxs(Fragment,{children:[jsx(ActionBtn,{label:ui.helpfulReply,active:vote==="up",onClick:()=>{setVote("up");onFeedback("up")},children:jsx(ThumbsUp,{size:13})}),jsx(ActionBtn,{label:ui.unhelpfulReply,active:vote==="down",onClick:()=>{setVote("down");onFeedback("down")},children:jsx(ThumbsDown,{size:13})})]})]})]})]})}function ActionBtn({onClick,label,active,children}){return jsx("button",{type:"button",onClick,"aria-label":label,title:label,"aria-pressed":active,className:cn("size-7 rounded-xs inline-flex items-center justify-center cursor-pointer transition-colors",active?"bg-soft text-brand-ink":"text-muted2 hover:bg-hover hover:text-ink"),children})}function AiDisclaimer({children}){const ui=useUIText();return jsx("span",{className:"block text-center text-micro leading-[1.7] text-muted2 [text-wrap:pretty]",children:children??ui.aiDisclaimer})}import{useEffect as useEffect3,useRef as useRef3}from"react";import{ArrowUp,Mic,Paperclip,Square,Trash2}from"lucide-react";import{useCallback,useEffect as useEffect2,useRef as useRef2,useState as useState2}from"react";function useStreamingText({speed=18}={}){const[shown,setShown]=useState2("");const[streaming,setStreaming]=useState2(false);const full=useRef2("");const timer=useRef2(void 0);const stopTimer=useCallback(()=>{clearInterval(timer.current);timer.current=void 0},[]);useEffect2(()=>stopTimer,[stopTimer]);const run=useCallback(()=>{if(timer.current)return;timer.current=setInterval(()=>{setShown(s=>{if(s.length>=full.current.length){stopTimer();setStreaming(false);return s}return full.current.slice(0,s.length+1)})},speed)},[speed,stopTimer]);const start=useCallback(text=>{stopTimer();full.current=text;setShown("");setStreaming(true);run()},[run,stopTimer]);const push=useCallback(chunk=>{full.current+=chunk;setStreaming(true);run()},[run]);const stop=useCallback(()=>{stopTimer();full.current=shown;setStreaming(false)},[shown,stopTimer]);const finish=useCallback(()=>{stopTimer();setShown(full.current);setStreaming(false)},[stopTimer]);const reset=useCallback(()=>{stopTimer();full.current="";setShown("");setStreaming(false)},[stopTimer]);return{text:shown,streaming,start,push,stop,finish,reset}}function useMicLevel(){const level=useRef2(0);const[recording,setRecording]=useState2(false);const[seconds,setSeconds]=useState2(0);const[denied,setDenied]=useState2(false);const stream=useRef2(null);const ctx=useRef2(null);const raf=useRef2(0);const tick=useRef2(void 0);const stop=useCallback(()=>{cancelAnimationFrame(raf.current);clearInterval(tick.current);stream.current?.getTracks().forEach(t=>t.stop());void ctx.current?.close();stream.current=null;ctx.current=null;level.current=0;setRecording(false);setSeconds(0)},[]);useEffect2(()=>stop,[stop]);const start=useCallback(async()=>{if(recording)return;try{const media=await navigator.mediaDevices.getUserMedia({audio:true});const Ctx=window.AudioContext??window.webkitAudioContext;if(!Ctx)throw new Error("\u0645\u0627\u0641\u064A\u0634 Web Audio");const c=new Ctx;const src=c.createMediaStreamSource(media);const analyser=c.createAnalyser();analyser.fftSize=512;analyser.smoothingTimeConstant=.7;src.connect(analyser);stream.current=media;ctx.current=c;setDenied(false);setRecording(true);setSeconds(0);const data=new Uint8Array(analyser.frequencyBinCount);const loop=()=>{analyser.getByteTimeDomainData(data);let sum=0;for(let i=0;i<data.length;i++){const v=(data[i]-128)/128;sum+=v*v}level.current=Math.min(1,Math.sqrt(sum/data.length)*3);raf.current=requestAnimationFrame(loop)};raf.current=requestAnimationFrame(loop);tick.current=setInterval(()=>setSeconds(s=>s+1),1e3)}catch{setDenied(true);setRecording(false)}},[recording]);return{level,recording,seconds,start,stop,denied}}import{jsx as jsx2,jsxs as jsxs2}from"react/jsx-runtime";function AiPromptChips({prompts,onPick}){if(prompts.length===0)return null;return jsx2("div",{className:"flex gap-2 overflow-x-auto pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",children:prompts.map(p=>jsx2("button",{type:"button",dir:"auto",onClick:()=>onPick(p),className:"shrink-0 h-8 px-3 rounded-full border border-border bg-surface text-caption font-bold text-ink2 whitespace-nowrap cursor-pointer transition-colors hover:border-brand hover:text-brand-ink",children:p},p))})}function LiveBars({level,count=28}){const bars=useRef3([]);const history=useRef3(Array.from({length:count},()=>0));useEffect3(()=>{let raf=0;let last=0;const loop=t=>{if(t-last>60){last=t;history.current=[...history.current.slice(1),level.current??0];for(let i=0;i<bars.current.length;i++){const el=bars.current[i];if(el)el.style.height=`${Math.max(10,(history.current[i]??0)*100)}%`}}raf=requestAnimationFrame(loop)};raf=requestAnimationFrame(loop);return()=>cancelAnimationFrame(raf)},[level]);return jsx2("span",{dir:"ltr","aria-hidden":"true",className:"flex-1 min-w-0 h-7 flex items-center gap-0.5",children:Array.from({length:count},(_,i)=>jsx2("span",{ref:el=>{bars.current[i]=el},className:"flex-1 rounded-full bg-brand",style:{height:"10%",transition:"height 70ms linear"}},i))})}function AiComposer({value,onChange,onSend,placeholder,busy,onStop,onVoice,onAttach,disabled,className}){const ui=useUIText();const mic=useMicLevel();const area=useRef3(null);useEffect3(()=>{const el=area.current;if(!el)return;el.style.height="auto";el.style.height=`${Math.min(el.scrollHeight,120)}px`},[value]);function submit(){const text=value.trim();if(!text||busy)return;onSend(text)}if(mic.recording){return jsxs2("div",{className:cn("flex items-center gap-3 h-14 px-3 rounded-lg bg-surface border border-brand",className),children:[jsx2("button",{type:"button",onClick:mic.stop,"aria-label":ui.cancelRecording,className:"size-9 rounded-sm shrink-0 flex items-center justify-center text-muted cursor-pointer transition-colors hover:bg-red-bg hover:text-red",children:jsx2(Trash2,{size:17,"aria-hidden":"true"})}),jsx2(LiveBars,{level:mic.level}),jsx2("span",{"data-num":true,className:"text-ui font-bold text-ink2 shrink-0 tabular-nums",children:formatTime(mic.seconds)}),jsx2("button",{type:"button",onClick:()=>{const s=mic.seconds;mic.stop();onVoice?.(s)},"aria-label":ui.sendRecording,className:"size-9 rounded-sm shrink-0 flex items-center justify-center bg-brand text-white cursor-pointer transition-colors hover:bg-brand-hover",children:jsx2(ArrowUp,{size:17,strokeWidth:2.4,"aria-hidden":"true"})})]})}const canSend=value.trim().length>0;return jsxs2("div",{className:cn("flex flex-col gap-1.5",className),children:[jsxs2("div",{className:cn("flex items-end gap-2 p-2 ps-3.5 rounded-lg bg-surface border border-border","focus-within:border-brand transition-colors",disabled&&"opacity-60 pointer-events-none"),children:[jsx2("label",{htmlFor:"ai-composer",className:"sr-only",children:placeholder??ui.askShehab}),jsx2("textarea",{id:"ai-composer",ref:area,rows:1,value,disabled,placeholder:placeholder??ui.askShehab,onChange:e=>onChange(e.target.value),onKeyDown:e=>{if(e.key==="Enter"&&!e.shiftKey){e.preventDefault();submit()}},className:"flex-1 min-w-0 py-2 bg-transparent border-none outline-none resize-none text-body text-ink placeholder:text-muted2"}),jsxs2("span",{className:"flex items-center gap-0.5 shrink-0",children:[onAttach&&jsx2("button",{type:"button",onClick:onAttach,"aria-label":ui.attachFile,className:"size-9 rounded-sm flex items-center justify-center text-muted cursor-pointer transition-colors hover:bg-hover hover:text-ink",children:jsx2(Paperclip,{size:17,"aria-hidden":"true"})}),onVoice&&!canSend&&!busy&&jsx2("button",{type:"button",onClick:()=>void mic.start(),"aria-label":ui.recordVoice,className:"size-9 rounded-sm flex items-center justify-center text-muted cursor-pointer transition-colors hover:bg-hover hover:text-ink",children:jsx2(Mic,{size:17,"aria-hidden":"true"})}),jsx2("button",{type:"button",onClick:busy?onStop:submit,disabled:!busy&&!canSend,"aria-label":busy?"\u0623\u0648\u0642\u0641 \u0627\u0644\u062A\u0648\u0644\u064A\u062F":"\u0625\u0631\u0633\u0627\u0644",className:cn("size-9 rounded-sm flex items-center justify-center shrink-0 transition-colors",busy?"bg-ink2 text-surface cursor-pointer hover:bg-ink":canSend?"bg-brand text-white cursor-pointer hover:bg-brand-hover":"bg-soft text-brand-ink/40 cursor-not-allowed"),children:busy?jsx2(Square,{size:13,fill:"currentColor","aria-hidden":"true"}):jsx2(ArrowUp,{size:17,strokeWidth:2.4,"aria-hidden":"true"})})]})]}),mic.denied&&jsx2("span",{className:"text-caption text-red",children:"\u0627\u0644\u0645\u064A\u0643\u0631\u0648\u0641\u0648\u0646 \u0645\u0631\u0641\u0648\u0636 \u2014 \u0627\u0633\u0645\u062D \u0644\u0644\u0645\u0648\u0642\u0639 \u0628\u0627\u0644\u0648\u0635\u0648\u0644 \u0644\u0644\u0645\u064A\u0643\u0631\u0648\u0641\u0648\u0646 \u0645\u0646 \u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0627\u0644\u0645\u062A\u0635\u0641\u0651\u062D."})]})}import{useState as useState5}from"react";import{useState as useState3}from"react";import{ArrowRight,Check as Check2,MessageSquarePlus}from"lucide-react";import{OTHER_PREFIX}from"jawab-ai";import{jsx as jsx3,jsxs as jsxs3}from"react/jsx-runtime";function AskCard({block,onAnswer,live=true}){const[at,setAt]=useState3(0);const[picked,setPicked]=useState3({});const[other,setOther]=useState3({});const[notes,setNotes]=useState3({});const[noting,setNoting]=useState3({});const[sent,setSent]=useState3(null);const answerable=live&&Boolean(onAnswer)&&!sent;const total=block.questions.length;const q=block.questions[at];const last=at===total-1;function toggle(question,value){setPicked(prev=>{const cur=prev[question.key]??[];if(!question.multi)return{...prev,[question.key]:[value]};return{...prev,[question.key]:cur.includes(value)?cur.filter(v=>v!==value):[...cur,value]}})}function valuesFor(question){const base=picked[question.key]??[];const typed=other[question.key]?.trim();return typed?[...base,OTHER_PREFIX+typed]:base}function labelsFor(question){return valuesFor(question).map(v=>v.startsWith(OTHER_PREFIX)?v.slice(OTHER_PREFIX.length):question.options.find(o=>o.value===v)?.label??v)}const answered=valuesFor(q).length>0;const canGo=q.required===false||answered;function submit(){const result={answers:{},values:{}};const notesOut={};for(const question of block.questions){const values=valuesFor(question);if(values.length===0)continue;result.values[question.key]=values;result.answers[question.label]=labelsFor(question).join("\u060C ");const note=notes[question.key]?.trim();if(note)notesOut[question.label]={notes:note}}if(Object.keys(notesOut).length)result.annotations=notesOut;setSent(result);onAnswer?.(block.id,result)}if(sent){return jsxs3("div",{className:"flex flex-col gap-3 p-4 rounded-card border border-line bg-bg",children:[block.questions.map(question=>jsxs3("div",{className:"flex flex-col gap-1.5",children:[jsx3("span",{className:"text-caption text-muted [text-wrap:pretty]",children:question.label}),jsx3(Picked,{labels:(sent.answers[question.label]??"").split("\u060C ").filter(Boolean),note:sent.annotations?.[question.label]?.notes})]},question.key)),jsxs3("span",{className:"flex items-center gap-1.5 text-caption font-bold text-green",children:[jsx3(Check2,{size:13,strokeWidth:2.6,"aria-hidden":"true"}),"\u0627\u062A\u0628\u0639\u062A"]})]})}return jsxs3("div",{className:"flex flex-col gap-3.5 p-4 rounded-card border border-soft2 bg-tint",children:[jsxs3("span",{className:"flex items-center justify-between gap-3",children:[q.header?jsx3("span",{className:"px-2 h-5 rounded-mark bg-soft text-brand-ink text-micro font-extrabold flex items-center",children:q.header}):jsx3("span",{}),total>1&&jsxs3("span",{className:"flex items-center gap-2 shrink-0",children:[jsx3("span",{"aria-hidden":"true",className:"flex items-center gap-1",children:block.questions.map((qq,i)=>jsx3("span",{className:cn("size-1.5 rounded-full transition-colors",i===at?"bg-brand":i<at?"bg-brand/40":"bg-border/40")},qq.key))}),jsxs3("span",{"data-num":true,className:"text-micro font-bold text-muted whitespace-nowrap",children:[at+1," \u0645\u0646 ",total]})]})]}),jsxs3("div",{className:"flex flex-col gap-2",children:[jsx3("span",{className:"text-ui font-extrabold text-ink [text-wrap:pretty]",children:q.label}),q.hint&&jsx3("span",{className:"text-caption leading-[1.7] text-muted",children:q.hint}),jsx3(ChoiceCards,{name:`${block.id}-${q.key}`,multiple:q.multi,disabled:!answerable,value:q.multi?picked[q.key]??[]:picked[q.key]?.[0]??null,onChange:v=>toggle(q,v),options:q.options.map(o=>({value:o.value,label:o.label,meta:o.description}))}),q.other!==false&&jsx3(Input,{size:"sm",value:other[q.key]??"",onChange:e=>setOther(p=>({...p,[q.key]:e.target.value})),disabled:!answerable,placeholder:q.other?.placeholder??"\u0623\u0648 \u0627\u0643\u062A\u0628 \u0625\u062C\u0627\u0628\u062A\u0643\u2026","aria-label":`\u0625\u062C\u0627\u0628\u0629 \u0623\u062E\u0631\u0649 \u0639\u0644\u0649: ${q.label}`}),noting[q.key]?jsx3(Textarea,{rows:2,value:notes[q.key]??"",onChange:e=>setNotes(p=>({...p,[q.key]:e.target.value})),disabled:!answerable,placeholder:"\u062D\u0627\u062C\u0629 \u062A\u062D\u0628\u0651 \u062A\u0642\u0648\u0644\u0647\u0627 \u0645\u0639 \u0627\u062E\u062A\u064A\u0627\u0631\u0643\u2026","aria-label":`\u0645\u0644\u0627\u062D\u0638\u0629 \u0639\u0644\u0649: ${q.label}`}):answerable&&jsxs3("button",{type:"button",onClick:()=>setNoting(p=>({...p,[q.key]:true})),className:"self-start inline-flex items-center gap-1.5 text-micro font-bold text-muted hover:text-brand cursor-pointer transition-colors",children:[jsx3(MessageSquarePlus,{size:13,"aria-hidden":"true"}),"\u0636\u064A\u0641 \u0645\u0644\u0627\u062D\u0638\u0629"]})]}),answerable&&jsxs3("span",{className:"flex items-center gap-2.5",children:[at>0&&jsx3(Button,{size:"sm",variant:"ghost",onClick:()=>setAt(i=>i-1),children:"\u0631\u062C\u0648\u0639"}),last?jsx3(Button,{size:"sm",onClick:submit,disabled:!canGo,children:block.submitLabel??"\u0627\u0628\u0639\u062A"}):jsx3(Button,{size:"sm",onClick:()=>setAt(i=>i+1),disabled:!canGo,iconEnd:jsx3(ArrowRight,{size:13,className:"rtl:rotate-180","aria-hidden":"true"}),children:"\u0627\u0644\u062A\u0627\u0644\u064A"}),!canGo&&jsx3("span",{className:"text-micro text-muted2",children:"\u0627\u062E\u062A\u0627\u0631 \u0625\u062C\u0627\u0628\u0629 \u0627\u0644\u0623\u0648\u0644"})]})]})}function Picked({labels,note}){if(labels.length===0)return jsx3("span",{className:"text-caption text-muted2",children:"\u0627\u062A\u062E\u0637\u0651\u0649"});return jsxs3("span",{className:"flex flex-col gap-1.5",children:[jsx3("span",{className:"flex flex-wrap gap-1.5",children:labels.map((l,i)=>jsxs3("span",{className:"inline-flex items-center gap-1 h-6 px-2 rounded-mark bg-soft text-brand-ink text-caption font-bold",children:[jsx3(Check2,{size:11,strokeWidth:3,"aria-hidden":"true"}),l]},i))}),note&&jsx3("span",{className:"text-micro leading-[1.7] text-muted border-s-2 border-line ps-2",children:note})]})}import{useEffect as useEffect4,useState as useState4}from"react";import{Check as Check3,Copy as Copy2,RotateCcw as RotateCcw2,Sparkles,X}from"lucide-react";import{Fragment as Fragment2,jsx as jsx4,jsxs as jsxs4}from"react/jsx-runtime";function DraftPanel({draft:source,basis,label,onInsert,onClose,insertLabel="\u0623\u062F\u0631\u0650\u062C",className}){const[attempt,setAttempt]=useState4(0);const[done,setDone]=useState4(false);const{text,streaming,start,finish}=useStreamingText({speed:12});const dynamic=typeof source==="function";useEffect4(()=>{start(dynamic?source(attempt):source)},[attempt,source]);return jsxs4("div",{className:cn("flex flex-col gap-2.5 p-3.5 rounded-card bg-bg border border-line","basis-full order-last w-full",className),children:[jsxs4("span",{className:"flex items-start justify-between gap-3",children:[jsxs4("span",{className:"flex flex-col gap-0.5 min-w-0",children:[jsxs4("span",{className:"flex items-center gap-1.5 text-caption font-extrabold text-ink",children:[jsx4(Sparkles,{size:13,className:"text-brand shrink-0","aria-hidden":"true"}),label]}),jsx4("span",{className:"text-micro leading-[1.65] text-muted",children:basis})]}),onClose&&jsx4("button",{type:"button",onClick:onClose,"aria-label":"\u0625\u063A\u0644\u0627\u0642",className:"shrink-0 size-6 rounded-mark flex items-center justify-center text-muted2 hover:bg-hover hover:text-ink cursor-pointer transition-colors",children:jsx4(X,{size:14,"aria-hidden":"true"})})]}),text===""&&streaming?jsx4(AiThinking,{label:"\u0628\u064A\u0643\u062A\u0628\u2026"}):jsxs4("span",{className:"block text-caption leading-[1.9] text-ink whitespace-pre-wrap [text-wrap:pretty]","aria-live":"polite",children:[text,streaming&&jsx4("span",{"aria-hidden":"true",className:"inline-block w-[2px] h-[1em] align-[-0.15em] ms-0.5 bg-brand animate-pulse"})]}),jsx4("span",{className:"flex items-center gap-2 flex-wrap",children:streaming?jsx4(Button,{size:"sm",variant:"outline",onClick:finish,children:"\u0627\u0639\u0631\u0636\u0647\u0627 \u0643\u0644\u0647\u0627"}):jsxs4(Fragment2,{children:[onInsert&&jsx4(Button,{size:"sm",onClick:()=>{onInsert(text);setDone(true);onClose?.()},icon:jsx4(Check3,{size:13,"aria-hidden":"true"}),children:insertLabel}),jsx4(Button,{size:"sm",variant:"outline",onClick:()=>navigator.clipboard?.writeText(text),icon:jsx4(Copy2,{size:13,"aria-hidden":"true"}),children:"\u0627\u0646\u0633\u062E"}),dynamic&&jsx4(Button,{size:"sm",variant:"ghost",onClick:()=>setAttempt(a=>a+1),icon:jsx4(RotateCcw2,{size:13,"aria-hidden":"true"}),children:"\u0627\u0643\u062A\u0628 \u063A\u064A\u0631\u0647\u0627"})]})}),!streaming&&!done&&jsx4(AiDisclaimer,{children:"\u0645\u0645\u0643\u0646 \u064A\u063A\u0644\u0637 \u2014 \u0627\u0642\u0631\u0627\u0647\u0627 \u0642\u0628\u0644 \u0645\u0627 \u062A\u0646\u0634\u0631\u0647\u0627."})]})}function AiDraft({size="sm",...panel}){const[open,setOpen]=useState4(false);if(!open){return jsx4(Button,{variant:"outline",size,onClick:()=>setOpen(true),icon:jsx4(Sparkles,{size:size==="sm"?13:15,"aria-hidden":"true"}),className:panel.className,children:panel.label})}return jsx4(DraftPanel,{...panel,onClose:()=>setOpen(false)})}import{Fragment as Fragment3,jsx as jsx5}from"react/jsx-runtime";function Markdown({md}){const chunks=md.split(/\n{2,}/);return jsx5(Fragment3,{children:chunks.map((chunk,i)=>{const lines=chunk.split("\n");const bullets=lines.every(l=>/^\s*[-•]\s+/.test(l));const numbers=lines.every(l=>/^\s*\d+[.)]\s+/.test(l));if(bullets||numbers){const items=lines.map(l=>l.replace(/^\s*(?:[-•]|\d+[.)])\s+/,""));const List=numbers?"ol":"ul";return jsx5(List,{className:numbers?"my-2 ps-5 list-decimal marker:text-muted2 flex flex-col gap-1":"my-2 ps-5 list-disc marker:text-muted2 flex flex-col gap-1",children:items.map((it,j)=>jsx5("li",{children:inline(it)},j))},i)}return jsx5("p",{className:"my-2 first:mt-0 last:mb-0 [text-wrap:pretty]",children:inline(chunk)},i)})})}var TOKEN=/(`[^`\n]+`)|(\*\*[^*\n]+\*\*)|(\*[^*\n]+\*)/g;function inline(src){const out=[];let last=0;let m;TOKEN.lastIndex=0;while((m=TOKEN.exec(src))!==null){if(m.index>last)out.push(src.slice(last,m.index));const[full]=m;const key=`${m.index}`;if(full.startsWith("`")){out.push(jsx5("code",{dir:"ltr",className:"px-1 py-px rounded-mark bg-hover text-[0.9em] font-mono text-brand-ink",children:full.slice(1,-1)},key))}else if(full.startsWith("**")){out.push(jsx5("strong",{className:"font-extrabold text-ink",children:full.slice(2,-2)},key))}else{out.push(jsx5("em",{className:"not-italic font-bold",children:full.slice(1,-1)},key))}last=m.index+full.length}if(last<src.length)out.push(src.slice(last));return out}import{Fragment as Fragment4,jsx as jsx6,jsxs as jsxs5}from"react/jsx-runtime";var TONE={good:"green",warn:"amber",bad:"red",plain:"neutral"};var NOTICE_TONE={info:"brand",warn:"amber",danger:"red"};function major(a){return a.minor/10**(a.decimals??2)}function TextBlockView({block}){if(!block.md)return null;return jsx6("div",{className:"text-body leading-[1.95] text-ink2",children:jsx6(Markdown,{md:block.md})})}function MetricBlockView({block}){return jsx6("div",{className:"grid gap-2.5 grid-cols-[repeat(auto-fit,minmax(9.5rem,1fr))]",children:block.items.map((m,i)=>jsx6(StatCard,{tone:TONE[m.tone??"plain"]??"neutral",label:m.label,value:m.amount?jsx6(Money,{value:major(m.amount),currency:isCurrency(m.amount.currency)?m.amount.currency:void 0,compact:true,trimZeros:true}):m.value,hint:m.delta!==void 0?deltaHint(m.delta,m.deltaBasis):void 0},i))})}function deltaHint(delta,basis){const up=delta>0;const flat=delta===0;return jsxs5("span",{className:cn("font-bold",flat?"text-muted2":up?"text-green":"text-red"),children:[flat?"=":up?"\u25B2":"\u25BC"," ",Math.abs(delta),"\u066A",basis?` ${basis}`:""]})}function TableBlockView({block}){const rows=block.rows.map((cells,i)=>{const row={__key:String(i)};block.cols.forEach((c,j)=>{row[c.key]=cells[j]??null});return row});const columns=block.cols.map(c=>({key:c.key,header:c.label,end:c.align==="end",cell:row=>renderCell(row[c.key],c)}));return jsxs5("span",{className:"flex flex-col gap-1.5",children:[block.caption&&jsx6("span",{className:"text-micro text-muted2",children:block.caption}),jsx6(DataTable,{columns,rows,rowKey:r=>r.__key,grid:block.cols.map(c=>c.align==="end"?"1fr":"1.6fr").join("_"),minWidth:block.cols.length*120,mobileCard:row=>jsx6("span",{className:"flex flex-col gap-1",children:block.cols.map(c=>jsxs5("span",{className:"flex items-baseline justify-between gap-3",children:[jsx6("span",{className:"text-micro text-muted2",children:c.label}),jsx6("span",{className:"text-caption text-ink",children:renderCell(row[c.key],c)})]},c.key))})})]})}function renderCell(v,col){if(v===null||v==="")return jsx6("span",{className:"text-muted2",children:"\u2014"});switch(col.format){case"money":return typeof v==="number"?jsx6(Money,{value:v,trimZeros:true}):v;case"badge":return jsx6(Badge,{size:"sm",children:v});case"number":return jsx6("span",{"data-num":true,children:v});default:return v}}function CardsBlockView({block,intents}){return jsx6("div",{className:"grid gap-2.5 grid-cols-[repeat(auto-fill,minmax(13rem,1fr))]",children:block.items.map(c=>jsx6(Card,{children:jsxs5("span",{className:"flex gap-3 items-start",children:[c.img&&jsx6("img",{src:c.img,alt:"",className:"size-12 rounded-sm object-cover bg-hover shrink-0"}),jsxs5("span",{className:"flex flex-col gap-1 min-w-0",children:[jsx6("span",{className:"text-ui font-bold text-ink truncate",children:c.title}),c.subtitle&&jsx6("span",{className:"text-micro text-muted",children:c.subtitle}),c.badge&&jsx6("span",{children:jsx6(Badge,{size:"sm",tone:"brand",children:c.badge})}),c.action&&jsx6(ActionButton,{action:c.action,intents,size:"sm"})]})]})},c.id))})}function ChartBlockView({block}){const labels=block.series.map(p=>String(p.x));const values=block.series.map(p=>p.y);const money=block.unit?.kind==="money";const fmt=v=>block.unit?.kind==="percent"?`${v}\u066A`:v.toLocaleString("ar-EG");if(block.kind==="bar"){return jsx6(BarChart,{labels,series:[{label:"",values}],formatValue:fmt})}return jsx6(TrendChart,{labels:axisLabels(labels),values,pointLabels:labels,formatValue:money?v=>v.toLocaleString("ar-EG"):fmt,height:170})}function axisLabels(all){if(all.length<=3)return all;return[all[0],all[Math.floor(all.length/2)],all[all.length-1]]}function ActionBlockView({block,intents}){const runnable=block.items.filter(a=>intents[a.intent]);if(runnable.length===0)return null;return jsx6("span",{className:"flex flex-wrap gap-2",children:runnable.map((a,i)=>jsx6(ActionButton,{action:a,intents},i))})}function ActionButton({action,intents,size="sm"}){const[asking,setAsking]=useState5(false);const run=intents[action.intent];if(!run)return null;function fire(){setAsking(false);run(action.args)}return jsxs5(Fragment4,{children:[jsx6(Button,{size,variant:action.style==="quiet"?"outline":"primary",onClick:()=>action.confirm?setAsking(true):fire(),children:action.label}),jsx6(Modal,{open:asking,title:action.label,description:action.confirm,onClose:()=>setAsking(false),size:"sm",footer:jsxs5(Fragment4,{children:[jsx6(Button,{variant:"outline",onClick:()=>setAsking(false),children:"\u0625\u0644\u063A\u0627\u0621"}),jsx6(Button,{onClick:fire,children:action.label})]})})]})}function ToolBlockView({block,intents}){const undo=block.undo;const run=undo?intents[undo.intent]:void 0;return jsx6(AiToolCall,{title:block.title,detail:block.detail,state:block.state,onUndo:run&&undo?()=>run(undo.args):void 0,undoLabel:undo?.label})}function NoticeBlockView({block}){return jsx6(Alert,{tone:NOTICE_TONE[block.tone],children:block.text})}function ChipsBlockView({block,onSend}){if(!onSend)return null;const byLabel=new Map(block.items.map(c=>[c.label,c.send]));return jsx6(AiPromptChips,{prompts:block.items.map(c=>c.label),onPick:label=>onSend(byLabel.get(label)??label)})}function BlockView({block,intents,onSend,onInsertDraft,onAnswer,live=true}){switch(block.type){case"text":return jsx6(TextBlockView,{block});case"metric":return jsx6(MetricBlockView,{block});case"table":return jsx6(TableBlockView,{block});case"cards":return jsx6(CardsBlockView,{block,intents});case"chart":return jsx6(ChartBlockView,{block});case"action":return jsx6(ActionBlockView,{block,intents});case"tool":return jsx6(ToolBlockView,{block,intents});case"notice":return jsx6(NoticeBlockView,{block});case"chips":return jsx6(ChipsBlockView,{block,onSend});case"ask":return jsx6(AskCard,{block,onAnswer,live});case"draft":return jsx6(DraftPanel,{draft:block.text,basis:block.basis,label:"\u0645\u0633\u0648\u0651\u062F\u0629",onInsert:onInsertDraft});default:return null}}import{jsx as jsx7,jsxs as jsxs6}from"react/jsx-runtime";var NO_INTENTS={};function ReplyView({reply,streaming,intents=NO_INTENTS,onSend,onInsertDraft,onAnswer,live=true,onRetry,onFeedback,className}){return jsx7(AiMessage,{role:"assistant",fill:true,streaming,sources:reply.sources,copyText:plainText(reply),onRetry,onFeedback,children:jsxs6("span",{className:cn("flex flex-col gap-3",className),children:[reply.blocks.map((block,i)=>jsx7(BlockView,{block,intents,onSend,onInsertDraft,onAnswer,live},i)),reply.disclaimer!==false&&!streaming&&jsx7(AiDisclaimer,{})]})})}function plainText(reply){const parts=[];for(const b of reply.blocks){switch(b.type){case"text":parts.push(b.md);break;case"metric":parts.push(b.items.map(m=>`${m.label}: ${m.value}`).join(" \xB7 "));break;case"table":parts.push([b.cols.map(c=>c.label).join(" "),...b.rows.map(r=>r.map(c=>c??"").join(" "))].join("\n"));break;case"cards":parts.push(b.items.map(c=>c.title).join("\n"));break;case"draft":parts.push(b.text);break;case"tool":parts.push(b.detail?`${b.title} \u2014 ${b.detail}`:b.title);break;case"notice":parts.push(b.text);break;case"ask":parts.push(b.questions.map(q=>q.label).join("\n"));break;case"chart":case"action":case"chips":break}}return parts.filter(Boolean).join("\n\n")}import{useCallback as useCallback2,useEffect as useEffect5,useRef as useRef4,useState as useState6}from"react";import{parseEvent,parseSSE,reduce}from"jawab-ai";function useReplyStream(url,init){const[reply,setReply]=useState6(null);const[streaming,setStreaming]=useState6(false);const[error,setError]=useState6(null);const abort=useRef4(null);const lastInput=useRef4("");useEffect5(()=>()=>abort.current?.abort(),[]);const stop=useCallback2(()=>{abort.current?.abort();abort.current=null;setStreaming(false)},[]);const send=useCallback2(text=>{const question=text.trim();if(!question)return;abort.current?.abort();const ctrl=new AbortController;abort.current=ctrl;lastInput.current=question;setError(null);setReply(null);setStreaming(true);void(async()=>{try{const res=await fetch(url,{method:"POST",headers:{"content-type":"application/json",accept:"text/event-stream"},body:JSON.stringify({input:question}),signal:ctrl.signal,...init});if(!res.ok||!res.body){throw new StreamError("http_"+res.status,`\u0627\u0644\u062E\u0627\u062F\u0645 \u0631\u062C\u0651\u0639 ${res.status}`)}const decoder=new TextDecoder;const readerStream=res.body.getReader();let rest="";let state=null;for(;;){const{done,value}=await readerStream.read();if(done)break;rest+=decoder.decode(value,{stream:true});const{events,rest:tail}=parseSSE(rest);rest=tail;for(const raw of events){const ev=parseEvent(raw);if(!ev)continue;if(ev.e==="error")throw new StreamError(ev.code,ev.message);state=reduce(state,ev);setReply(state);if(ev.e==="done")break}}setStreaming(false)}catch(e){if(ctrl.signal.aborted)return;const se=e;setError({code:se.code??"unknown",message:se.message??"\u0627\u0644\u0627\u062A\u0635\u0627\u0644 \u0627\u062A\u0642\u0637\u0639.",lastInput:lastInput.current});setStreaming(false)}})()},[url,init]);return{reply,streaming,error,send,stop}}var StreamError=class extends Error{code;constructor(code,message){super(message);this.name="StreamError";this.code=code}};export{AiAvatar,AiComposer,AiDisclaimer,AiDraft,AiMessage,AiPromptChips,AiSources,AiThinking,AiToolCall,AskCard,DraftPanel,ReplyView,useMicLevel,useReplyStream,useStreamingText};
2
+ import{BarChart,ChoiceCards,DataTable,Money,TrendChart,isCurrency}from"./chunk-QJPINFN2.js";import{Button,Input,Textarea}from"./chunk-L3NDN5GB.js";import{Badge,Card,StatCard,formatTime}from"./chunk-JJCKT5OO.js";import{Alert,Modal,cn,useUIText}from"./chunk-DDRJTIIP.js";import{useEffect,useRef,useState}from"react";import{Bot,Check,Copy,ExternalLink,RotateCcw,ThumbsDown,ThumbsUp,TriangleAlert}from"lucide-react";import{Fragment,jsx,jsxs}from"react/jsx-runtime";function AiAvatar({state="idle",level,size="md",icon:Icon=Bot}){const ring=useRef(null);useEffect(()=>{if(state!=="speaking"||!level)return;let raf=0;const loop=()=>{const v=level.current??0;if(ring.current){ring.current.style.transform=`scale(${1+v**1.6*.9})`;ring.current.style.opacity=String(Math.min(.45,v))}raf=requestAnimationFrame(loop)};raf=requestAnimationFrame(loop);const el=ring.current;return()=>{cancelAnimationFrame(raf);if(el)el.style.opacity="0"}},[state,level]);const box={sm:"size-8",md:"size-9",lg:"size-12"}[size];const px={sm:16,md:18,lg:24}[size];return jsxs("span",{className:"relative inline-flex shrink-0",children:[jsx("span",{ref:ring,"aria-hidden":"true",className:cn("absolute inset-0 rounded-sm bg-brand opacity-0",box),style:{transition:"transform 90ms linear, opacity 90ms linear"}}),jsx("span",{"aria-hidden":"true",className:cn("relative rounded-sm bg-soft border border-soft2 flex items-center justify-center",box,state==="thinking"&&"animate-pulse"),children:jsx(Icon,{size:px,strokeWidth:2,className:"text-brand"})})]})}function AiThinking({label}){const ui=useUIText();return jsxs("span",{role:"status","aria-live":"polite",className:"flex items-center gap-2.5",children:[jsx(AiAvatar,{size:"sm",state:"thinking"}),jsxs("span",{className:"flex items-center gap-2 py-2.5 px-3.5 rounded-card bg-bg border border-line",children:[jsx("span",{"aria-hidden":"true",className:"flex items-center gap-1",children:[0,1,2].map(i=>jsx("span",{className:"size-1.5 rounded-full bg-muted2 animate-bounce",style:{animationDelay:`${i*140}ms`,animationDuration:"900ms"}},i))}),jsx("span",{className:"text-caption text-muted",children:label??ui.shehabThinking})]})]})}function AiToolCall({icon:Icon,title,detail,state="done",onUndo,undoLabel}){const ui=useUIText();const tone={running:"bg-bg border-line text-muted",done:"bg-green-bg2 border-green-line text-green",failed:"bg-red-bg border-red-line text-red",reverted:"bg-bg border-line text-muted2"}[state];const off=state==="reverted";return jsxs("div",{className:cn("flex items-start gap-2.5 p-3 rounded-card border",tone),children:[jsx("span",{"aria-hidden":"true",className:"shrink-0 mt-0.5",children:state==="failed"?jsx(TriangleAlert,{size:15}):off?jsx(RotateCcw,{size:15}):state==="done"?jsx(Check,{size:15}):Icon?jsx(Icon,{size:15}):jsx("span",{className:"block size-3.5 rounded-full border-2 border-current border-t-transparent animate-spin"})}),jsxs("span",{className:"flex flex-col gap-1 min-w-0 flex-1",children:[jsx("span",{className:cn("text-ui font-bold",off?"text-muted":"text-ink"),children:title}),detail&&jsx("span",{className:cn("text-caption leading-[1.7]",off?"text-muted2 line-through":"text-ink2"),children:detail})]}),onUndo&&state==="done"&&jsx("button",{type:"button",onClick:onUndo,className:"shrink-0 text-caption font-bold text-brand-ink cursor-pointer hover:underline underline-offset-4",children:undoLabel??ui.undo})]})}function AiSources({sources,label}){const ui=useUIText();if(sources.length===0)return null;return jsxs("span",{className:"flex items-center gap-1.5 flex-wrap",children:[jsx("span",{className:"text-micro text-muted2",children:label??ui.from}),sources.map(s=>{const inner=jsxs(Fragment,{children:[s.label,jsx(ExternalLink,{size:10,"aria-hidden":"true"})]});const cls="inline-flex items-center gap-1 h-6 px-2 rounded-full bg-soft text-brand-ink text-micro font-bold no-underline cursor-pointer transition-colors hover:bg-soft2";return s.href?jsx("a",{href:s.href,className:cls,children:inner},s.id):jsx("button",{type:"button",onClick:s.onOpen,className:cls,children:inner},s.id)})]})}function AiMessage({role,children,streaming,sources,copyText,onRetry,onFeedback,tools,fill}){const ui=useUIText();const[copied,setCopied]=useState(false);const[vote,setVote]=useState(null);const user=role==="user";const actionable=!user&&!streaming&&(copyText||onRetry||onFeedback);return jsxs("div",{className:cn("flex gap-2.5",fill?"w-full":"max-w-[88%]",user?"self-end":"self-start"),children:[!user&&jsx(AiAvatar,{size:"sm"}),jsxs("div",{className:cn("flex flex-col gap-2 min-w-0",fill&&"flex-1"),children:[jsxs("div",{dir:"auto",className:cn("py-2.5 px-3.5 rounded-card text-ui leading-[1.85] whitespace-pre-line",user?"bg-brand text-white":"bg-bg text-ink border border-line"),children:[children,streaming&&jsx("span",{"aria-hidden":"true",className:"inline-block w-[2px] h-[1em] align-[-2px] ms-0.5 bg-brand animate-pulse"})]}),tools,sources&&sources.length>0&&!streaming&&jsx(AiSources,{sources}),actionable&&jsxs("div",{className:"flex items-center gap-0.5",children:[copyText&&jsx(ActionBtn,{label:copied?"\u0627\u062A\u0646\u0633\u062E":"\u0646\u0633\u062E",onClick:()=>{navigator.clipboard?.writeText(copyText);setCopied(true);setTimeout(()=>setCopied(false),1400)},children:copied?jsx(Check,{size:13,className:"text-green"}):jsx(Copy,{size:13})}),onRetry&&jsx(ActionBtn,{label:ui.tryAnotherReply,onClick:onRetry,children:jsx(RotateCcw,{size:13})}),onFeedback&&jsxs(Fragment,{children:[jsx(ActionBtn,{label:ui.helpfulReply,active:vote==="up",onClick:()=>{setVote("up");onFeedback("up")},children:jsx(ThumbsUp,{size:13})}),jsx(ActionBtn,{label:ui.unhelpfulReply,active:vote==="down",onClick:()=>{setVote("down");onFeedback("down")},children:jsx(ThumbsDown,{size:13})})]})]})]})]})}function ActionBtn({onClick,label,active,children}){return jsx("button",{type:"button",onClick,"aria-label":label,title:label,"aria-pressed":active,className:cn("size-7 rounded-xs inline-flex items-center justify-center cursor-pointer transition-colors",active?"bg-soft text-brand-ink":"text-muted2 hover:bg-hover hover:text-ink"),children})}function AiDisclaimer({children}){const ui=useUIText();return jsx("span",{className:"block text-center text-micro leading-[1.7] text-muted2 [text-wrap:pretty]",children:children??ui.aiDisclaimer})}import{useEffect as useEffect3,useRef as useRef3}from"react";import{ArrowUp,Mic,Paperclip,Square,Trash2}from"lucide-react";import{useCallback,useEffect as useEffect2,useRef as useRef2,useState as useState2}from"react";function useStreamingText({speed=18}={}){const[shown,setShown]=useState2("");const[streaming,setStreaming]=useState2(false);const full=useRef2("");const timer=useRef2(void 0);const stopTimer=useCallback(()=>{clearInterval(timer.current);timer.current=void 0},[]);useEffect2(()=>stopTimer,[stopTimer]);const run=useCallback(()=>{if(timer.current)return;timer.current=setInterval(()=>{setShown(s=>{if(s.length>=full.current.length){stopTimer();setStreaming(false);return s}return full.current.slice(0,s.length+1)})},speed)},[speed,stopTimer]);const start=useCallback(text=>{stopTimer();full.current=text;setShown("");setStreaming(true);run()},[run,stopTimer]);const push=useCallback(chunk=>{full.current+=chunk;setStreaming(true);run()},[run]);const stop=useCallback(()=>{stopTimer();full.current=shown;setStreaming(false)},[shown,stopTimer]);const finish=useCallback(()=>{stopTimer();setShown(full.current);setStreaming(false)},[stopTimer]);const reset=useCallback(()=>{stopTimer();full.current="";setShown("");setStreaming(false)},[stopTimer]);return{text:shown,streaming,start,push,stop,finish,reset}}function useMicLevel(){const level=useRef2(0);const[recording,setRecording]=useState2(false);const[seconds,setSeconds]=useState2(0);const[denied,setDenied]=useState2(false);const stream=useRef2(null);const ctx=useRef2(null);const raf=useRef2(0);const tick=useRef2(void 0);const stop=useCallback(()=>{cancelAnimationFrame(raf.current);clearInterval(tick.current);stream.current?.getTracks().forEach(t=>t.stop());void ctx.current?.close();stream.current=null;ctx.current=null;level.current=0;setRecording(false);setSeconds(0)},[]);useEffect2(()=>stop,[stop]);const start=useCallback(async()=>{if(recording)return;try{const media=await navigator.mediaDevices.getUserMedia({audio:true});const Ctx=window.AudioContext??window.webkitAudioContext;if(!Ctx)throw new Error("\u0645\u0627\u0641\u064A\u0634 Web Audio");const c=new Ctx;const src=c.createMediaStreamSource(media);const analyser=c.createAnalyser();analyser.fftSize=512;analyser.smoothingTimeConstant=.7;src.connect(analyser);stream.current=media;ctx.current=c;setDenied(false);setRecording(true);setSeconds(0);const data=new Uint8Array(analyser.frequencyBinCount);const loop=()=>{analyser.getByteTimeDomainData(data);let sum=0;for(let i=0;i<data.length;i++){const v=(data[i]-128)/128;sum+=v*v}level.current=Math.min(1,Math.sqrt(sum/data.length)*3);raf.current=requestAnimationFrame(loop)};raf.current=requestAnimationFrame(loop);tick.current=setInterval(()=>setSeconds(s=>s+1),1e3)}catch{setDenied(true);setRecording(false)}},[recording]);return{level,recording,seconds,start,stop,denied}}import{jsx as jsx2,jsxs as jsxs2}from"react/jsx-runtime";function AiPromptChips({prompts,onPick}){if(prompts.length===0)return null;return jsx2("div",{className:"flex gap-2 overflow-x-auto pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",children:prompts.map(p=>jsx2("button",{type:"button",dir:"auto",onClick:()=>onPick(p),className:"shrink-0 h-8 px-3 rounded-full border border-border bg-surface text-caption font-bold text-ink2 whitespace-nowrap cursor-pointer transition-colors hover:border-brand hover:text-brand-ink",children:p},p))})}function LiveBars({level,count=28}){const bars=useRef3([]);const history=useRef3(Array.from({length:count},()=>0));useEffect3(()=>{let raf=0;let last=0;const loop=t=>{if(t-last>60){last=t;history.current=[...history.current.slice(1),level.current??0];for(let i=0;i<bars.current.length;i++){const el=bars.current[i];if(el)el.style.height=`${Math.max(10,(history.current[i]??0)*100)}%`}}raf=requestAnimationFrame(loop)};raf=requestAnimationFrame(loop);return()=>cancelAnimationFrame(raf)},[level]);return jsx2("span",{dir:"ltr","aria-hidden":"true",className:"flex-1 min-w-0 h-7 flex items-center gap-0.5",children:Array.from({length:count},(_,i)=>jsx2("span",{ref:el=>{bars.current[i]=el},className:"flex-1 rounded-full bg-brand",style:{height:"10%",transition:"height 70ms linear"}},i))})}function AiComposer({value,onChange,onSend,placeholder,busy,onStop,onVoice,onAttach,disabled,className}){const ui=useUIText();const mic=useMicLevel();const area=useRef3(null);useEffect3(()=>{const el=area.current;if(!el)return;el.style.height="auto";el.style.height=`${Math.min(el.scrollHeight,120)}px`},[value]);function submit(){const text=value.trim();if(!text||busy)return;onSend(text)}if(mic.recording){return jsxs2("div",{className:cn("flex items-center gap-3 h-14 px-3 rounded-lg bg-surface border border-brand",className),children:[jsx2("button",{type:"button",onClick:mic.stop,"aria-label":ui.cancelRecording,className:"size-9 rounded-sm shrink-0 flex items-center justify-center text-muted cursor-pointer transition-colors hover:bg-red-bg hover:text-red",children:jsx2(Trash2,{size:17,"aria-hidden":"true"})}),jsx2(LiveBars,{level:mic.level}),jsx2("span",{"data-num":true,className:"text-ui font-bold text-ink2 shrink-0 tabular-nums",children:formatTime(mic.seconds)}),jsx2("button",{type:"button",onClick:()=>{const s=mic.seconds;mic.stop();onVoice?.(s)},"aria-label":ui.sendRecording,className:"size-9 rounded-sm shrink-0 flex items-center justify-center bg-brand text-white cursor-pointer transition-colors hover:bg-brand-hover",children:jsx2(ArrowUp,{size:17,strokeWidth:2.4,"aria-hidden":"true"})})]})}const canSend=value.trim().length>0;return jsxs2("div",{className:cn("flex flex-col gap-1.5",className),children:[jsxs2("div",{className:cn("flex items-end gap-2 p-2 ps-3.5 rounded-lg bg-surface border border-border","focus-within:border-brand transition-colors",disabled&&"opacity-60 pointer-events-none"),children:[jsx2("label",{htmlFor:"ai-composer",className:"sr-only",children:placeholder??ui.askShehab}),jsx2("textarea",{id:"ai-composer",ref:area,rows:1,value,disabled,placeholder:placeholder??ui.askShehab,onChange:e=>onChange(e.target.value),onKeyDown:e=>{if(e.key==="Enter"&&!e.shiftKey){e.preventDefault();submit()}},className:"flex-1 min-w-0 py-2 bg-transparent border-none outline-none resize-none text-body text-ink placeholder:text-muted2"}),jsxs2("span",{className:"flex items-center gap-0.5 shrink-0",children:[onAttach&&jsx2("button",{type:"button",onClick:onAttach,"aria-label":ui.attachFile,className:"size-9 rounded-sm flex items-center justify-center text-muted cursor-pointer transition-colors hover:bg-hover hover:text-ink",children:jsx2(Paperclip,{size:17,"aria-hidden":"true"})}),onVoice&&!canSend&&!busy&&jsx2("button",{type:"button",onClick:()=>void mic.start(),"aria-label":ui.recordVoice,className:"size-9 rounded-sm flex items-center justify-center text-muted cursor-pointer transition-colors hover:bg-hover hover:text-ink",children:jsx2(Mic,{size:17,"aria-hidden":"true"})}),jsx2("button",{type:"button",onClick:busy?onStop:submit,disabled:!busy&&!canSend,"aria-label":busy?"\u0623\u0648\u0642\u0641 \u0627\u0644\u062A\u0648\u0644\u064A\u062F":"\u0625\u0631\u0633\u0627\u0644",className:cn("size-9 rounded-sm flex items-center justify-center shrink-0 transition-colors",busy?"bg-ink2 text-surface cursor-pointer hover:bg-ink":canSend?"bg-brand text-white cursor-pointer hover:bg-brand-hover":"bg-soft text-brand-ink/40 cursor-not-allowed"),children:busy?jsx2(Square,{size:13,fill:"currentColor","aria-hidden":"true"}):jsx2(ArrowUp,{size:17,strokeWidth:2.4,"aria-hidden":"true"})})]})]}),mic.denied&&jsx2("span",{className:"text-caption text-red",children:"\u0627\u0644\u0645\u064A\u0643\u0631\u0648\u0641\u0648\u0646 \u0645\u0631\u0641\u0648\u0636 \u2014 \u0627\u0633\u0645\u062D \u0644\u0644\u0645\u0648\u0642\u0639 \u0628\u0627\u0644\u0648\u0635\u0648\u0644 \u0644\u0644\u0645\u064A\u0643\u0631\u0648\u0641\u0648\u0646 \u0645\u0646 \u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0627\u0644\u0645\u062A\u0635\u0641\u0651\u062D."})]})}import{useState as useState5}from"react";import{useState as useState3}from"react";import{ArrowRight,Check as Check2,MessageSquarePlus}from"lucide-react";import{OTHER_PREFIX}from"@qumra/jawab-ai";import{jsx as jsx3,jsxs as jsxs3}from"react/jsx-runtime";function AskCard({block,onAnswer,live=true}){const[at,setAt]=useState3(0);const[picked,setPicked]=useState3({});const[other,setOther]=useState3({});const[notes,setNotes]=useState3({});const[noting,setNoting]=useState3({});const[sent,setSent]=useState3(null);const answerable=live&&Boolean(onAnswer)&&!sent;const total=block.questions.length;const q=block.questions[at];const last=at===total-1;function toggle(question,value){setPicked(prev=>{const cur=prev[question.key]??[];if(!question.multi)return{...prev,[question.key]:[value]};return{...prev,[question.key]:cur.includes(value)?cur.filter(v=>v!==value):[...cur,value]}})}function valuesFor(question){const base=picked[question.key]??[];const typed=other[question.key]?.trim();return typed?[...base,OTHER_PREFIX+typed]:base}function labelsFor(question){return valuesFor(question).map(v=>v.startsWith(OTHER_PREFIX)?v.slice(OTHER_PREFIX.length):question.options.find(o=>o.value===v)?.label??v)}const answered=valuesFor(q).length>0;const canGo=q.required===false||answered;function submit(){const result={answers:{},values:{}};const notesOut={};for(const question of block.questions){const values=valuesFor(question);if(values.length===0)continue;result.values[question.key]=values;result.answers[question.label]=labelsFor(question).join("\u060C ");const note=notes[question.key]?.trim();if(note)notesOut[question.label]={notes:note}}if(Object.keys(notesOut).length)result.annotations=notesOut;setSent(result);onAnswer?.(block.id,result)}if(sent){return jsxs3("div",{className:"flex flex-col gap-3 p-4 rounded-card border border-line bg-bg",children:[block.questions.map(question=>jsxs3("div",{className:"flex flex-col gap-1.5",children:[jsx3("span",{className:"text-caption text-muted [text-wrap:pretty]",children:question.label}),jsx3(Picked,{labels:(sent.answers[question.label]??"").split("\u060C ").filter(Boolean),note:sent.annotations?.[question.label]?.notes})]},question.key)),jsxs3("span",{className:"flex items-center gap-1.5 text-caption font-bold text-green",children:[jsx3(Check2,{size:13,strokeWidth:2.6,"aria-hidden":"true"}),"\u0627\u062A\u0628\u0639\u062A"]})]})}return jsxs3("div",{className:"flex flex-col gap-3.5 p-4 rounded-card border border-soft2 bg-tint",children:[jsxs3("span",{className:"flex items-center justify-between gap-3",children:[q.header?jsx3("span",{className:"px-2 h-5 rounded-mark bg-soft text-brand-ink text-micro font-extrabold flex items-center",children:q.header}):jsx3("span",{}),total>1&&jsxs3("span",{className:"flex items-center gap-2 shrink-0",children:[jsx3("span",{"aria-hidden":"true",className:"flex items-center gap-1",children:block.questions.map((qq,i)=>jsx3("span",{className:cn("size-1.5 rounded-full transition-colors",i===at?"bg-brand":i<at?"bg-brand/40":"bg-border/40")},qq.key))}),jsxs3("span",{"data-num":true,className:"text-micro font-bold text-muted whitespace-nowrap",children:[at+1," \u0645\u0646 ",total]})]})]}),jsxs3("div",{className:"flex flex-col gap-2",children:[jsx3("span",{className:"text-ui font-extrabold text-ink [text-wrap:pretty]",children:q.label}),q.hint&&jsx3("span",{className:"text-caption leading-[1.7] text-muted",children:q.hint}),jsx3(ChoiceCards,{name:`${block.id}-${q.key}`,multiple:q.multi,disabled:!answerable,value:q.multi?picked[q.key]??[]:picked[q.key]?.[0]??null,onChange:v=>toggle(q,v),options:q.options.map(o=>({value:o.value,label:o.label,meta:o.description}))}),q.other!==false&&jsx3(Input,{size:"sm",value:other[q.key]??"",onChange:e=>setOther(p=>({...p,[q.key]:e.target.value})),disabled:!answerable,placeholder:q.other?.placeholder??"\u0623\u0648 \u0627\u0643\u062A\u0628 \u0625\u062C\u0627\u0628\u062A\u0643\u2026","aria-label":`\u0625\u062C\u0627\u0628\u0629 \u0623\u062E\u0631\u0649 \u0639\u0644\u0649: ${q.label}`}),noting[q.key]?jsx3(Textarea,{rows:2,value:notes[q.key]??"",onChange:e=>setNotes(p=>({...p,[q.key]:e.target.value})),disabled:!answerable,placeholder:"\u062D\u0627\u062C\u0629 \u062A\u062D\u0628\u0651 \u062A\u0642\u0648\u0644\u0647\u0627 \u0645\u0639 \u0627\u062E\u062A\u064A\u0627\u0631\u0643\u2026","aria-label":`\u0645\u0644\u0627\u062D\u0638\u0629 \u0639\u0644\u0649: ${q.label}`}):answerable&&jsxs3("button",{type:"button",onClick:()=>setNoting(p=>({...p,[q.key]:true})),className:"self-start inline-flex items-center gap-1.5 text-micro font-bold text-muted hover:text-brand cursor-pointer transition-colors",children:[jsx3(MessageSquarePlus,{size:13,"aria-hidden":"true"}),"\u0636\u064A\u0641 \u0645\u0644\u0627\u062D\u0638\u0629"]})]}),answerable&&jsxs3("span",{className:"flex items-center gap-2.5",children:[at>0&&jsx3(Button,{size:"sm",variant:"ghost",onClick:()=>setAt(i=>i-1),children:"\u0631\u062C\u0648\u0639"}),last?jsx3(Button,{size:"sm",onClick:submit,disabled:!canGo,children:block.submitLabel??"\u0627\u0628\u0639\u062A"}):jsx3(Button,{size:"sm",onClick:()=>setAt(i=>i+1),disabled:!canGo,iconEnd:jsx3(ArrowRight,{size:13,className:"rtl:rotate-180","aria-hidden":"true"}),children:"\u0627\u0644\u062A\u0627\u0644\u064A"}),!canGo&&jsx3("span",{className:"text-micro text-muted2",children:"\u0627\u062E\u062A\u0627\u0631 \u0625\u062C\u0627\u0628\u0629 \u0627\u0644\u0623\u0648\u0644"})]})]})}function Picked({labels,note}){if(labels.length===0)return jsx3("span",{className:"text-caption text-muted2",children:"\u0627\u062A\u062E\u0637\u0651\u0649"});return jsxs3("span",{className:"flex flex-col gap-1.5",children:[jsx3("span",{className:"flex flex-wrap gap-1.5",children:labels.map((l,i)=>jsxs3("span",{className:"inline-flex items-center gap-1 h-6 px-2 rounded-mark bg-soft text-brand-ink text-caption font-bold",children:[jsx3(Check2,{size:11,strokeWidth:3,"aria-hidden":"true"}),l]},i))}),note&&jsx3("span",{className:"text-micro leading-[1.7] text-muted border-s-2 border-line ps-2",children:note})]})}import{useEffect as useEffect4,useState as useState4}from"react";import{Check as Check3,Copy as Copy2,RotateCcw as RotateCcw2,Sparkles,X}from"lucide-react";import{Fragment as Fragment2,jsx as jsx4,jsxs as jsxs4}from"react/jsx-runtime";function DraftPanel({draft:source,basis,label,onInsert,onClose,insertLabel="\u0623\u062F\u0631\u0650\u062C",className}){const[attempt,setAttempt]=useState4(0);const[done,setDone]=useState4(false);const{text,streaming,start,finish}=useStreamingText({speed:12});const dynamic=typeof source==="function";useEffect4(()=>{start(dynamic?source(attempt):source)},[attempt,source]);return jsxs4("div",{className:cn("flex flex-col gap-2.5 p-3.5 rounded-card bg-bg border border-line","basis-full order-last w-full",className),children:[jsxs4("span",{className:"flex items-start justify-between gap-3",children:[jsxs4("span",{className:"flex flex-col gap-0.5 min-w-0",children:[jsxs4("span",{className:"flex items-center gap-1.5 text-caption font-extrabold text-ink",children:[jsx4(Sparkles,{size:13,className:"text-brand shrink-0","aria-hidden":"true"}),label]}),jsx4("span",{className:"text-micro leading-[1.65] text-muted",children:basis})]}),onClose&&jsx4("button",{type:"button",onClick:onClose,"aria-label":"\u0625\u063A\u0644\u0627\u0642",className:"shrink-0 size-6 rounded-mark flex items-center justify-center text-muted2 hover:bg-hover hover:text-ink cursor-pointer transition-colors",children:jsx4(X,{size:14,"aria-hidden":"true"})})]}),text===""&&streaming?jsx4(AiThinking,{label:"\u0628\u064A\u0643\u062A\u0628\u2026"}):jsxs4("span",{className:"block text-caption leading-[1.9] text-ink whitespace-pre-wrap [text-wrap:pretty]","aria-live":"polite",children:[text,streaming&&jsx4("span",{"aria-hidden":"true",className:"inline-block w-[2px] h-[1em] align-[-0.15em] ms-0.5 bg-brand animate-pulse"})]}),jsx4("span",{className:"flex items-center gap-2 flex-wrap",children:streaming?jsx4(Button,{size:"sm",variant:"outline",onClick:finish,children:"\u0627\u0639\u0631\u0636\u0647\u0627 \u0643\u0644\u0647\u0627"}):jsxs4(Fragment2,{children:[onInsert&&jsx4(Button,{size:"sm",onClick:()=>{onInsert(text);setDone(true);onClose?.()},icon:jsx4(Check3,{size:13,"aria-hidden":"true"}),children:insertLabel}),jsx4(Button,{size:"sm",variant:"outline",onClick:()=>navigator.clipboard?.writeText(text),icon:jsx4(Copy2,{size:13,"aria-hidden":"true"}),children:"\u0627\u0646\u0633\u062E"}),dynamic&&jsx4(Button,{size:"sm",variant:"ghost",onClick:()=>setAttempt(a=>a+1),icon:jsx4(RotateCcw2,{size:13,"aria-hidden":"true"}),children:"\u0627\u0643\u062A\u0628 \u063A\u064A\u0631\u0647\u0627"})]})}),!streaming&&!done&&jsx4(AiDisclaimer,{children:"\u0645\u0645\u0643\u0646 \u064A\u063A\u0644\u0637 \u2014 \u0627\u0642\u0631\u0627\u0647\u0627 \u0642\u0628\u0644 \u0645\u0627 \u062A\u0646\u0634\u0631\u0647\u0627."})]})}function AiDraft({size="sm",...panel}){const[open,setOpen]=useState4(false);if(!open){return jsx4(Button,{variant:"outline",size,onClick:()=>setOpen(true),icon:jsx4(Sparkles,{size:size==="sm"?13:15,"aria-hidden":"true"}),className:panel.className,children:panel.label})}return jsx4(DraftPanel,{...panel,onClose:()=>setOpen(false)})}import{Fragment as Fragment3,jsx as jsx5}from"react/jsx-runtime";function Markdown({md}){const chunks=md.split(/\n{2,}/);return jsx5(Fragment3,{children:chunks.map((chunk,i)=>{const lines=chunk.split("\n");const bullets=lines.every(l=>/^\s*[-•]\s+/.test(l));const numbers=lines.every(l=>/^\s*\d+[.)]\s+/.test(l));if(bullets||numbers){const items=lines.map(l=>l.replace(/^\s*(?:[-•]|\d+[.)])\s+/,""));const List=numbers?"ol":"ul";return jsx5(List,{className:numbers?"my-2 ps-5 list-decimal marker:text-muted2 flex flex-col gap-1":"my-2 ps-5 list-disc marker:text-muted2 flex flex-col gap-1",children:items.map((it,j)=>jsx5("li",{children:inline(it)},j))},i)}return jsx5("p",{className:"my-2 first:mt-0 last:mb-0 [text-wrap:pretty]",children:inline(chunk)},i)})})}var TOKEN=/(`[^`\n]+`)|(\*\*[^*\n]+\*\*)|(\*[^*\n]+\*)/g;function inline(src){const out=[];let last=0;let m;TOKEN.lastIndex=0;while((m=TOKEN.exec(src))!==null){if(m.index>last)out.push(src.slice(last,m.index));const[full]=m;const key=`${m.index}`;if(full.startsWith("`")){out.push(jsx5("code",{dir:"ltr",className:"px-1 py-px rounded-mark bg-hover text-[0.9em] font-mono text-brand-ink",children:full.slice(1,-1)},key))}else if(full.startsWith("**")){out.push(jsx5("strong",{className:"font-extrabold text-ink",children:full.slice(2,-2)},key))}else{out.push(jsx5("em",{className:"not-italic font-bold",children:full.slice(1,-1)},key))}last=m.index+full.length}if(last<src.length)out.push(src.slice(last));return out}import{Fragment as Fragment4,jsx as jsx6,jsxs as jsxs5}from"react/jsx-runtime";var TONE={good:"green",warn:"amber",bad:"red",plain:"neutral"};var NOTICE_TONE={info:"brand",warn:"amber",danger:"red"};function major(a){return a.minor/10**(a.decimals??2)}function TextBlockView({block}){if(!block.md)return null;return jsx6("div",{className:"text-body leading-[1.95] text-ink2",children:jsx6(Markdown,{md:block.md})})}function MetricBlockView({block}){return jsx6("div",{className:"grid gap-2.5 grid-cols-[repeat(auto-fit,minmax(9.5rem,1fr))]",children:block.items.map((m,i)=>jsx6(StatCard,{tone:TONE[m.tone??"plain"]??"neutral",label:m.label,value:m.amount?jsx6(Money,{value:major(m.amount),currency:isCurrency(m.amount.currency)?m.amount.currency:void 0,compact:true,trimZeros:true}):m.value,hint:m.delta!==void 0?deltaHint(m.delta,m.deltaBasis):void 0},i))})}function deltaHint(delta,basis){const up=delta>0;const flat=delta===0;return jsxs5("span",{className:cn("font-bold",flat?"text-muted2":up?"text-green":"text-red"),children:[flat?"=":up?"\u25B2":"\u25BC"," ",Math.abs(delta),"\u066A",basis?` ${basis}`:""]})}function TableBlockView({block}){const rows=block.rows.map((cells,i)=>{const row={__key:String(i)};block.cols.forEach((c,j)=>{row[c.key]=cells[j]??null});return row});const columns=block.cols.map(c=>({key:c.key,header:c.label,end:c.align==="end",cell:row=>renderCell(row[c.key],c)}));return jsxs5("span",{className:"flex flex-col gap-1.5",children:[block.caption&&jsx6("span",{className:"text-micro text-muted2",children:block.caption}),jsx6(DataTable,{columns,rows,rowKey:r=>r.__key,grid:block.cols.map(c=>c.align==="end"?"1fr":"1.6fr").join("_"),minWidth:block.cols.length*120,mobileCard:row=>jsx6("span",{className:"flex flex-col gap-1",children:block.cols.map(c=>jsxs5("span",{className:"flex items-baseline justify-between gap-3",children:[jsx6("span",{className:"text-micro text-muted2",children:c.label}),jsx6("span",{className:"text-caption text-ink",children:renderCell(row[c.key],c)})]},c.key))})})]})}function renderCell(v,col){if(v===null||v==="")return jsx6("span",{className:"text-muted2",children:"\u2014"});switch(col.format){case"money":return typeof v==="number"?jsx6(Money,{value:v,trimZeros:true}):v;case"badge":return jsx6(Badge,{size:"sm",children:v});case"number":return jsx6("span",{"data-num":true,children:v});default:return v}}function CardsBlockView({block,intents}){return jsx6("div",{className:"grid gap-2.5 grid-cols-[repeat(auto-fill,minmax(13rem,1fr))]",children:block.items.map(c=>jsx6(Card,{children:jsxs5("span",{className:"flex gap-3 items-start",children:[c.img&&jsx6("img",{src:c.img,alt:"",className:"size-12 rounded-sm object-cover bg-hover shrink-0"}),jsxs5("span",{className:"flex flex-col gap-1 min-w-0",children:[jsx6("span",{className:"text-ui font-bold text-ink truncate",children:c.title}),c.subtitle&&jsx6("span",{className:"text-micro text-muted",children:c.subtitle}),c.badge&&jsx6("span",{children:jsx6(Badge,{size:"sm",tone:"brand",children:c.badge})}),c.action&&jsx6(ActionButton,{action:c.action,intents,size:"sm"})]})]})},c.id))})}function ChartBlockView({block}){const labels=block.series.map(p=>String(p.x));const values=block.series.map(p=>p.y);const money=block.unit?.kind==="money";const fmt=v=>block.unit?.kind==="percent"?`${v}\u066A`:v.toLocaleString("ar-EG");if(block.kind==="bar"){return jsx6(BarChart,{labels,series:[{label:"",values}],formatValue:fmt})}return jsx6(TrendChart,{labels:axisLabels(labels),values,pointLabels:labels,formatValue:money?v=>v.toLocaleString("ar-EG"):fmt,height:170})}function axisLabels(all){if(all.length<=3)return all;return[all[0],all[Math.floor(all.length/2)],all[all.length-1]]}function ActionBlockView({block,intents}){const runnable=block.items.filter(a=>intents[a.intent]);if(runnable.length===0)return null;return jsx6("span",{className:"flex flex-wrap gap-2",children:runnable.map((a,i)=>jsx6(ActionButton,{action:a,intents},i))})}function ActionButton({action,intents,size="sm"}){const[asking,setAsking]=useState5(false);const run=intents[action.intent];if(!run)return null;function fire(){setAsking(false);run(action.args)}return jsxs5(Fragment4,{children:[jsx6(Button,{size,variant:action.style==="quiet"?"outline":"primary",onClick:()=>action.confirm?setAsking(true):fire(),children:action.label}),jsx6(Modal,{open:asking,title:action.label,description:action.confirm,onClose:()=>setAsking(false),size:"sm",footer:jsxs5(Fragment4,{children:[jsx6(Button,{variant:"outline",onClick:()=>setAsking(false),children:"\u0625\u0644\u063A\u0627\u0621"}),jsx6(Button,{onClick:fire,children:action.label})]})})]})}function ToolBlockView({block,intents}){const undo=block.undo;const run=undo?intents[undo.intent]:void 0;return jsx6(AiToolCall,{title:block.title,detail:block.detail,state:block.state,onUndo:run&&undo?()=>run(undo.args):void 0,undoLabel:undo?.label})}function NoticeBlockView({block}){return jsx6(Alert,{tone:NOTICE_TONE[block.tone],children:block.text})}function ChipsBlockView({block,onSend}){if(!onSend)return null;const byLabel=new Map(block.items.map(c=>[c.label,c.send]));return jsx6(AiPromptChips,{prompts:block.items.map(c=>c.label),onPick:label=>onSend(byLabel.get(label)??label)})}function BlockView({block,intents,onSend,onInsertDraft,onAnswer,live=true}){switch(block.type){case"text":return jsx6(TextBlockView,{block});case"metric":return jsx6(MetricBlockView,{block});case"table":return jsx6(TableBlockView,{block});case"cards":return jsx6(CardsBlockView,{block,intents});case"chart":return jsx6(ChartBlockView,{block});case"action":return jsx6(ActionBlockView,{block,intents});case"tool":return jsx6(ToolBlockView,{block,intents});case"notice":return jsx6(NoticeBlockView,{block});case"chips":return jsx6(ChipsBlockView,{block,onSend});case"ask":return jsx6(AskCard,{block,onAnswer,live});case"draft":return jsx6(DraftPanel,{draft:block.text,basis:block.basis,label:"\u0645\u0633\u0648\u0651\u062F\u0629",onInsert:onInsertDraft});default:return null}}import{jsx as jsx7,jsxs as jsxs6}from"react/jsx-runtime";var NO_INTENTS={};function ReplyView({reply,streaming,intents=NO_INTENTS,onSend,onInsertDraft,onAnswer,live=true,onRetry,onFeedback,className}){return jsx7(AiMessage,{role:"assistant",fill:true,streaming,sources:reply.sources,copyText:plainText(reply),onRetry,onFeedback,children:jsxs6("span",{className:cn("flex flex-col gap-3",className),children:[reply.blocks.map((block,i)=>jsx7(BlockView,{block,intents,onSend,onInsertDraft,onAnswer,live},i)),reply.disclaimer!==false&&!streaming&&jsx7(AiDisclaimer,{})]})})}function plainText(reply){const parts=[];for(const b of reply.blocks){switch(b.type){case"text":parts.push(b.md);break;case"metric":parts.push(b.items.map(m=>`${m.label}: ${m.value}`).join(" \xB7 "));break;case"table":parts.push([b.cols.map(c=>c.label).join(" "),...b.rows.map(r=>r.map(c=>c??"").join(" "))].join("\n"));break;case"cards":parts.push(b.items.map(c=>c.title).join("\n"));break;case"draft":parts.push(b.text);break;case"tool":parts.push(b.detail?`${b.title} \u2014 ${b.detail}`:b.title);break;case"notice":parts.push(b.text);break;case"ask":parts.push(b.questions.map(q=>q.label).join("\n"));break;case"chart":case"action":case"chips":break}}return parts.filter(Boolean).join("\n\n")}import{useCallback as useCallback2,useEffect as useEffect5,useRef as useRef4,useState as useState6}from"react";import{parseEvent,parseSSE,reduce}from"@qumra/jawab-ai";function useReplyStream(url,init){const[reply,setReply]=useState6(null);const[streaming,setStreaming]=useState6(false);const[error,setError]=useState6(null);const abort=useRef4(null);const lastInput=useRef4("");useEffect5(()=>()=>abort.current?.abort(),[]);const stop=useCallback2(()=>{abort.current?.abort();abort.current=null;setStreaming(false)},[]);const send=useCallback2(text=>{const question=text.trim();if(!question)return;abort.current?.abort();const ctrl=new AbortController;abort.current=ctrl;lastInput.current=question;setError(null);setReply(null);setStreaming(true);void(async()=>{try{const res=await fetch(url,{method:"POST",headers:{"content-type":"application/json",accept:"text/event-stream"},body:JSON.stringify({input:question}),signal:ctrl.signal,...init});if(!res.ok||!res.body){throw new StreamError("http_"+res.status,`\u0627\u0644\u062E\u0627\u062F\u0645 \u0631\u062C\u0651\u0639 ${res.status}`)}const decoder=new TextDecoder;const readerStream=res.body.getReader();let rest="";let state=null;for(;;){const{done,value}=await readerStream.read();if(done)break;rest+=decoder.decode(value,{stream:true});const{events,rest:tail}=parseSSE(rest);rest=tail;for(const raw of events){const ev=parseEvent(raw);if(!ev)continue;if(ev.e==="error")throw new StreamError(ev.code,ev.message);state=reduce(state,ev);setReply(state);if(ev.e==="done")break}}setStreaming(false)}catch(e){if(ctrl.signal.aborted)return;const se=e;setError({code:se.code??"unknown",message:se.message??"\u0627\u0644\u0627\u062A\u0635\u0627\u0644 \u0627\u062A\u0642\u0637\u0639.",lastInput:lastInput.current});setStreaming(false)}})()},[url,init]);return{reply,streaming,error,send,stop}}var StreamError=class extends Error{code;constructor(code,message){super(message);this.name="StreamError";this.code=code}};export{AiAvatar,AiComposer,AiDisclaimer,AiDraft,AiMessage,AiPromptChips,AiSources,AiThinking,AiToolCall,AskCard,DraftPanel,ReplyView,useMicLevel,useReplyStream,useStreamingText};
package/dist/index.d.ts CHANGED
@@ -616,46 +616,53 @@ declare const CURRENCY: {
616
616
  decimals: number;
617
617
  };
618
618
  };
619
- /** صيغة الوحدة المعروضة */
620
- /** الصيغ الصريحةكلٌّ منها يقول شكلاً بعينه */
619
+ /** The displayed unit form */
620
+ /** The explicit forms each names one exact shape */
621
621
  type Base = 'short' | 'word' | 'code' | 'sign';
622
622
  /**
623
- * صيغة الوحدة المعروضة.
623
+ * The displayed unit form.
624
624
  *
625
- * `short` اختصار عربيّ · `word` الكلمة · `code` رمز ISO · `sign` الرمز
626
- * الرسوميّ · `best` ما تختاره المكتبة.
625
+ * `short` Arabic abbreviation · `word` the word · `code` the ISO code ·
626
+ * `sign` the graphic symbol · `best` whatever the library picks.
627
627
  *
628
- * ── و`best` ليس خوارزميّة بل رأي ────────────────────────────────────────
629
- * لكل عملة سطر في الجدول يقول أيّ صيغة تُقرأ أسرع **لها هي**، لأن
630
- * الإجابة تختلف بينها:
628
+ * ## `best` is an opinion, not an algorithm
631
629
  *
632
- * · الريال السعوديّ رمزه، لأن له رمزاً رسميّاً يعرفه أهله
633
- * · الجنيه والدينار والدرهم كلمتها، فلا رمز رسميّ لها والاختصار
634
- * يُقرأ حرفين قبل أن يُفهم
635
- * · الدولار واليورو والإسترلينيّ → رموزها، فهي مقروءة عالميّاً
636
- * وأقصر من «دولار»
630
+ * Every currency has a row in the table saying which form reads fastest **for
631
+ * that currency**, because the answer differs between them:
637
632
  *
638
- * ولو حُسبت هذه بقاعدة واحدة («رمز إن وُجد») لخرج الجنيه `£` وهو
639
- * صحيح تقنيّاً ويُقرأ «إسترلينيّاً» في مصر. الرأي المكتوب لكل عملة
640
- * أصدق من قاعدة تبدو أنظف.
633
+ * - Saudi riyal its symbol, because it has an official one its readers know
634
+ * - Pound, dinar, dirham the word, because they have no official symbol
635
+ * and the abbreviation takes two characters before it means anything
636
+ * - Dollar, euro, sterling → their symbols, which read worldwide and are
637
+ * shorter than spelling them out
641
638
  *
642
- * ── وفي الإنجليزيّة ────────────────────────────────────────────────────
643
- * `best` يصير الرمز إن وُجد وإلّا رمز ISO الكلمة العربية لا معنى لها
644
- * في صفحة إنجليزيّة، و«جنيه» لا تُكتب `pound` لأن ذلك يخلطه بالإسترلينيّ.
639
+ * Derive these from one rule ("symbol if there is one") and the Egyptian
640
+ * pound comes out as `£` technically correct, and read as *sterling* in
641
+ * Egypt. A written opinion per currency is truer than a rule that looks
642
+ * tidier.
643
+ *
644
+ * ## In English
645
+ *
646
+ * `best` becomes the symbol where one exists and the ISO code otherwise. The
647
+ * Arabic word means nothing on an English page, and «جنيه» is not rendered as
648
+ * "pound" because that conflates it with sterling.
645
649
  */
646
650
  type SymbolStyle = Base | 'best';
647
651
  type Currency = keyof typeof CURRENCY;
648
652
  /**
649
- * يفحص كود عملة جاءَ من الخادم قبل تمريره إلى `Money`.
653
+ * Checks a currency code coming from the server before passing it to `Money`.
650
654
  *
651
- * ── لماذا حارس لا `as` ──────────────────────────────────────────────────
652
- * `Money.currency` اتّحاد مغلق، والخوادم تُرجع `string`. فكل مستهلك كان
653
- * يكتب `code as Currency` — وهذا **إسكات لا فحص**: كود غير معروف
654
- * (`XYZ`، أو حقل فارغ) يمرّ ثم يفشل عند القراءة من الجدول.
655
+ * ## Why a guard and not `as`
655
656
  *
656
- * والحارس يجعل المجهول `undefined`، و`Money` عندها ترجع إلى عملة المتجر
657
- * من `MoneyProvider` — وهو **السلوك الصحيح**: عرض المبلغ بعملة اللوحة
658
- * أقلّ ضرراً من عرضه برمز خاطئ أو بلا رمز.
657
+ * `Money.currency` is a closed union and servers return `string`, so every
658
+ * caller was writing `code as Currency` — which **silences rather than
659
+ * checks**: an unknown code (`XYZ`, or an empty field) passes through and
660
+ * then fails at the table lookup.
661
+ *
662
+ * The guard turns the unknown into `undefined`, and `Money` then falls back
663
+ * to the store currency from `MoneyProvider` — which is the **right**
664
+ * behaviour: showing the amount in the dashboard's currency does less damage
665
+ * than showing it with the wrong symbol, or none.
659
666
  *
660
667
  * ```tsx
661
668
  * <Money value={total} currency={isCurrency(code) ? code : undefined} />
@@ -665,19 +672,21 @@ declare function isCurrency(c?: string | null): c is Currency;
665
672
  declare function MoneyProvider({ currency, locale, children, }: {
666
673
  currency: Currency;
667
674
  /**
668
- * لغة العرض على الخادموسم BCP-47 كامل (`ar-EG` · `ar-MA` · `en`).
675
+ * Display locale on the server a full BCP-47 tag (`ar-EG` · `ar-MA` · `en`).
669
676
  *
670
- * المتصفّح يقرأها من `<html lang>` وحده، فهذه للتصيير من الخادم فقط.
671
- * أهملها ولن يُكسر شيء لكن أوّل رسم سيخرج بأرقام `ar-EG` مهما كانت
672
- * لغة الصفحة. الشرح عند `useMoneyLocale`.
677
+ * The browser reads this from `<html lang>` by itself, so this is for
678
+ * server rendering only. Leave it out and nothing breaks but the first
679
+ * paint will use `ar-EG` digits whatever the page language is. The full
680
+ * explanation is on `useMoneyLocale`.
673
681
  */
674
682
  locale?: string;
675
683
  children: ReactNode;
676
684
  }): react.JSX.Element;
677
685
  /**
678
- * عملة المتجر الحاليّة.
686
+ * The current store currency.
679
687
  *
680
- * لغير `Money`: لاحقة حقل سعر، وعنوان عمود، ونصّ «السعر بالـ…».
688
+ * For everything that is not `Money`: a price field's suffix, a column
689
+ * header, the "priced in …" line.
681
690
  */
682
691
  declare function useCurrency(): Currency;
683
692
  declare const SIZE: {
@@ -695,79 +704,86 @@ declare const TONE: {
695
704
  readonly red: "text-red";
696
705
  };
697
706
  interface MoneyProps {
698
- /** المبلغ بالوحدة الكبرىجنيهات لا قروش */
707
+ /** The amount in major units pounds, not piastres */
699
708
  value: number;
700
- /** استثناء محلّي يغلب عملة المتجرسعر بالدولار في متجر مصريّ */
709
+ /** A local override that beats the store currency a dollar price in an Egyptian store */
701
710
  currency?: Currency;
702
711
  size?: keyof typeof SIZE;
703
712
  /**
704
- * اللون.
713
+ * The colour.
705
714
  *
706
- * الافتراضيّ يرث لون ما حولهوالسالب **لا يُلوَّن تلقائياً**.
707
- * خصم في شلّال ومرتجَع في قائمة كلاهما سالب، والأول محايد والثاني
708
- * يستحقّ الأحمر. الفرق يعرفه السياق لا المكوّن.
715
+ * The default inherits from its surroundings and a negative is **not
716
+ * coloured automatically**. A deduction in a waterfall and a refund in a
717
+ * list are both negative; the first is neutral and the second earns red.
718
+ * Context knows the difference, the component does not.
709
719
  */
710
720
  tone?: keyof typeof TONE;
711
- /** يُظهر `+` على الموجبللفروق لا للأرصدة */
721
+ /** Shows `+` on positivesfor deltas, not balances */
712
722
  signed?: boolean;
713
- /** «٧١٫٤ ألف» بدل «٧١٬٤٠٠»للكروت والرسوم */
723
+ /** "71.4K" instead of "71,400"for stat cards and charts */
714
724
  compact?: boolean;
715
725
  /**
716
- * يُخفي الكسور حين تكون أصفاراً.
726
+ * Hides the decimals when they are zeros.
717
727
  *
718
- * قائمة أسعار كلّها `٫٠٠` تضيف ضجيجاً بلا معلومة. لكن **الفاتورة
719
- * لا تُخفيه**: المبلغ الرسميّ يُكتب كاملاً.
728
+ * A price list that is all `.00` adds noise and no information. But **an
729
+ * invoice does not hide them**: a formal amount is written in full.
720
730
  */
721
731
  trimZeros?: boolean;
722
- /** السعر قبل الخصميُشطب ويوضع بعد الحالي */
732
+ /** The price before the discount struck through, placed after the current one */
723
733
  was?: number;
724
734
  /**
725
- * صيغة الوحدة.
735
+ * The unit form.
726
736
  *
727
- * `short` الاختصار العربيّ (افتراضيّ) · `code` رمز ISO للفاتورة
728
- * والتصدير · `sign` الرمز الرسوميّ حيث يوجد ويحمله الخطّ.
737
+ * `short` the Arabic abbreviation (default) · `code` the ISO code for
738
+ * invoices and exports · `sign` the graphic symbol where one exists and the
739
+ * font carries it.
729
740
  *
730
- * و`sign` يرجع إلى `short` تلقائياً للعملة التي لا رمز لها في الخطّ
731
- * — والقائمة في أعلى الملفّ تقول أيّها.
741
+ * `sign` falls back to `short` automatically for any currency whose symbol
742
+ * is not in the font the table at the top of this file says which.
732
743
  *
733
- * والافتراضيّ يتبع لغة الصفحة: `short` في العربية و`code` في
734
- * الإنجليزية — «ج.م» لا تُقرأ لمن لا يقرأ العربية.
744
+ * The default follows the page language: `short` in Arabic and `code` in
745
+ * English — «ج.م» means nothing to someone who does not read Arabic.
735
746
  */
736
747
  symbol?: SymbolStyle;
737
- /** يُخفي الوحدةلعمود جدول عنوانه يحملها */
748
+ /** Hides the unit for a table column whose header already carries it */
738
749
  hideSymbol?: boolean;
739
750
  className?: string;
740
751
  }
741
752
  /**
742
- * مبلغ.
753
+ * An amount of money.
754
+ *
755
+ * ## Why a component and not a format function
743
756
  *
744
- * ── لماذا مكوّن لا دالة تنسيق ──────────────────────────────────────────
745
- * الدالة تُرجع نصّاً، والنصّ يفقد ثلاثة أشياء يحتاجها المبلغ: العزل
746
- * الاتجاهيّ حول الرقم، ومنع القطع بين الرقم وعملته، وأرقام بعرض ثابت
747
- * تصطفّ في العمود. كلّها خصائص عرض لا صياغةفمكانها عنصر.
757
+ * A function returns a string, and a string loses three things an amount
758
+ * needs: directional isolation around the number, a guarantee the number and
759
+ * its currency never break across lines, and fixed-width digits that line up
760
+ * in a column. All three are presentation, not formatting so they belong to
761
+ * an element.
748
762
  */
749
763
  declare function Money({ value, currency, size, tone, signed, compact, trimZeros, was, symbol, hideSymbol, className, }: MoneyProps): react.JSX.Element;
750
764
  /**
751
- * وحدة العملة وحدهالعنوان عمود أو لاحقة حقل.
765
+ * The currency unit on its own for a column header or a field suffix.
752
766
  *
753
- * نفس الرجوع: `sign` غير الموجود في الخطّ يعود إلى `short فما يظهر
754
- * في عنوان العمود يطابق ما يظهر في صفوفه دائماً.
767
+ * Same fallback: a `sign` the font does not carry returns `short`, so what
768
+ * appears in the column header always matches what appears in its rows.
755
769
  */
756
770
  /**
757
- * وحدة العملة وحدهالعنوان عمود أو لاحقة حقل.
771
+ * The currency unit on its own for a column header or a field suffix.
758
772
  *
759
- * ترجع `ReactNode` لا نصّاً: وحدة الريال في صيغة `word` **رسم** لأن
760
- * رمزه الرسميّ هو كلمة «ريال» نفسها. من يحتاج نصّاً خالصاً — عنوان
761
- * ملفّ مُصدَّر مثلاًيطلب `code`.
773
+ * Returns a `ReactNode` rather than a string: the riyal's unit in `word` form
774
+ * is **drawn**, because its official symbol is the word "riyal" itself.
775
+ * Anyone who needs plain text the header of an exported file, say — should
776
+ * ask for `code`.
762
777
  */
763
778
  declare function currencySymbol(c?: Currency, style?: SymbolStyle, locale?: string): ReactNode;
764
- /** الاسم المنطوقلقارئ الشاشة وللقوائم المنسدلة */
779
+ /** The spoken name for screen readers and select menus */
765
780
  declare const currencyName: (c?: Currency) => string;
766
781
  /**
767
- * وحدة العملة تابعةً للغة الصفحةلعنوان عمود يحمل عملة صفوفه.
782
+ * The currency unit following the page language for a column header that
783
+ * carries its rows' currency.
768
784
  *
769
- * `currencySymbol` الساكنة لا تعرف اللغة، فعنوان العمود كان يظلّ «ج.م»
770
- * فوق صفوف مكتوبة `EGP`.
785
+ * The static `currencySymbol` does not know the language, so a column header
786
+ * stayed as «ج.م» above rows written `EGP`.
771
787
  */
772
788
  declare function useCurrencySymbol(c?: Currency, style?: SymbolStyle): ReactNode;
773
789
 
@@ -619,75 +619,81 @@ interface NotificationContext {
619
619
  onAction?: (n: Notification) => void;
620
620
  }
621
621
  interface NotificationProviderProps {
622
- /** سجلّ الأنواعالافتراضي سجلّ قمرة */
622
+ /** The kind registry Qumra's registry by default */
623
623
  kinds?: NotificationKindRegistry;
624
- /** لغة الوقت النسبيالافتراضي `ar` */
624
+ /** Locale for relative time — `ar` by default */
625
625
  locale?: string;
626
626
  /**
627
- * دالة الترجمة من نظام مشروعكلقوالب عناوين الأنواع.
627
+ * The translate function from your own i18n system for kind title templates.
628
628
  *
629
- * من غيرها القوالب بتتجاهل ونصّ السيرفر بيتعرض. المكتبة مابتترجمش
630
- * بنفسها: بتنادي `t(key, vars)` وبس، فالنصوص بتفضل في كتالوجك
631
- * والجمع شغل نظامك.
629
+ * Without it templates are ignored and the server's text is shown. The
630
+ * library does not translate on its own: it calls `t(key, vars)` and
631
+ * nothing more, so strings stay in your catalogue and pluralisation stays
632
+ * your system's job.
632
633
  */
633
634
  t?: NotificationTranslate;
634
635
  /**
635
- * التنقّل لوجهة الإشعار.
636
+ * Navigation to a notification's destination.
636
637
  *
637
- * المكتبة مابتنقّلش بنفسها لأن الرسالة بتحمل `href` مش دالة.
638
- * خدها من `n.action.href` ووصّلها لراوترك.
638
+ * The library does not navigate on its own, because a message carries an
639
+ * `href` and not a callback. Take it from `n.action.href` and hand it to
640
+ * your router.
639
641
  */
640
642
  onAction?: (n: Notification) => void;
641
643
  children: ReactNode;
642
644
  }
643
- /** بيوزّع السجلّ واللغة والتنقّل على كل الأسطحمرّة في الجذر */
645
+ /** Distributes registry, locale and navigation to every surface once, at the root */
644
646
  declare function NotificationProvider({ kinds, locale, t, onAction, children, }: NotificationProviderProps): react.JSX.Element;
645
647
  declare const useNotificationContext: () => NotificationContext;
646
648
  interface NotificationBellProps {
647
649
  unread: number;
648
650
  /**
649
- * منهم كام محتاج إجراءبيغيّر **شكل** العدّاد مش رقمه.
651
+ * How many of them need action this changes the badge's **shape**, not its
652
+ * number.
650
653
  *
651
- * نقطة حمرا مابتروحش أبداً بترجّع نفس الضغط اللي بيخلّي التاجر
652
- * يطفّي الإشعارات. فالعدّاد بيفضل رقم غير المقروء، والحرج بيتعلّم
653
- * بحلقة حواليه علامة تانية على نفس الرقم مش رقم تاني.
654
+ * A red dot that never goes away invites exactly the press that makes a
655
+ * merchant turn notifications off. So the badge stays the unread count, and
656
+ * critical items are marked with a ring around it a second signal on the
657
+ * same number, not a second number.
654
658
  */
655
659
  needsAction?: number;
656
660
  onClick: () => void;
657
- /** بيوصل لـ`aria-expanded` — الجرس اللي بيفتح لوح لازم يقولها */
661
+ /** Reaches `aria-expanded` — a bell that opens a panel has to say so */
658
662
  expanded?: boolean;
659
663
  label?: string;
660
- /** «٣ غير مقروءة» الرقم في `aria-label` مش في الشكل وحده */
664
+ /** "3 unread"the number belongs in `aria-label`, not only in the shape */
661
665
  unreadLabel?: (n: number) => string;
662
666
  className?: string;
663
667
  }
664
668
  /**
665
- * زرّ الجرس بعدّاد غير المقروء.
669
+ * The bell button with its unread badge.
666
670
  *
667
- * العدّاد أحمر ومحاط بحلقة من لون الخلفيةمن غيرها الرقم بيتلزق في
668
- * أي أيقونة وراه وبيتقرا جزء منها. والرقم في `aria-label` كمان: قارئ
669
- * الشاشة مابيشوفش الشارة، وجرس بيقول «الإشعارات» وبس بيخلّي اللي بيسمع
670
- * يفتح الصندوق كل مرة عشان يعرف فيه جديد ولا لأ.
671
+ * The badge is red and ringed in the background colour without the ring the
672
+ * number collides with whatever icon is behind it and half of it becomes
673
+ * unreadable. The number is in `aria-label` too: a screen reader never sees
674
+ * the badge, and a bell that only says "notifications" makes a listener open
675
+ * the inbox every time just to find out whether anything is there.
671
676
  */
672
677
  declare function NotificationBell({ unread, needsAction, onClick, expanded, label, unreadLabel, className, }: NotificationBellProps): react.JSX.Element;
673
678
  interface NotificationRowProps {
674
679
  item: Notification;
675
- /** بيتنادى بعد فتح الوجهةعلّمه مقروء هنا */
680
+ /** Called after the destination opens mark it read here */
676
681
  onOpen?: (n: Notification) => void;
677
682
  onDismiss?: (id: string) => void;
678
683
  dismissLabel?: string;
679
684
  unreadLabel?: string;
680
685
  }
681
686
  /**
682
- * سطر إشعار واحد.
687
+ * A single notification row.
683
688
  *
684
- * غير المقروء بيتعلّم بحاجتين: نقطة وخطّ أتقل. اللون وحده مابيكفيش
685
- * واحد من كل اتني عشر راجل مابيفرّقش بين درجتين رماديين صغيّرين،
686
- * والوزن بيتقرا بالعين مهما كان.
689
+ * Unread is marked twice: with a dot and with heavier text. Colour alone is
690
+ * not enough one man in twelve cannot separate two close greys, and weight
691
+ * reads regardless.
687
692
  *
688
- * والسطر **مش زرار كله**. الصندوق فيه سطور بتفتح وسطور بتتقري وبس،
689
- * ولو السطر كله قابل للضغط اللي عايز يقرا بيدوس بالغلط ويلاقي نفسه في
690
- * صفحة تانية. الإجراء زرار له اسم.
693
+ * And the row is **not one big button**. An inbox holds rows that open
694
+ * something and rows that are only read, and if the whole row is pressable
695
+ * someone who meant to read lands on another page instead. The action is a
696
+ * button with a name.
691
697
  */
692
698
  declare function NotificationRow({ item, onOpen, onDismiss, dismissLabel, unreadLabel, }: NotificationRowProps): react.JSX.Element;
693
699
  interface NotificationListProps {
@@ -698,24 +704,25 @@ interface NotificationListProps {
698
704
  emptyDescription?: ReactNode;
699
705
  className?: string;
700
706
  }
701
- /** القايمة نفسهانفسها في اللوح المنسدل وفي الدرج الجانبي بالظبط */
707
+ /** The list itself identical in the dropdown panel and in the side drawer */
702
708
  declare function NotificationList({ items, onOpen, onDismiss, emptyTitle, emptyDescription, className, }: NotificationListProps): react.JSX.Element;
703
709
  interface NotificationPanelProps extends NotificationListProps {
704
710
  title?: ReactNode;
705
711
  unread?: number;
706
712
  /**
707
- * غير المقروء اللي «تعليم الكل» مش بيمسحه.
713
+ * The unread that "mark all read" does not clear.
708
714
  *
709
- * لازم يتعرض: العدّاد بينزل من ١٢ لـ٢ وبيقف، واللي مايعرفش ليه
710
- * بيدوس تاني ومافيش، فبيتقرا عطل. السطر بيقول السبب مرّة واحدة.
715
+ * This has to be shown: the badge drops from 12 to 2 and stops, and anyone
716
+ * who does not know why presses again and nothing happens, so it reads as a
717
+ * bug. One line says why, once.
711
718
  */
712
719
  needsAction?: number;
713
720
  onMarkAllRead?: () => void;
714
721
  markAllLabel?: string;
715
- /** تذييل «عرض الكل»من غيره الصندوق بيبقى نهاية الطريق */
722
+ /** A "see all" footer without it the inbox is the end of the road */
716
723
  onViewAll?: () => void;
717
724
  viewAllLabel?: string;
718
- /** زرّ كتم الصوت في الترويسةمرّره من `useNotificationSound` */
725
+ /** The mute button in the header pass it from `useNotificationSound` */
719
726
  sound?: {
720
727
  muted: boolean;
721
728
  setMuted: (m: boolean) => void;
@@ -723,54 +730,56 @@ interface NotificationPanelProps extends NotificationListProps {
723
730
  };
724
731
  }
725
732
  /**
726
- * اللوح المنسدلصندوق الإشعارات جنب الجرس.
733
+ * The dropdown panel the notification inbox beside the bell.
727
734
  *
728
- * ارتفاع القايمة محدود و**بيتمرّر جوّه**: اللوح اللي بيطول بطول القايمة
729
- * بيوصل لآخر الشاشة وبيقص آخر إشعار من غير ما يبان إنه مقصوص.
735
+ * The list has a bounded height and **scrolls inside itself**: a panel that
736
+ * grows with its list reaches the bottom of the screen and cuts the last
737
+ * notification off with nothing to show it was cut.
730
738
  *
731
- * و«تعليم الكل كمقروء» بيختفي لما يبقى مفيش غير مقروءزرار بيعمل لا
732
- * حاجة موجود دايماً بيعلّم المستخدم إن الأزرار هنا مش بترد.
739
+ * And "mark all read" disappears once nothing is unreada button that does
740
+ * nothing, always present, teaches the user that buttons here do not respond.
733
741
  */
734
742
  declare function NotificationPanel({ items, title, unread, needsAction, onMarkAllRead, markAllLabel, onViewAll, viewAllLabel, sound, onOpen, onDismiss, emptyTitle, emptyDescription, className, }: NotificationPanelProps): react.JSX.Element;
735
743
  interface SoundToggleProps {
736
744
  muted: boolean;
737
745
  setMuted: (m: boolean) => void;
738
- /** الصوت اتفكّقبلها الزرار بيقول إنه مقفول من المتصفّح مش من المستخدم */
746
+ /** Audio is unlocked before that the button says the browser blocked it, not the user */
739
747
  ready: boolean;
740
748
  className?: string;
741
749
  }
742
750
  /**
743
- * كتم صوت الإشعارات.
751
+ * Muting notification sound.
744
752
  *
745
- * الحالة التالتة مهمة: **مش مكتوم بس المتصفّح لسه قافل**. الزرار
746
- * بيقولها بدل ما يدّعي إن الصوت شغّال والمستخدم اللي مستني رنّة
747
- * مش جاية يستاهل يعرف السبب.
753
+ * The third state matters: **not muted, but the browser has not unlocked
754
+ * audio yet**. The button says so instead of pretending sound is on — someone
755
+ * waiting for a chime that never comes deserves to know why.
748
756
  */
749
757
  declare function SoundToggle({ muted, setMuted, ready, className }: SoundToggleProps): react.JSX.Element;
750
758
  interface NotificationMenuProps extends Omit<NotificationPanelProps, 'unread'> {
751
759
  unread: number;
752
760
  bellLabel?: string;
753
761
  /**
754
- * الحافة اللي اللوح بيتحاذى عليها مع الجرسالافتراضي `end`.
762
+ * Which edge the panel aligns to against the bell — `end` by default.
755
763
  *
756
- * الجرس في الشريط العلوي بيبقى ناحية `end فاللوح بيتمدّد ناحية
757
- * `start` وبيفضل جوّه الشاشة. لو الجرس اتحرّك لازم ده يتقلب.
764
+ * The bell sits at the `end` of the top bar, so the panel extends toward
765
+ * `start` and stays on screen. Move the bell and this has to flip.
758
766
  */
759
767
  align?: 'start' | 'end';
760
768
  /**
761
- * فوق الجرس ولا تحتهالافتراضي `bottom`.
769
+ * Above the bell or below it — `bottom` by default.
762
770
  *
763
- * الشريط السفلي على الموبايل محتاج `top`: لوح بينزل من جرس على آخر
764
- * الشاشة بيطلع كله برّه.
771
+ * A bottom bar on mobile needs `top`: a panel dropping from a bell at the
772
+ * bottom of the screen lands entirely outside it.
765
773
  */
766
774
  side?: 'top' | 'bottom';
767
775
  className?: string;
768
776
  }
769
777
  /**
770
- * الجرس واللوح كوحدةده اللي الشريط العلوي بيحطّه.
778
+ * The bell and the panel as one unit this is what a top bar mounts.
771
779
  *
772
- * الإغلاق بالضغط برّه وبـEscape. والاتنين لازمين: الضغط برّه هو
773
- * المتوقّع بالفأرة، وEscape هو المخرج الوحيد للي ماسك كيبورد.
780
+ * It closes on an outside click and on Escape. Both are needed: the outside
781
+ * click is what a mouse expects, and Escape is the only way out for anyone on
782
+ * a keyboard.
774
783
  */
775
784
  declare function NotificationMenu({ unread, bellLabel, align, side, className, onViewAll, onOpen, ...panel }: NotificationMenuProps): react.JSX.Element;
776
785
  interface NotificationDrawerProps extends NotificationListProps {
@@ -778,29 +787,32 @@ interface NotificationDrawerProps extends NotificationListProps {
778
787
  onClose: () => void;
779
788
  title?: ReactNode;
780
789
  unread?: number;
781
- /** زي `NotificationPanel` — الشرح لازم يبان على الموبايل كمان */
790
+ /** Same as `NotificationPanel` — the explanation has to show on mobile too */
782
791
  needsAction?: number;
783
792
  onMarkAllRead?: () => void;
784
793
  markAllLabel?: string;
785
794
  closeLabel?: string;
786
795
  }
787
796
  /**
788
- * الدرج الجانبينفس الصندوق لكن من حافة الشاشة.
797
+ * The side drawer the same inbox, from the edge of the screen.
789
798
  *
790
- * ليه التنين مش واحد: اللوح المنسدل معلّق في الجرس، وعلى شاشة ٣٦٠
791
- * بكسل ده يعني لوح بيغطّي الشاشة وطرفه بره الحافة. والدرج بيفتح من
792
- * `inline-end` — الشمال في العربي واليمين في الإنجليزي، لوحده.
799
+ * Why two and not one: the dropdown panel is anchored to the bell, and on a
800
+ * 360px screen that means a panel covering the whole screen with one edge
801
+ * hanging off it. The drawer opens from `inline-end` — the left in Arabic and
802
+ * the right in English, by itself.
793
803
  *
794
- * والقايمة جوّاه **هي هي** بلا فرق حرف: اللي شاف إشعاراً على الديسكتوب
795
- * وبصّ له على الموبايل لازم يشوف نفس السطر، وإلا بيفتكرهم اتنين.
804
+ * And the list inside is **identical**, down to the character: someone who
805
+ * saw a notification on desktop and looks for it on mobile has to see the
806
+ * same row, or they will think there are two.
796
807
  */
797
808
  declare function NotificationDrawer({ open, onClose, items, title, unread, needsAction, onMarkAllRead, markAllLabel, closeLabel, onOpen, onDismiss, emptyTitle, emptyDescription, }: NotificationDrawerProps): react.JSX.Element;
798
809
  /**
799
- * ركن الشاشة اللي الطاير بينزل فيه.
810
+ * The screen corner a toast drops into.
800
811
  *
801
- * `start`/`end` منطقية مش يمين وشمال: `end` يعني الشمال في العربي
802
- * واليمين في الإنجليزي. الجرس بيقلب مكانه مع اللغة، والإشعار لازم
803
- * يقلب معاهوإلا بيجي من ناحية وبيتخزّن في ناحية تانية.
812
+ * `start`/`end` are logical, not left and right: `end` means the left in
813
+ * Arabic and the right in English. The bell moves with the language, and the
814
+ * notification has to move with it otherwise it arrives from one side and
815
+ * is filed on the other.
804
816
  */
805
817
  type NotificationPlacement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
806
818
  interface NotificationToastHostProps {
@@ -808,25 +820,26 @@ interface NotificationToastHostProps {
808
820
  onClose: (id: string) => void;
809
821
  onOpen?: (n: Notification) => void;
810
822
  /**
811
- * الركنالافتراضي `top-end` عشان يقع جنب الجرس.
823
+ * The corner — `top-end` by default, so it lands beside the bell.
812
824
  *
813
- * وخلّيه بعيد عن ركن `ToastHost` (`bottom-start` افتراضاً): التوست
814
- * بيقول «الفعل اللي عملته حصل» والإشعار بيقول «حاجة جت من برّه»،
815
- * ولو الاتنين طلعوا من نفس المكان بيتقروا طابور واحد.
825
+ * Keep it away from `ToastHost`'s corner (`bottom-start` by default): a
826
+ * toast says "the thing you did happened" and a notification says
827
+ * "something arrived from outside", and if both come from the same place
828
+ * they read as one queue.
816
829
  */
817
830
  placement?: NotificationPlacement;
818
- /** عرض العمود فوق `sm` — تحتها بياخد العرض كله */
831
+ /** Column width above `sm` — below it takes the full width */
819
832
  width?: string;
820
833
  closeLabel?: string;
821
834
  regionLabel?: string;
822
835
  className?: string;
823
836
  }
824
837
  /**
825
- * الإشعار الطايرالكرت اللي بينزل لما حاجة توصل.
838
+ * The toast notification the card that drops in when something arrives.
826
839
  *
827
- * `aria-live="polite"` مش `assertive`: الأخير بيقطع قارئ الشاشة في
828
- * نصّ الجملة. الإشعار مقاطعة أصلاً، مايستاهلش يقاطع مرتين اللي بيقرا
829
- * هيسمعه لما يخلّص السطر.
840
+ * `aria-live="polite"`, not `assertive`: the latter cuts a screen reader off
841
+ * mid-sentence. A notification is already an interruption; it does not earn a
842
+ * second one whoever is listening will hear it once the line finishes.
830
843
  */
831
844
  declare function NotificationToastHost({ items, onClose, onOpen, placement, width, closeLabel, regionLabel, className, }: NotificationToastHostProps): react.JSX.Element | null;
832
845
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qumra/fanar",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "description": "Qumra's Arabic-first component library and design tokens. Tailwind 4, RSC-safe.",
6
6
  "license": "UNLICENSED",
@@ -13,56 +13,41 @@
13
13
  "arabic"
14
14
  ],
15
15
  "exports": {
16
- ".": "./src/index.ts",
17
- "./lib": "./src/lib.ts",
18
- "./tokens": "./src/tokens/tokens.ts",
19
- "./orb": "./src/orb/index.ts",
20
- "./editor": "./src/editor/index.ts",
21
- "./notifications": "./src/notifications/index.ts",
22
- "./ai": "./src/ai/index.ts",
23
- "./editor.css": "./src/editor/editor.css",
24
- "./tokens.css": "./src/tokens/tokens.css",
25
- "./tokens.json": "./src/tokens/tokens.json",
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./lib": {
21
+ "types": "./dist/lib.d.ts",
22
+ "import": "./dist/lib.js"
23
+ },
24
+ "./tokens": {
25
+ "types": "./dist/tokens.d.ts",
26
+ "import": "./dist/tokens.js"
27
+ },
28
+ "./orb": {
29
+ "types": "./dist/orb.d.ts",
30
+ "import": "./dist/orb.js"
31
+ },
32
+ "./editor": {
33
+ "types": "./dist/editor.d.ts",
34
+ "import": "./dist/editor.js"
35
+ },
36
+ "./notifications": {
37
+ "types": "./dist/notifications.d.ts",
38
+ "import": "./dist/notifications.js"
39
+ },
40
+ "./ai": {
41
+ "types": "./dist/ai.d.ts",
42
+ "import": "./dist/ai.js"
43
+ },
44
+ "./editor.css": "./dist/editor.css",
45
+ "./tokens.css": "./dist/tokens.css",
46
+ "./tokens.json": "./dist/tokens.json",
26
47
  "./package.json": "./package.json"
27
48
  },
28
49
  "publishConfig": {
29
- "access": "public",
30
- "main": "./dist/index.js",
31
- "types": "./dist/index.d.ts",
32
- "exports": {
33
- ".": {
34
- "types": "./dist/index.d.ts",
35
- "import": "./dist/index.js"
36
- },
37
- "./lib": {
38
- "types": "./dist/lib.d.ts",
39
- "import": "./dist/lib.js"
40
- },
41
- "./tokens": {
42
- "types": "./dist/tokens.d.ts",
43
- "import": "./dist/tokens.js"
44
- },
45
- "./orb": {
46
- "types": "./dist/orb.d.ts",
47
- "import": "./dist/orb.js"
48
- },
49
- "./editor": {
50
- "types": "./dist/editor.d.ts",
51
- "import": "./dist/editor.js"
52
- },
53
- "./notifications": {
54
- "types": "./dist/notifications.d.ts",
55
- "import": "./dist/notifications.js"
56
- },
57
- "./ai": {
58
- "types": "./dist/ai.d.ts",
59
- "import": "./dist/ai.js"
60
- },
61
- "./editor.css": "./dist/editor.css",
62
- "./tokens.css": "./dist/tokens.css",
63
- "./tokens.json": "./dist/tokens.json",
64
- "./package.json": "./package.json"
65
- }
50
+ "access": "public"
66
51
  },
67
52
  "files": [
68
53
  "dist"
@@ -70,29 +55,6 @@
70
55
  "sideEffects": [
71
56
  "*.css"
72
57
  ],
73
- "scripts": {
74
- "verify": "node scripts/verify.mjs",
75
- "typecheck": "tsc --noEmit",
76
- "lint": "eslint .",
77
- "format": "prettier --write .",
78
- "format:check": "prettier --check .",
79
- "check-tsconfig": "node scripts/check-tsconfig.mjs",
80
- "check-engines": "node scripts/check-engines.mjs",
81
- "check-tokens": "node scripts/check.mjs",
82
- "check-contrast": "node scripts/check-contrast.mjs",
83
- "test": "vitest run",
84
- "check-publish": "node scripts/check-publish.mjs",
85
- "test:watch": "vitest",
86
- "generate": "node scripts/generate.mjs",
87
- "generate:ts": "node scripts/generate-ts.mjs",
88
- "extract": "node scripts/extract.mjs",
89
- "build": "node scripts/generate.mjs && node scripts/generate-ts.mjs && node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsup",
90
- "size": "size-limit",
91
- "dep-order": "node scripts/dep-order.mjs",
92
- "changeset": "changeset",
93
- "changeset:version": "changeset version",
94
- "changeset:publish": "changeset publish"
95
- },
96
58
  "devDependencies": {
97
59
  "@changesets/cli": "^3.0.1",
98
60
  "@eslint/js": "^10.0.1",
@@ -114,7 +76,7 @@
114
76
  "eslint-plugin-react-hooks": "^7.1.1",
115
77
  "globals": "^17.11.0",
116
78
  "happy-dom": "^20.11.6",
117
- "jawab-ai": "link:../jawab-ai",
79
+ "@qumra/jawab-ai": "link:../jawab-ai",
118
80
  "prettier": "^3.9.6",
119
81
  "react": "^19.2.8",
120
82
  "react-dom": "^19.2.8",
@@ -162,14 +124,14 @@
162
124
  "react": "^19.0.0",
163
125
  "react-dom": "^19.0.0",
164
126
  "tailwindcss": "^4.0.0",
165
- "jawab-ai": "^0.1.0",
127
+ "@qumra/jawab-ai": "^0.1.0",
166
128
  "three": ">=0.171.0"
167
129
  },
168
130
  "peerDependenciesMeta": {
169
131
  "@google/model-viewer": {
170
132
  "optional": true
171
133
  },
172
- "jawab-ai": {
134
+ "@qumra/jawab-ai": {
173
135
  "optional": true
174
136
  },
175
137
  "tailwindcss": {
@@ -179,9 +141,33 @@
179
141
  "optional": true
180
142
  }
181
143
  },
182
- "packageManager": "pnpm@11.22.0",
183
144
  "engines": {
184
145
  "node": ">=22.22.2",
185
146
  "pnpm": ">=11.0.0"
186
- }
187
- }
147
+ },
148
+ "scripts": {
149
+ "verify": "node scripts/verify.mjs",
150
+ "typecheck": "tsc --noEmit",
151
+ "lint": "eslint .",
152
+ "format": "prettier --write .",
153
+ "format:check": "prettier --check .",
154
+ "check-tsconfig": "node scripts/check-tsconfig.mjs",
155
+ "check-engines": "node scripts/check-engines.mjs",
156
+ "check-tokens": "node scripts/check.mjs",
157
+ "check-contrast": "node scripts/check-contrast.mjs",
158
+ "test": "vitest run",
159
+ "check-publish": "node scripts/check-publish.mjs",
160
+ "test:watch": "vitest",
161
+ "generate": "node scripts/generate.mjs",
162
+ "generate:ts": "node scripts/generate-ts.mjs",
163
+ "extract": "node scripts/extract.mjs",
164
+ "build": "node scripts/generate.mjs && node scripts/generate-ts.mjs && node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsup",
165
+ "size": "size-limit",
166
+ "dep-order": "node scripts/dep-order.mjs",
167
+ "changeset": "changeset",
168
+ "changeset:version": "changeset version",
169
+ "changeset:publish": "changeset publish"
170
+ },
171
+ "main": "./dist/index.js",
172
+ "types": "./dist/index.d.ts"
173
+ }