A calm conversation companion.
Darya is a thoughtful, bilingual (Persian/English) conversation companion that listens and responds with care. Pick a language, choose your theme, and start a conversation. There is no sign-up or data collection, and conversation processing stays local in your browser without external APIs.
Think of Darya as a quiet, attentive friend who remembers what you have said during your conversation, notices how you are feeling, and asks gentle follow-up questions. It is not a substitute for professional support. It is a calm space to think out loud.
- Bilingual from the start. Choose Persian or English. The language stays with you for the whole conversation. Iranian Persian responses consistently use
یandک; a final display guard converts Arabic look-alike code points before any bot message is shown. - Live-edge chat behavior. While you are at the bottom, every new user and Darya message remains visible automatically. The jump control appears only after you deliberately scroll upward, preserves that reading position as replies arrive, and re-engages automatic following when tapped.
- Two visual themes. Ocean (deep, calm blues) or Beach (bright sand and surf), each with a soft frosted-glass interface. Your preference is remembered between visits.
- Calm ambient soundscapes. Optional theme-matched background audio (beach or ocean sound loops). Always silent on arrival: you turn it on with the sound toggle, and it is never remembered between visits. It cross-fades smoothly when you switch themes.
- Real conversation flow. Darya recognizes a wide range of topics (family, work, sleep, anxiety, joy, grief, and more), asks thoughtful questions, and keeps track of what matters to you. The relationship is collaborative: the user is not Darya's boss, and Darya does not position herself as the user's authority.
- Global informal-language awareness. Darya understands a curated range of Iranian slang, Afghan Persian expressions, global internet language, and regional English from Britain and Ireland, Australia, Nigeria, South Africa, and Singapore. Ask what a term means or use it naturally in context. Darya explains or routes it without caricaturing the speaker, and stays honest that slang changes quickly.
- Support across life stages. Child online-safety and school-bullying disclosures get trusted-adult steps; teenagers get practical boundaries for peer pressure and private-image coercion; adults get support for financial, work, parenting, and caregiver strain; and older adults get retirement, technology-learning, and scam-safety guidance.
- Sensitive subjects without shame or operational risk. Darya can discuss sex work, pornography literacy, compulsive sexual behavior, substance and behavioral addiction, exploitation, consent, and policy debates. Personal coercion gets safety-first support; requests to procure, advertise, distribute, conceal, or evade enforcement are refused.
- Iran-aware legal caution. For conduct that may create legal exposure in Iran, Darya avoids guessing at punishments or declaring a specific situation legal. She recommends pausing the activity, avoiding identifying details, checking current official information, and speaking confidentially with a licensed Iranian lawyer.
- Software engineering and defensive security. The offline shelf now reaches beyond beginner syntax into architecture, databases, networking, concurrency, profiling, observability, accessibility, offline-first PWAs, refactoring, open-source work, secure development, threat modeling, incident response, and cybersecurity careers. Ethical-hacking guidance requires explicit authorization and written scope; credential theft, unauthorized access, malware, destructive traffic, persistence, and detection evasion are refused.
- Work that starts from real constraints. Darya can help someone with no experience compare quick-entry work, guarding, retail, warehouse work, tutoring, trade apprenticeships, local services, and tiny service businesses without selling a get-rich fantasy. It covers guard duties and shift safety, job-scam signals, irregular income, unemployment, study-versus-work decisions, and Iran's inflation pressure. Product prices are never hardcoded; users are told to compare current quotes.
- Migration and compulsory-service realism. Migration guidance compares staying, moving inside the country, and lawful international routes across different budgets, including the honest fact that international migration is not literally free. It rejects guaranteed-visa claims, smugglers, fabricated stories, and false documents. Iranian military-service answers explain that duration varies by assignment and current rules, treat the career interruption as real, compare other conscription systems, and require current official information for an individual case.
- Conflict history without slogans or false equivalence. Darya explains how to separate structural causes, public claims, triggers, conduct, civilian harm, and outcomes. Coverage includes ancient Persian and Greek wars, the Arab conquest, Mongol, Safavid-Ottoman and Russo-Persian conflicts, Iran in both world wars, the Iran-Iraq War, the Arab-Israeli wars, Gulf wars, Afghanistan, Lebanon, and the Crusades. Giving each actor's reasons does not make every claim true or every responsibility equal.
- Family and appearance without shallow reassurance. Repeated family control and humiliation, dependency-aware boundaries, toxic friendships, appearance bias, facial comparison, and body-dysmorphic warning signs get distinct responses. Darya does not diagnose from one message, order a dependent person to leave without safety planning, or validate a hierarchy in which a prettier face makes someone more valuable.
- Travel from Iran to the planets. The offline shelf covers cultural sites across Tehran, Isfahan, Shiraz, Yazd, Kerman, northern and western Iran, Khuzestan, Tabriz, Mashhad, the Persian Gulf coast and islands, major world-heritage styles, responsible itinerary planning, and clearly labeled hypothetical tourism on the Moon, Mars, and elsewhere in the solar system. Live safety, visas, borders, transport, weather, and opening hours always require current official sources.
- Cultural humor with context. Darya explains why jokes do not always translate and offers clean Persian, dry, travel, and cross-cultural humor without turning a nationality, ethnicity, religion, gender, or class into the punchline.
- Session memory of who you are. Tell Darya your name, age, or where you live and she remembers it for the rest of the conversation: ask "what is my name?", "چند سالمه؟", or "where do I live?" later and she answers from what you said, honestly, instead of pretending to forget or claiming to know things you never told her. Children get an age-appropriate reply that warmly points to a trusted adult.
- Guided exercises, step by step. Ask for a breathing exercise, a grounding exercise, a body scan, or a thought record ("breathing exercise", "تمرین تنفس", "ثبت فکر") and Darya walks you through it one step per turn with tappable yes/no chips under her replies.
- Session mood check-ins. Ask to log your mood ("mood check", "حالم رو ثبت کن"), tap a rating on the 1..10 scale chips, and later ask how you have been feeling: Darya reads back the recorded arc and its direction.
- Emotional awareness. An optional breathing button softly fades into the chat header when you ask for breathing help or describe present, first-person panic, anxiety, overwhelm, or anger. It stays out of the way for greetings, general information, third-person stories, resolved feelings, urgent physical symptoms, and safety-critical disclosures, where the main reply must remain the clear next step.
- Honest limits. If you ask her to do something she cannot do from the chat ("turn on ambient sound", "switch the theme", "پوسته رو عوض کن"), she says so plainly and points you to the right button instead of pretending she did it.
- The story of ELIZA. Ask who made her, what ELIZA was, or about MIT and Darya explains the project's real origin story with substance rather than a canned deflection.
- Learning support. "How can I learn English?" and its Persian equivalent get a structured, practical method (comprehensible input, spaced repetition, daily routine), not a hand-off.
- Time and calendar answers. Ask for the current time or date and Darya replies in your language and calendar, with both Jalali and Gregorian dates for Persian.
- Quick facts and math. Darya answers arithmetic with a real expression evaluator, so multi-operator input gets the mathematically correct result with proper precedence and parentheses: "2+2*3" is 8, "(2+3)*4" is 20, "-(3+4)*2" is -14, in ASCII or Persian digits. Percentages, square roots (including "square root of 144", "جذر ۱۶", and an honest "no real square root" for negatives), random numbers, primality checks, and coin flips ("flip a coin", "شیر یا خط") all answer in your language. Asked for the capital of a specific country, she answers in one sentence ("The capital of France is Paris", «پایتخت ژاپن توکیو است») instead of reciting a list. And live data she cannot know offline (today's prices, weather, news, scores) gets an honest limitation up front instead of a timeless lecture. Everyday word problems are solved too: «اگه ۵ تا سیب داشته باشم و ۲ تا بدم، چند تا میمونه؟» and "if I have 5 apples and give away 2" get real arithmetic instead of a source pointer.
- A real calculator under the hood. Beyond expressions and percentages, Darya computes factorials («فاکتوریل ۵», "6!"), GCD and LCM («ب.م.م ۱۲ و ۱۸», "lcm of 4 and 6"), averages of listed numbers, remainders ("17 mod 5", «باقیمانده ۱۷ بر ۵»), even/odd and divisibility checks, percent relations ("15 is what percent of 60?") and percent change («از ۵۰ به ۶۵ چند درصد رشد؟»), cube roots, absolute values, and rounding, in both digit systems. Undefined cases refuse honestly (remainder by zero, factorial of a negative), oversized factorials admit the precision limit, and a matching concept shelf explains pi, primes, Fibonacci, Pythagoras, infinity, averages, percentages, and equations in plain conversation.
- Reads how people actually type. Dashes instead of periods («سلام - چهطوری؟», «سلام-خوبی»), tatweel-stretched words («سلامــــ»), stacked punctuation («خوبی؟؟؟!!!»), decorations (
سلام, ((سلام))), ALL CAPS, ransom case, glued dots and underscores, slashes, and spelling variants («چهطوری», «چه طوری», «چجوری») all reach the right rule. The dash normalization is surgical: subtraction («۵-۳»), negative numbers, and hyphenated safety-critical English ("self-harm") keep working exactly as before. - Professional handling of hostility. Insults, bullying, and inappropriate comments aimed at Darya are met with calm, boundary-setting responses, never with an argument.
- A joke when you need one. Ask for a joke or a laugh ("tell me a joke", "بخندون من") and Darya shares clean, kind humor in your language.
- Celebrates your wins. Good news - a promotion, a raise, passing an exam, graduating, landing a job, getting engaged - is celebrated with warmth instead of being misread as a work-stress complaint. Burnout ("I work 80 hours a week", «سوختم») is recognized as the deep exhaustion it is, distinct from ordinary stress.
- Follows the emotional arc. Darya remembers the arc of the conversation: when your mood clearly improves across turns, she gently acknowledges it ("You sound lighter than you did earlier") rather than reacting to each line in isolation.
- Clear, answerable follow-ups. Reflective questions are phrased plainly ("When you get anxious, what does your mind usually predict will happen?") so they are easy to answer, in everyday language in both Persian and English. No question is ever asked twice word-for-word in the same session: once a topic's questions are spent, Darya changes register instead of sounding like a broken record. And after you have sat with one heavy topic for several turns, an explicit "what should I do?" gets a concrete, three-small-steps answer instead of yet another reflective question.
- Fresh, varied media recommendations. Movie, series, anime, game, music, podcast, book, and documentary requests draw randomized choices from a broad offline catalog, so Darya does not hand back the same top-ten list repeatedly. Genre and era filters are real: horror anime stays in its own shelf, cozy games avoid high-pressure titles, and both "80s" and "eighties" horror requests return only 1980s films. Dedicated Iranian shelves answer "فیلم ایرانی" with Kiarostami, Farhadi, Majidi, and Panahi, and "آهنگ ایرانی" with Googoosh, Shajarian, Namjoo, and other Iranian artists instead of international picks.
- Honest, useful teaching. For risky or delicate subjects Darya teaches with care and clear disclaimers (trading and leveraged forex are flagged as high-risk with a "not financial advice" warning), and gives real, reliable guidance for things like making friends or managing money.
- Understands restatements. «منظورم این هست که چه قابلیتهایی داری؟», "I mean, what capabilities do you have?", and «میخوام بدونم قابلیتهات چیه» are answered from the restated intent, and the restated text feeds the knowledge layer too («منظورم اینه که مسی کیه؟» answers the Messi fact). Capability questions work in their everyday forms («قابلیتهات چیه», "what are your abilities?") while identity questions stay separate.
- Everyday 2026 Persian. Colloquial registers are understood the way Iranians actually type them: «چخبر؟», «کجایی؟», «چته؟», «چی شده؟», and «مشکلی نیست؟» get natural warm replies instead of bookish prompts, and pure-Latin interjections in a Persian chat ("ok", "tnx", "lol", "bye") answer naturally instead of triggering the write-in-Persian redirect.
- Sports and people breadth. The offline shelf now covers 24 sports (tennis, chess, wrestling, cricket, American football, boxing, swimming, athletics, gymnastics, judo, taekwondo, cycling, skiing, esports, table tennis, badminton, handball, rugby, golf, Formula One, offside, VAR, futsal, weightlifting) with Iranian entries (Takhti to Yazdani, Iran futsal, Iran weightlifting), and 60+ notable figures across eras and cultures (Ferdowsi, Hafez, Rumi, Khayyam, Kiarostami, Shajarian, Mirzakhani, Avicenna, Einstein, Plato, Cleopatra, Shakespeare, Van Gogh, Beethoven, Malala, Ali Daei, and many more) in both languages.
- Speaks like a person, not a textbook. Every reply ships in the everyday conversational register people actually use in 2026: Persian says «کتابهاش رو گرفته» instead of «کتابهایش را گرفته است» and «کتابهاتون رو از زمین بردار عزیز» instead of «کتابهایتان را از روی زمین بردارید بزرگوار», and English says "I'm here, don't worry" instead of "I am here, do not worry". A dedicated register layer (
js/text/conversational.js) rewrites verb morphology, collapses the polite plural («کردید» → «کردی», «حرفتان» → «حرفتون»), merges the copula («مهم است» → «مهمه»), and contracts English, while protecting quoted poetry, titles, and tricky homographs («اینترنت کند», «بدهی» the debt, «استان» the province) from ever being mangled. The same layer also repairs sentence punctuation: a question-shaped sentence always ends with «؟» or "?", an exclamation-shaped one with "!", and statements are never turned into questions by mistake. - Occasionally a little chaotic, on purpose. On light, safe turns Darya sometimes colors a reply with a small human spark: a conversational opener («راستی،», "Honestly,"), a friendly check-in tag («، نه؟» or ", right?"), or an exclamation shared with you on good news. It is rare, cooldown-limited, and never appears on crisis, heavy, or guided-exercise turns, so the warmth stays human without ever getting in the way of care.
- Combat sports without the shrug. A dedicated fighters shelf covers 50+ famous fighters and martial artists across eras in both languages: UFC greats from Royce Gracie, Fedor, Anderson Silva, and GSP through Jon Jones, Khabib, and McGregor to Topuria, Makhachev, Pereira, Chimaev, and Aspinall; the women's divisions (Nunes, Shevchenko, Rousey, Zhang Weili, Kayla Harrison, and more); Iranian names (Beneil Dariush, Amir Aliakbari); and the wider arts (Bruce Lee, Tyson, Buakaw, Rico Verhoeven).
- Records with receipts. Ask «رکوردش چیه؟» or "what is his record?" after any fighter and Darya answers from her snapshot plus an honest caveat: a retired career (Khabib's 29-0) is presented as settled, an active one (Topuria, Makhachev) always carries a my-offline-data-may-be-stale note with a pointer to live sources, and a person with no stats on the shelf gets an honest no-numbers reply, never an invented figure.
- Deepening requests actually go deeper. After any person or topic answer, «بازم بگو»-style follow-ups serve a curated deep-dive exactly once (Khabib's story, Bruce Lee's philosophy, Messi's journey); when the shelf runs out, Darya says so plainly and points to Wikipedia or another reliable source instead of improvising.
- Plays along with fame games. «من مسیام» and "I'm the next Messi" get playful, warm replies (and the joke is never stored as your name); «من جانشین سقراطم» works for philosophers too; the classic «مسی بهتره یا سیم مسی؟» pun and "Messi or messy?" land as the jokes they are; and «مسی بهتره یا رونالدو؟» opens the real GOAT debate while «مسی یا سقراط» gets a cheerful apples-and-oranges answer.
- Honest self-awareness. «تو هوشیاری؟», «تو احساس داری؟», «میتونی عاشق بشی؟», "do you have feelings?", and "can you fall in love?" get honest identity answers instead of evasive lines or stored "preferences". Name corrections are remembered correctly («اسمم علی نیست، کوروشه»), contradictions («جوابت با قبل فرق داره») get honest self-awareness instead of a topic hop, and betrayal, work humiliation, being misunderstood, bad days, ethical dilemmas, and decision dilemmas each have their own caring pool. Pet-name requests get creative names, and poem requests get short public-domain verses.
- Feels human, not robotic. On a long streak of terse or repetitive replies, Darya occasionally lets a gentle, affectionate "huff" through ("Even a calm companion can raise an eyebrow when the reply is just ok"), while staying calm and caring on heavy or crisis turns.
- Aware of the app. Darya knows what she can and cannot do in the app: she honestly points you to the theme and sound controls, tells you the Export button in the menu downloads the conversation, and explains that the conversation lives only in the current browser tab (refreshing or a new chat clears it; only your theme is remembered).
- Self-aware and honest. Asked about consciousness, limits, or what she is, Darya answers plainly and honestly rather than impersonating a human: she is an offline companion with a clear sense of her own limits, and says so when a question is beyond her.
- Help starting the conversation. Not sure how to begin? Say "how do I start?" or "نمیدونم چی بگم" and Darya offers easy, low-pressure openers. If you stay silent after the greeting, she gently breaks the ice herself with a light question.
- Honest shopping guidance. Darya cannot make purchases, and says so plainly, then helps you think the purchase through: needs, budget, and how to compare options.
- Support for heavier feelings. Prolonged low mood is met with empathy and a gentle nudge toward professional support, never a diagnosis. Despair phrasings in both languages ("tired of life", "I am done with everything", "life feels pointless", «از زندگی خسته شدم», «زندگی بی معنی شده», «دیگه طاقت ندارم») route to the caring pool, and Darya keeps acknowledging the struggle turn after turn instead of bouncing away. An adult disclosing attraction toward a minor always receives a calm, non-shaming reply that is clear about the harm and points to confidential specialist help.
- Verified crisis resources. Acute crisis language always routes to a supportive safety response that names concrete, verified hotlines: 123 (Iran's social emergency, free 24/7) and 1480 (the Behzisti counseling line) in Persian, 988 (US/Canada, free 24/7) and 116 123 (Europe) in English. Crisis phrasing is matched across every register people actually type in: formal wording, slang and internet shorthand ("kms", "unalive", "end it all"), apostrophe-free contractions ("dont wanna live"), passive ideation ("I wish I could sleep and never wake up", «کاش میمردم», «بهتره که دیگه نباشم»), and plan or means statements ("I bought a rope", «قرص جمع کردم», «میخوام خودمو حلقآویز کنم»). The numbers appear inside the relevant safety reply rather than the ambient interface, so urgent guidance is prominent without clinically framing every ordinary conversation. Darya never dials; the numbers are offered as the immediate next step.
- A real safety net, not just one rule. Beyond first-person crisis language, Darya has dedicated, carefully-worded responses for the whole family of safety-critical disclosures: someone else at risk ("my friend wants to kill herself", «دوستم میخواد خودکشی کنه» gets concrete caregiver guidance), domestic violence and assault ("my husband hits me", «شوهرم منو میزنه», "I was raped" are believed first, never met with curiosity), extended food refusal and eating distress, psychosis-adjacent experiences (hearing voices), and method-seeking questions, which get a firm, warm refusal with no information, ever. Ideation wrapped in a joking softener ("i wanna die lol jk") gets a gentle, serious check-in instead of an echo. After any safety-critical turn, the whole session stays crisis-aware: goodbye confirmations restate the hotline instead of a breezy "I will wish you well", and playful tones stay off. As a second line of defense, any message containing death or self-harm vocabulary is never mirrored back, never answered with a playful huff or boredom line, and never handed a "what makes it interesting to you?" fallback; heavy unmatched turns get a caring acknowledgment-first reply instead.
- Offline and private. Conversation processing runs in your browser without external APIs, analytics, or transmitted chat data. Conversation text is kept only in the current tab, and the app works offline after its first successful load.
- Conversation export. Download your chat as plain text whenever you like.
- Open the page.
- Choose Persian or English.
- Pick your preferred theme (Ocean or Beach).
- Darya will greet you. Just start typing like any chat app.
That is all. No accounts, no setup, no configuration.
Darya's conversation engine runs entirely in your browser. Every response is generated locally. No conversation is recorded outside your tab, and nothing is persisted after you close it. A hosted PWA fetches its own static app files and update metadata, but no conversation text is transmitted and no external inference or analytics service is called.
Each turn follows a deterministic pipeline:
-
Input validation: Empty input, wrong script (e.g. Arabic text in English mode), and mixed-language input are caught early with appropriate redirection.
-
Normalization: The raw text goes through two parallel paths:
- One version applies Unicode normalization, quote unification, and whitespace cleanup. This version is stored in conversation memory so Darya can quote the user back naturally.
- A second version additionally strips punctuation, removes zero-width characters, expands common chat abbreviations (e.g. "btw" to "by the way", "idk" to "i do not know"), and for Persian unifies progressive prefix spelling variants ("می شود", "میشود", "میشود" all collapse to one canonical form). This expanded form is used only for pattern matching and is never stored in memory. Darya never quotes "i do not know" back at someone who typed "idk".
-
Rule matching: Darya maintains a priority-sorted list of topics (stress, family, grief, joy, gratitude, safety, and many more). Each topic has a regex pattern and a pool of varied responses. The highest- priority matching rule selects the response pool.
-
Dialogue act classification: Each turn is classified as one of: greeting, question, statement, emotional statement, acknowledgement, correction, gratitude, affirmation, negation, test input, or safety.
-
Response strategy selection: Based on the matched rule, topic blend, and reference context, the engine picks a strategy: topic-question, topic-reflection, context-reference, topic-blend, contextual-fallback, and others.
-
Smart overrides: Several detectors can override the normal response path when specific signals are present:
- Cultural language: Curated slang-definition questions become direct word-meaning answers, while natural slang and age-context disclosures enter the normal topic pipeline with explicit ambiguity guards. Safety rules always retain higher priority.
- Profile memory: When the user discloses their age or name ("من ۲۴ سالمه", "my name is Sara", "call me Sara", «منو سارا صدا کن», «اسممو سارا بذار») or asks Darya to recall it ("چند سالمه؟", "what is my name?"), the disclosure is stored in-memory for the session and the recall is answered from it, never from an evasive "I do not have an answer" line. If the user asks about something never disclosed, Darya says so directly and invites the disclosure. A disclosed age at or below 13 switches the reply to the age-appropriate young pool, which encourages talking to a trusted adult.
- Question recall: Asking "یادته آخرین سوالی که پرسیدم چی بود؟" / "do you remember the last question I asked?" makes Darya quote the user's own last question back from memory ("آره، یادم هست: «...». درست میگویم؟"), and when nothing was asked yet she says so honestly instead of faking recall.
- Knowledge-expansion requests: A request to enrich her dataset ("تو باید دیتاست خیلی غنیای داشته باشی", "you need a richer dataset of good questions") gets an honest offline acknowledgment that names what was noted and invites a concrete starting topic, instead of being misread as a work question or answered with an unrelated fun fact.
- Guided exercises: An explicit exercise request ("breathing exercise", "تمرین تنفس", "ثبت فکر") arms a small turn-based state machine (see responder-exercises.js): the offer is made, each affirmative advances one step, and stop/decline releases the exercise gracefully. Every step reply attaches yes/no quick-reply chips for the UI.
- Mood tracker: A "mood check" ask records a rating on the 1..10 scale in the session mood log, reflects on the band (low/moderate/high), and answers "how have I been feeling" with the recorded arc and its direction (see responder-mood.js). All data is session-only.
- App-command honesty: Theme and sound requests ("turn on ambient sound", "پوسته رو عوض کن") are answered by pointing to the real UI control, never with fake compliance or a quote-back.
- Family conflict: Falling out with a parent ("من با مامانم قهر هستم... چی کار کنم؟") gets family-specific care instead of being misread as a work question by the bare "کار" in "چی کار".
- Frustration/insult detection: Triple exclamation marks, repeated question marks, and expanded insult patterns trigger calm de-escalation responses.
- Teasing/mocking detection: Sarcastic praise, dismissive signals, condescending phrases, and mock agreement are detected with a signal-threshold system.
- Harassment handling: Insults, bullying, and inappropriate sexual comments directed at Darya are detected before the general insult check and answered from dedicated boundary-setting pools.
- Word repetition: If a word appears 4+ times across recent turns, Darya gently points it out.
- Wellbeing checks: After a few heavy turns, asking "how are you?" triggers a thoughtful check-in.
- Boredom detection: After a streak of very brief replies, Darya may invite the user to share more.
- Depression support: Prolonged low mood, hopelessness, and emptiness are acknowledged with empathy and a gentle, real nudge toward professional support, never a diagnosis.
- Child-safety protection: An adult disclosing sexual or romantic attraction toward a minor always receives a calm, non-shaming reply that is clear about the legal and real harm, separates thoughts from actions, and points to confidential specialist help (Stop It Now and mental health professionals). The detection requires adult context, attraction phrasing, and a minor-age marker together, so a teenager's own peer crush or ordinary family affection never triggers it, and no later override can replace it.
-
Emotional calibration: The engine detects primary emotions (sad, anxious, angry, grieving, overwhelmed, excited, etc.) through keyword patterns and adjusts the tone of the response with an appropriate emotional prefix.
-
Human touch: Darya may add a gentle follow-up line, a pronoun reflection ("I feel" to "you feel"), or a quote of something the user said earlier, giving the conversation a natural, attentive feel.
-
Memory recording: All relevant metadata (topic, sentiment, entities, dialogue act, strategy) is recorded in conversation memory for contextual continuity in future turns.
The engine itself is entirely language-agnostic. All rules, patterns, and configuration live in language packs with identical structure. Each pack provides:
- A normalization function for memory storage
- A progressive-prefix binding function (Persian only) that unifies "می شود", "میشود", and "میشود" for rule matching
- A priority-sorted rules list with regex patterns and response pools
- A curated cultural glossary and context rules for regional and internet slang
- Topic seriousness values that control question-asking behavior
- Insult, teasing, and frustration detection patterns
- Greeting, farewell, and other UI pools
- A sentiment lexicon for lightweight positive/negative scoring
- Entity callback templates for recalling named entities
Darya carries two offline knowledge layers, both fully offline with no network calls.
Reflective shelf (18 domains): philosophy, thinkers, focus, learning, communication, creativity, mindfulness, stress, self-compassion, conflict, decision-making, grief, resilience, forgiveness, purpose, relationship, career, and anxiety. Each domain has 4 entries in both English and Persian (thinkers has 10 curated entries, one per historical figure). When the user asks for guidance on these emotional and growth topics, Darya draws from this shelf rather than fabricating advice.
Factual layer (world knowledge, 2020s culture, careers): a keyword- scored fact base that answers concrete questions directly: science (the Sun and every planet from Mercury to Neptune, Jupiter, quantum physics, black holes, the solar system), technology (AI, programming foundations, software architecture, databases, web networking, concurrency, profiling, observability, refactoring, accessibility, offline-first PWAs, open-source contribution, secure development, threat modeling, authorized security testing, vulnerability disclosure, incident response, phishing defense, and cybersecurity careers), careers and money (quick-entry and low-cost work, security guarding and shift safety, job-search scams, freelancing, remote work, portfolios, vocational training, irregular income, unemployment, inflation-aware household planning, and the Iranian job market), education (choosing a college major, preparing for the Iranian konkur, career paths for teenagers), professions (carpentry, mechanical engineering, sculpture, music and singing, acting, mathematics degrees, diving, firefighting), culture and entertainment (a curated list of non-mainstream movies and series from Iranian and world cinema, plus per-genre lists for horror, romantic, comedy, dark comedy, fantasy, thriller, sci-fi, documentary, animation, short series, stories based on true events, dedicated TV series suggestions from beloved mainstream hits to underground gems, and anime picks), life decisions (marriage and children, respectful guidance on religion), modern culture (Gen-Z slang like rizz and brain rot, TikTok and the internet, ghosting and dating culture, quiet quitting), Persian language (half-space rules, ezafe, tanvin, common insults), relationships (crush confessions, breakup healing, the traits of a good relationship and the red flags of a toxic one), practical advice (sleep, study techniques, quick stress relief, imposter syndrome), daily-life guidance (cooking basics for complete beginners, building good habits, breaking bad ones, common health questions answered without diagnosis, starting exercise from a sedentary start, gym and fitness anxiety for beginners, living with little money, loneliness and making friends as an adult, melancholia and heavy low mood, dating-app fatigue and dating-profile questions, remote-work isolation, postpartum overwhelm, and pet-loss grief), intimacy (shame-free communication about sex and desire, with clear professional-help disclaimers), careers for the 2026 market (skills-based hiring, growing fields in tech, healthcare, trades, and creative work, and mid-career changes at any age), video games (classic consoles from the PlayStation 1 era, modern consoles, mobile picks, and per-genre lists), concrete career plans for common paths (software development, data science, design, teaching, marketing, entrepreneurship), psychology and health basics (CBT, neuroplasticity, sleep needs), sports (football rules, the Olympics, the marathon), history (the Achaemenid Empire and Cyrus the Great, the Egyptian pyramids, the Berlin Wall, methods for reading war sources, ancient Persian-Greek wars, the Arab conquest, Mongol invasions, Safavid-Ottoman and Russo-Persian wars, Iran in both world wars, the Iran-Iraq War, Arab-Israeli wars, the Gulf wars, Afghanistan, Lebanon, and the Crusades), cooking and food (Persian cuisine, saffron, tea culture), migration and conscription (internal moves, lawful routes at multiple budget levels, fraud protection, current-source caution for Iranian military service, and career continuity), relationships and appearance (healthy relationship plans, family control, dependency-aware boundaries, toxic friendships, appearance bias, facial comparison, body image, and shame-free sex education covering consent and boundaries), and the project itself (official repository, PWA, and Myket links on separate bullet lines, plus its codebase, response pipeline, privacy, packaging, and test architecture).
The research hierarchy, durable-versus-live boundary, legal cautions, and source families are recorded in KNOWLEDGE-SOURCES.md. Community discussions inform realistic test wording but never establish a legal, historical, clinical, or economic fact.
Knowledge talk is sequential: after any factual answer, a short follow-up naming a topic refines the conversation in place ("in horror genre please" after movie suggestions, "and Saturn?" after Jupiter, "بیزینس" after career advice, "and consent?" after relationship talk); ambiguous fragments such as "مشتری جذب کنم" are never hijacked. The lookup scores keywords against the normalized input, requires question framing for ambiguous terms (so "مشتری" as a customer never triggers the Jupiter entry), and only fires on confident matches. Factual questions are answered directly in both languages; emotional disclosures are never hijacked by the factual layer. A separate curated fun-fact pool (89 verified facts per language across 13 categories: science, space, animals, history, the human body, food, technology, life skills, social connection, relationships, sports, art and music, and money and finance) answers "tell me 3 facts", "give me just one shocking fact", "حداقل سه تا حقیقت بگو", and topic-filtered requests like "facts about space". When a question falls outside the offline knowledge base, Darya says so honestly and can point to reliable sources (Wikipedia, reputable educational channels, qualified experts) instead of fabricating an answer. Crisis language (suicidal thoughts, hopelessness, self-harm) always routes to a supportive safety response with professional help resources.
Darya tracks named entities (people, places, topics the user mentions) with activation scores that decay over time. Corrections ("I meant my manager, not my mother") are handled gracefully: the old entity is removed and the corrected one is promoted. Entities with high enough activation may trigger a gentle callback ("That mother thread is still with us...") when the topic is relevant.
User: "I am so stressed about this deadline"
Step by step (behind the scenes):
- The raw input is received.
- The memory version stores the text as typed: "I am so stressed about this deadline". This is kept for potential quoted callbacks.
- The matching version produces the same text (no abbreviations or punctuation to strip).
- The rule matcher iterates the priority-sorted rule list. The anxiety rule (which matches "stressed") fires first.
- Dialogue act classification returns "statement".
- Response strategy selection picks "topic-question": anxiety has high seriousness, and the question budget allows one follow-up question.
- A topic-specific question for anxiety is selected, e.g. "Does the worry feel constant or does it come in waves?"
- Emotional calibration checks for a primary emotion keyword. "stressed" does not appear in any emotion pattern, so no prefix is added.
- The reply is returned to the UI for display.
User: "wtf are you talking about"
Step by step (behind the scenes):
- The raw input is received.
- The memory version stores: "wtf are you talking about". The original abbreviation is preserved for natural quoted callbacks.
- The matching version applies abbreviation replacements. The pattern matches "wtf", producing: "what the fuck are you talking about".
- Rule matching runs against the expanded text. No standard topic rule matches (this is not a greeting, emotional statement, etc.).
- After normal routing completes with a fallback reply, the frustration override check fires.
- The insult pattern tests the matching text. It contains "fuck", so the insult flag is set.
- Since the input is not a safety turn, the frustration override fires.
- The reply is replaced with a calm de-escalation response.
- Memory is unaffected: the stored utterance remains "wtf are you talking about". The expanded form is never persisted.
User (turn 1): "I am really worried about my mother"
Step by step (behind the scenes):
- Sentiment analysis: "worried" is in the negative lexicon, so entity extraction is enabled this turn (neutral turns skip extraction).
- The entity extractor finds the person "mother" (confidence 0.94, family vocabulary) and stores it with activation 0.94 and context topic "family".
- The family rule matches (topic seriousness 0.7). Darya responds with a family question and does not mention mother (the first-mention guard prevents a callback on the same turn).
User (turn 2): "She has been unwell for a while"
- Turn 2 begins: entity activation decays by 18% per turn. Mother becomes 0.94 x (1 - 0.18) = 0.77, still above the 0.6 callback threshold.
- The input is classified, and the health/family context keeps mother relevant.
- The entity callback check runs: mother (activation 0.77 > 0.6) is eligible, and the 55% probability passes.
- A callback template is selected, replacing the placeholder with "mother": "That mother thread is still with us. Its place in your day seems worth noticing."
کاربر: "دستت درد نکنه خیلی کمک کردی"
Step by step (behind the scenes):
- The half-space normalizer runs: Arabic character substitution, digit conversion, diacritic stripping, and progressive prefix correction. The ZWNJ in "دستت" is already correct.
- Stored in memory: "دستت درد نکنه خیلی کمک کردی" (with ZWNJ intact).
- The matching version converts ZWNJ into a plain space, so "دستت" becomes the two tokens "دست ت" in the matching text.
- The rule matcher runs. The gratitude rule matches the idiom: its pattern accepts both the joined form and the space-normalized form.
- Teasing detection finds no sarcasm signals.
- The gratitude response pool is used. One response is selected.
- Emotional calibration is skipped because the dialogue act is "gratitude" (a light topic), so the word "درد" inside the idiom does not trigger a hurt prefix.
کاربر: "تو خیلی احمق هستی"
Step by step (behind the scenes):
- The half-space normalizer runs. No changes needed.
- Stored in memory: "تو خیلی احمق هستی".
- The matching version strips zero-width characters. No changes.
- Normal routing runs. No standard topic rule matches this insult.
- Frustration override check:
- The insult pattern tests the matching text. It uses Unicode-aware word boundaries. The word "احمق" is surrounded by spaces, so both boundaries match. The insult flag is set.
- The turn is not a safety turn, so the frustration override fires.
- The reply is selected from the frustration response pool.
- Darya responds with calm de-escalation. It does not argue, does not get defensive, and does not repeat the insult back to the user.
کاربر: "به من درباره تمرکز توضیح بده"
Step by step (behind the scenes):
- The half-space normalizer processes the input. No changes needed.
- The matching version keeps the explicit request wording: "به من درباره تمرکز توضیح بده".
- Rule matching: the knowledge rule pattern matches because the input is an explicit request ("درباره" + "توضیح بده" are knowledge keywords). Personal disclosures that merely mention a topic are intentionally routed to the lived-experience rule instead.
- Since the matched topic is "knowledge", all smart overrides check whether the topic is knowledge and are suppressed. The response stays informative rather than emotional.
- The offline knowledge shelf queries the focus domain. It returns 4 curated Persian entries on focus techniques.
- One entry is selected from the shelf.
- The reply contains practical wisdom on concentration, never fabricated advice.
Darya recognizes a broad set of everyday topics:
- Family: parents, siblings, relationships
- Work and school: career, study, exams, stress
- Emotions: sadness, anxiety, anger, joy, loneliness
- Sleep and health: rest, well-being, physical concerns
- Grief and loss: mourning, remembering, healing
- Self-esteem and motivation: confidence, purpose, growth
- Finances: money worries, planning
- Relationships and careers: connection, growth, and big decisions
- Wisdom topics: philosophy, creativity, decision-making, and mindfulness
- Practical self-development: learning English with a method, family conflict, personal boundaries
- Daily life: cooking basics, healthy habits (and breaking bad ones), common health questions, living with little money, making friends
- Life transitions: a new baby, night-shift work, exam pressure, quitting smoking, starting exercise, and caregiving for an aging parent ("I take care of my aging mother", "شیفت شب کار میکنم") each get their own thread instead of a generic reply
- Entertainment: TV series suggestions, anime, and movie picks by genre
- Literature and poetry: Hafez of Shiraz and his ghazals, the author of 1984 (George Orwell), and world classics
- The 2026 job market: in-demand fields, skills-based hiring, and changing careers at any age
- Loss of passion: when a hobby or creative spark you used to love fades, Darya meets it as a real quiet grief instead of the unknown-topic pool
- Heavy low mood: melancholia, anhedonia, and the honest path to professional care
Darya also understands Persian conversational nuances: "سلامتی؟" and "سلامت هستی؟" are read as everyday "how are you" greetings, and the word "کس" (person) is never mistaken for profanity when it appears in phrases like "کس دیگه" (someone else). Informal expressions such as «دهنم سرویسه» and «پولم ته کشیده» route to the intended pressure instead of literal family or cooking topics. A small verified Dari glossary recognizes phrases such as «جور استی؟», «خیر است», and «چکر» without claiming all Afghan regional usage.
Darya will never diagnose, prescribe, or give professional advice. For medical, legal, or financial decisions, please consult a qualified human professional.
See PRIVACY.md for the full privacy policy (required for store listings).
Darya has no accounts, no analytics, no tracking, and no server-side processing. Your conversation exists only in your browser tab. When you close the tab, the conversation is gone.
The only persistent data is your theme preference (Ocean or Beach), stored locally in localStorage with a small cookie fallback so it is remembered on your next visit. Ambient sound is never persisted: every visit starts silent, and sound plays only after you tap the sound toggle.
No conversation data is sent to any server, no analytics are collected, and no external APIs are called. When used as a hosted PWA, the browser still downloads Darya's own static files and checks the same-origin service worker for updates. After the first successful load, the cached app works fully offline, including both ambient sound files.
After the first visit, Darya works fully offline. You can install it on your phone or desktop as a Progressive Web App. See the OFFLINE.md file for details.
The android/ platform folder is committed. Android releases are built
only when you manually run .github/workflows/build-android.yml from the
GitHub Actions tab. Pushing a version tag does not trigger this workflow.
When starting the workflow, enter the release tag to publish (for example
1.2.3). If the tag does not already exist, the workflow creates the
GitHub Release, and the release action creates the tag at the commit/ref
you selected for the manual run. If the tag already exists, the workflow
checks out that tag before building so the attached files match the tagged
source. To rebuild the files for an existing release, enable Replace
existing release; the workflow deletes and recreates the GitHub Release
for the same tag after the APK/AAB build succeeds, without deleting or
moving the git tag.
The workflow:
- runs the full CI gate (
npm run test:full: lint, CSS lint, format check, and the complete Node test suite), then syncs the web bundle into the platform (npm run sync:web && npx cap sync android), - stamps the app version from the requested tag (
1.2.3becomesversionCode123,versionName "1.2.3"), - builds a signed release AAB and a signed release APK (the AAB is required by Google Play, the APK is used by Iranian stores like Cafe Bazaar and Myket),
- attaches both to the GitHub Release for the tag, with the version
embedded in the filename (e.g.
darya-1.2.3-release.aabanddarya-1.2.3-release.apk), and also keeps them as workflow artifacts.
All four Android signing secrets must be configured before running the workflow; manual release builds fail early if any signing secret is missing.
Two things behave differently inside the Capacitor WebView, and the
web layer compensates through js/app/native.js:
-
Export. The Android WebView has no download machinery, so the website's blob-URL download would silently do nothing. In the shell the Export menu item calls the first-party
ExportPlugin(android/app/src/main/java/com/darya/companion/ExportPlugin.java), which saves the transcript into the system Downloads folder through MediaStore (no storage permission needed; on Android 9 and older it lands in the app's own Downloads directory). If the write fails, the transcript is copied to the clipboard instead. -
Service worker. The APK already bundles the whole app, so the PWA's service worker adds nothing there and is never registered; a registration left by an older build would keep serving the previous app shell after an update. Leftovers are retired at boot both from the web layer and by a snippet
MainActivityinjects on every page start, which recovers devices already stuck on a cached old shell without clearing app data. -
Back button. Capacitor 8 ships no back handling, so back would finish the activity even mid-conversation. The first-party
ShellPlugin(android/app/src/main/java/com/darya/companion/ShellPlugin.java) hands each press to the web policy (js/app/backbutton.js), which unwinds one surface at a time: menu, new-chat dialog, breathing overlay, pending farewell bar, then the active conversation (asking before ending it, exactly like the in-app goodbye flow), then the ended conversation (back to the picker), and on the picker itself back leaves the app. Until the web policy subscribes (or if the bundle ever fails to boot), the platform default applies.
The Android launcher icon is not generated by npx cap sync; that
command only copies the web bundle, so the native icon lives in the
committed android/app/src/main/res/ tree. When you first add the
platform with npx cap add android it ships the stock Capacitor logo,
which is why a release can show the wrong icon even though the artwork
under assets/icons/ is correct.
To rebuild every icon variant (legacy square, round, and the API 26+ adaptive foreground/background layers) from the source artwork:
npm run android:icons # -> python3 scripts/generate-android-icons.pyThe script reads assets/icons/android-chrome-512x512.png and rewrites
the mipmap PNGs, the adaptive-icon XML in mipmap-anydpi-v26, and the
ic_launcher_background colour value. Run it and commit the result
whenever you change the icon. The CI workflow runs the same step on
every build, so the shipped APK always carries the real icon.
The web side has two delivery details an icon swap must not forget:
- The PWA icons and favicons under
assets/icons/are precached into the service worker's static-assets cache, which is filled once per cache name and never re-fetched afterwards. BumpSTATIC_CACHE_NAMEinsw.jswhenever the icon artwork changes, or already-installed PWAs keep serving the previous icons forever; activation retires the superseded cache automatically. - Browsers only reinstall the worker when the
sw.jsbytes change, so any change that affects precached content (icons included) must also touchsw.js; see the refresh note in its header comment.
The release keystore is never committed. Generate it once and back it up somewhere safe: losing it means you can never publish an update to Cafe Bazaar or Myket (Google Play offers an upload-key reset, the Iranian stores do not). With Java installed:
keytool -genkeypair -v \
-keystore darya-release.jks \
-alias darya \
-keyalg RSA -keysize 2048 -validity 10000 \
-storepass CHANGE_ME_STORE \
-keypass CHANGE_ME_KEY \
-dname "CN=Darya, OU=Darya, O=Darya, L=Tehran, ST=Tehran, C=IR"Then add four repository secrets in GitHub (Settings > Secrets and variables > Actions):
ANDROID_KEYSTORE_BASE64:base64 -w0 darya-release.jksoutputANDROID_KEYSTORE_PASSWORD: the store passwordANDROID_KEY_ALIAS:daryaANDROID_KEY_PASSWORD: the key password
For local release builds, create android/keystore.properties
(gitignored) instead. The storeFile path is resolved relative to
android/app/, so if the keystore lives there (as
android/app/darya-release-key.jks does here), a bare filename works:
storeFile=darya-release-key.jks
storePassword=CHANGE_ME_STORE
keyAlias=darya
keyPassword=CHANGE_ME_KEYWork through this checklist once, before the first release:
- Back up the keystore. Copy
darya-release-key.jksand both passwords to a password manager and one offline location (encrypted USB or safe deposit box). Iranian stores have no key-reset mechanism: losing the keystore permanently locks you out of updates. - Verify the keystore. Confirm the alias and expiry with
keytool -list -v -keystore android/app/darya-release-key.jks -storepass YOUR_STORE_PASSWORD; the alias must bedaryaand the validity should span decades (the-validity 10000default is about 27 years). - Add the four GitHub secrets listed above, with
ANDROID_KEYSTORE_BASE64frombase64 -w0 android/app/darya-release-key.jks. - Enable GitHub Pages (Settings > Pages, deploy from a branch) so PRIVACY.md resolves at a public URL for the Play Console, or host it anywhere else.
- Trigger the workflow manually from the GitHub Actions tab, enter
the version tag, and confirm the GitHub Release shows the
version-tagged
darya-<tag>-release.aabanddarya-<tag>-release.apkattached, and that the APK is signed with your key (install it on a device, or check withapksigner verify --print-certs). - Upload: the AAB to Google Play (enrolling in Play App Signing), the signed APK to Cafe Bazaar and Myket.
- Google Play: upload the AAB and enroll in Play App Signing, which splits your key into an upload key (kept by you) and a signing key (managed by Google). New apps must target the current Android API level; this project already targets API 36. Complete the Data safety form as "no data collected" and link a hosted privacy policy. Enable GitHub Pages for the repository (Settings > Pages, deploy from a branch) so PRIVACY.md resolves at a public URL, or host it anywhere else.
- Cafe Bazaar and Myket: upload the signed release APK. Developer accounts require Iranian national ID verification. Both stores moderate content, so a text-based app dealing with relationship and intimacy education may need an appeal if flagged; the content is respectful, non-explicit, and always points to professional help. Keep the keystore backed up, since these stores have no key-reset mechanism.
The project ships with dependency-free test suites:
npm startserves the app locally on port 8080.npm testruns the Node.js engine test suite using Node's built-in test runner.npm run test:e2eruns the four real-browser accessibility, offline, quick-reply, and ambient-sound suites; they skip cleanly when no Chrome or Chromium binary is installed.npm run test:smokeruns a shell check that verifies the file structure, JS syntax, and that every asset serves correctly over a throwaway local server.npm run test:fullruns lint, CSS lint, formatting check, and the full test suite together, matching the CI gate. CI runs this same gate on every push to any branch and on every pull request (.github/workflows/ci.yml), so a regression is caught at PR time; the manual Android release workflow re-runs it before every release build.
The scenario directory contains more than one hundred bilingual and multi-turn fixtures spanning daily life, personas, knowledge, mixed topics, safety, memory, and emotional shifts. They include new parents, night-shift workers, students, caregivers, people changing careers, newcomers to a city, relationship repair, money pressure, grief, online life, and many other contexts. Each fixture drives the real engine and checks dialogue act, intent, and topic continuity so routing regressions are caught by the test run.
- Dedicated suites add targeted regression tests and multi-turn
scenario fixtures.
tests/software-work-history-intelligence.test.mjscontributes 348 bilingual lookup, live-engine, hard-persona, cyber-boundary, clean-formatting, no-fixed-price, conflict-history, migration, conscription, Persian-code-point, and offline-wiring decisions.tests/engine-regression.test.mjspins specific engine behaviors (math, knowledge edge cases, despair coverage, the dirty-talk boundary, randomized media, project awareness, trading disclaimers, self-awareness),tests/persona-conversations.test.mjscovers everyday personas (promotions, despair, grief, new parents, caregivers, burnout, crisis),tests/context-memory-conversations.test.mjsadds deep personas spanning passions, entertainment, breakups, loneliness, loss, jealousy, anger, sadness, poverty, depression, excitement, flirty users, rude users, and the emotional-shift memory touch, andtests/mixed-topic-sessions.test.mjsruns 27+ long sessions in which the user jumps between unrelated subjects, validating that the context-window and memory stay coherent under topic-switching pressure. bash run-tests.sh -n 50stress-runs the engine tests 50 times and reports a pass/fail summary per round, useful for shaking out flaky assertions.npm run lint,npm run lint:css, andnpm run format:checkverify Google-style ESLint, Stylelint, and Prettier compliance without modifying files.
The version lives in several files that must change together:
package.jsonandpackage-lock.json: the canonical version. The service worker reads it at install time and derives its precache cache name from it, so bumping it also tells returning visitors to fetch the new app shell.manifest.json: the PWA manifest version.android/app/build.gradle:versionCodeandversionName, the local Android build defaults. CI re-stamps them from the pushed git tag, so they matter only for local builds.
npm run version:bump 1.3.1The script (scripts/bump-version.mjs) validates the new version,
refuses downgrades, updates all four files in one step (deriving
versionCode the same way CI does, digits only: 1.3.0 -> 130),
verifies every file after the edit, and prints a summary. Preview the
change without touching anything:
node scripts/bump-version.mjs 1.3.1 --dry-runThe script deliberately does not write CHANGELOG.md (its entries are
hand-written prose) and does not create the git tag. After bumping, add
a changelog entry, commit, and run the manual Android release workflow
with the new version tag (see "Android APK Builds").
package.json: set the"version"field.package-lock.json: set the root"version"field in both spots (the top-level field and thepackages[""]entry). These drift easily, which is exactly why the helper script exists.manifest.json: set the"version"field.android/app/build.gradle: setversionNameto the new version andversionCodeto its digits (1.3.0 -> 130), and update the// tag (...)comment above them.CHANGELOG.md: add a dated entry at the top describing the changes.
Copyright (c) 2026 Artin Mohammadi. Licensed under the PolyForm Noncommercial License 1.0.0. See LICENSE.md for the full terms. This license allows personal and noncommercial use only. Commercial use requires a separate agreement.