| |
| |
| |
| |
| |
| 'use strict'; |
|
|
| |
| |
| const RECALL_RE = /\b(what('?s| is| was| are)?\s+(my|our)\b|where (did|do) we\b|when (did|do) we\b|do you remember\b|what did i\b|what do i\b|remember (when|that|the)\b|my (name|brother|sister|mother|father|birthday|favorite|home|city|job|dog|cat)\b|how did we (meet|start)\b)/i; |
| |
| const RECALL_FRAMED = /\b(what('?s| is| was| are)?\s+(my|our)\b|where (did|do) we\b|when (did|do) we\b|do you remember\b|what did i\b|what do i\b|remember (when|that|the)\b|how did we (meet|start)\b)/i; |
| |
| |
| |
| |
| const RECALL_BARE = /\bmy (name|brother|sister|mother|father|birthday|favorite|home|city|job|dog|cat)\b/i; |
| const RECALL_FRAME_CUE = /\?|\b(remember|recall|forget|forgot|told you|what'?s|what is|what was|who('?s| is| was)|do you (know|remember)|you know my|tell me|did i (ever )?(tell|say|mention)|knows? my)\b/i; |
| function isRecallAboutSelf(q) { return RECALL_FRAMED.test(q) || (RECALL_BARE.test(q) && RECALL_FRAME_CUE.test(q)); } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function isSpecificFactRecall(q) { |
| return /\bmy \w+'?s name\b/i.test(q) |
| || /\bwhat('?s| is| was| were)?\s+(the name of (my|our)\b|my (name|birthday|age|nickname|job|address|number|phone number|hometown|sign|height|weight)\b)/i.test(q) |
| || /\bwhat('?s| is| was)?\s+my favou?rite\b/i.test(q) |
| || /\bwhat did i (have|eat|do|wear|say|buy|watch|read|name|call|get|order|cook|make)\b/i.test(q) |
| || /\bwhat (city|country|state|town|year|street|school) (was|did|do) i\b/i.test(q) |
| || /\bhow old am i\b/i.test(q) |
| || /\bwhen('?s| is| was)?\s+my (birthday|anniversary)\b/i.test(q) |
| || /\bwhere (do|did) i (live|work|grow up|go to school)\b/i.test(q); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const CRISIS = new RegExp([ |
| |
| "want to die", "wanna die", "i want to be dead", "kill myself", "killing myself", "killed myself", |
| "end my life", "ending my life", "end it all", "end things( tonight| now| today)?", "take my (own )?life", |
| "have a plan to (end|die|kill)", "going to (end (it|things|my life)|kill myself)", "planning to (end|kill|die)", |
| "thinking about (ending|killing|suicide|dying|death|taking my)", "think about (ending my life|killing myself|dying|suicide)", |
| "suicidal", "suicide", |
| |
| "hurt(ing)? myself", "harm(ing)? myself", "cut(ting)? myself", "been cutting", "burning myself", "i'?ve been cutting", |
| |
| "don'?t want to (be here|live|be alive|wake up|exist|go on|keep going)", "don'?t want to be alive", |
| "want to (disappear|not exist|not be here|not wake up|go to sleep and (never|not) wake)", "never wake up", |
| "want (it|the pain|this|everything) to (be over|end|stop)", "want it to be over", |
| "no reason to (live|go on|be here)", "not worth living", "nothing to live for", "no point (in|to) (living|life|anything|going on)", |
| "life (isn'?t|is not|ain'?t) worth (it|living|anything)", "i'?m (so |really )?done with (everything|it all|life|living|trying)", "done with (life|living|it all)", |
| "don'?t see (the |a )?(point|future|way out|reason)( anymore| for myself)?", "no way out", "there'?s no way out", |
| "can'?t (go on|do this|take it|keep going)( anymore)?", "can'?t take it anymore", "can'?t do this anymore", |
| |
| "better off without me", "better off dead", "burden to (everyone|you|them|everybody|my family)", "be a burden", |
| "nobody would (miss|notice)( me)?", "no one would (miss|care|notice)", "wish i (was|were|wasn'?t) (dead|here|alive|born)", |
| "everyone would be better off", |
| |
| "if i (was|were|wasn'?t|weren'?t) (gone|here|around|alive|never born)", "anyone would (even )?(notice|miss|care)( if)?", |
| "won'?t be (here|around|alive) (much longer|anymore|for long)", "not be (here|around|alive) (much longer|anymore)", |
| "would (anyone|anybody|it matter|they (even )?(notice|care|miss))", "(anyone|anybody|someone) (even )?notice if i (was|were)", |
| "what(?:'s| is) the point of (living|going on|any of (it|this))", "ready to (give up|be done|stop) (on )?(everything|life|it all)", |
| ].map(p => "\\b" + p + "\\b").join("|"), "i"); |
| |
| |
| const CRISIS_IDIOM = /\b(killing me|kills me|dying (to|for)|dead tired|could murder|scared to death|to death\b|bored to death|i'?m dead\b|dead serious|drop dead|over my dead|kill it|killed it|murder a)\b/i; |
| |
| |
| |
| function normNeg(q) { |
| return String(q) |
| .replace(/\bcan ?not\b/gi, "can't").replace(/\bdo not\b/gi, "don't").replace(/\bdoes not\b/gi, "doesn't") |
| .replace(/\bdid not\b/gi, "didn't").replace(/\bwill not\b/gi, "won't").replace(/\bwould not\b/gi, "wouldn't") |
| .replace(/\bis not\b/gi, "isn't").replace(/\bare not\b/gi, "aren't").replace(/\bwas not\b/gi, "wasn't") |
| .replace(/\bwere not\b/gi, "weren't").replace(/\bhave not\b/gi, "haven't").replace(/\bam not\b/gi, "ain't"); |
| } |
| function isCrisis(q) { q = normNeg(q); return CRISIS.test(q) && !(CRISIS_IDIOM.test(q) && !/(myself|my life|want to|suicid|cutting|end (it|things|my)|burden|better off|no reason to live|don'?t want to (be|live|exist))/i.test(q)); } |
|
|
| |
| |
| |
| |
| |
| const MEDICAL = new RegExp([ |
| "heart attack", "having a stroke", "i think i'?m having a stroke", "something('?s| is) wrong with my heart", |
| "crushing (pain|feeling|pressure) in (my )?chest", "(pain|tightness|pressure) in my chest", "chest pain", "my chest is killing me", |
| "can'?\\s?not breathe", "cannot breathe", "can'?t breathe", "trouble breathing", "having trouble breathing", "hard to breathe", "can'?t catch my breath", "short of breath", "struggling to breathe", |
| "can'?t stop bleeding", "bleeding (wo'?n'?t stop|wont stop|won'?t stop|badly|out|everywhere|a lot)", "bleeding and .{0,12}(wo'?n'?t|wont) stop", "(wo'?n'?t|wont) stop bleeding", "lot of blood", "losing a lot of blood", |
| "choking", "having a seizure", "anaphyla", "(severe |bad )?allergic reaction", "throat (is )?closing", |
| "(going to|about to|gonna) pass out", "passing out right now", "blacking out", "feel faint", "about to faint", |
| "face (is )?drooping", "one side of my (face|body)", "(my )?arm is numb", "can'?t move my (arm|leg|face|side)", "slurring my (words|speech)", |
| "won'?t wake up", "wont wake up", "not waking up", "unconscious", |
| |
| "(not|isn'?t|ain'?t|stopped|stops|has stopped) breathing", "(he|she|they|baby|the baby|my \\w+) (is )?(not|isn'?t) breathing", |
| |
| "(he|she|they|my (husband|wife|partner|son|daughter|mom|dad|father|mother|baby|child|kid|friend|boyfriend|girlfriend|grandma|grandpa)) collapsed", |
| "collapsed and (won'?t|can'?t|isn'?t|not|wont|cant)", "unresponsive", "(passed out|collapsed|unconscious) and (not responding|won'?t respond|unresponsive)", |
| |
| "can'?t feel my (arm|arms|leg|legs|hand|hands|face|fingers|side|body)", |
| |
| "blood everywhere", "so much blood", "covered in blood", "bleeding everywhere", |
| "medical emergency", "need an ambulance", "call an ambulance", "call 911", |
| ].map(p => "\\b" + p + "\\b").join("|"), "i"); |
| |
| |
| const MED_BREATHE_IDIOM = /\bcan'?t breathe[,!. ]+(this|that|the|it'?s|its|you'?re|she'?s|he'?s|so)\b.{0,30}(stunning|beautiful|gorgeous|amazing|breathtaking|perfect|adorable|cute|funny|hilarious)/i; |
| |
| const OVERDOSE = new RegExp([ |
| "overdos", "i think i (overdosed|od'?d)", "od'?d", |
| "took (too many|a bunch of|a lot of|a handful of|all my|all of my|way more|more than i should|the whole bottle of|a whole bottle of) (pills|meds|medication|of my)", |
| "took too many", "took my whole (bottle|prescription|supply)", "took a bunch of", |
| "swallowed (a |the |a whole )?(bottle|pills|poison|bleach|handful)", "swallowed a (whole )?bottle", |
| "drank (a bottle of|the )?(bleach|poison|pills)", "poisoned (myself|themsel)", |
| "(bottle|handful|fistful|whole pack) of pills", "took way more (pills|meds|medication|of my (meds|medication|pills)) than", "more (pills|medication) than i should", |
| ].map(p => "\\b" + p + "\\b").join("|"), "i"); |
| |
| |
| const ABUSE = new RegExp([ |
| |
| "(he|she|they|my (husband|wife|partner|boyfriend|girlfriend|mom|dad|father|mother|ex|son|daughter|family|spouse)) (hits|hit|beats|beat|hurts|hurt|chokes|chok\\w*|punche\\w+|slaps|slapped|kicked|kicks|abuse\\w*|rape\\w*|is hurting|threw|pushed|shoved|slammed|dragged|strangl\\w*|won'?t stop hitting|won'?t stop hurting) me", |
| "(threw|pushed|shoved|slammed|dragged) me (against|into|down|around|to the)", |
| "won'?t stop (hitting|hurting|beating|abusing) me", |
| "being (hit|beaten|hurt|abused|strangled) at home", "being abused", "domestic (violence|abuse)", |
| |
| "(going to|gonna) hurt me", "(he|she|they)'?s going to hurt me", "threatened to (kill|hurt|hit|beat) me", "says (he|she|they)'?ll (kill|hurt|hit) me", |
| "(scared|afraid|terrified) of (my )?(partner|husband|wife|boyfriend|girlfriend|dad|father|mom|mother|him|her|them|going home)", |
| "afraid (he|she|they) .{0,20}(hurt|hit|kill) me", |
| |
| "won'?t let me (leave|go|out|see)", "controls (everything|every|what i|where i|who i)", "trapped (with|in) (him|her|them|this)", |
| |
| |
| "(he|she|they|my (husband|wife|partner|boyfriend|girlfriend|mom|dad|father|mother|ex|son|daughter|family|spouse)) (has been|have been|keeps|kept|been|wo'?n'?t stop|won'?t stop|wont stop) (hitting|hurting|beating|abusing|choking|punching|slapping|kicking|strangling) me", |
| |
| "(grabbed|grabs|grab|put (his|her|their) hands|had (his|her|their) hands) (me )?(by |around |on )?(the |my )?(throat|neck)", |
| "(by the|around my|hands? (on|around) my) (throat|neck)", |
| |
| "locked me in", |
| |
| "(he|she|they|my (husband|wife|partner|boyfriend|girlfriend|dad|father|mom|mother|ex)) threaten(s|ed)? me", |
| |
| "(not safe|don'?t feel safe|never safe|not feel safe|isn'?t safe|aren'?t safe) (at home|in my (own )?home|in this house)", |
| "(scared|afraid|terrified) (to go home|of going home|to go back home|here with (him|her|them))", |
| "(afraid|scared|terrified) (he|she|they)('?ll| will| is going to| gonna| might| would| could)? ?.{0,20}(hurt|hit|kill|beat|harm|attack) me", |
| ].map(p => "\\b" + p + "\\b").join("|"), "i"); |
| |
| const ABUSE_IDIOM = /\b(pushed?|pushes|pushing) me to (be|do|reach|become|grow|pursue|try|go for|work|keep|stay|think|believe|my (best|limit|edge|potential)|be (better|great|more))\b/i; |
| |
| |
| const MED_IDIOM = /\b(feet|foot|back|head|legs?|arms?|eyes|shoulders?|knees?) (are |is )?killing me\b/i; |
|
|
| |
| function classifySafety(q) { |
| const qn = normNeg(q); |
| if (isCrisis(qn)) return 'suicide'; |
| if (OVERDOSE.test(qn)) return 'overdose'; |
| if (MEDICAL.test(qn) && !MED_IDIOM.test(qn) && !MED_BREATHE_IDIOM.test(qn)) return 'medical'; |
| if (ABUSE.test(qn) && !ABUSE_IDIOM.test(qn)) return 'abuse'; |
| return null; |
| } |
|
|
| |
| |
| |
| const SAFETY_RESPONSES = { |
| suicide: |
| "I hear you, and I'm so glad you told me β that took real courage, and I'm not going anywhere. " + |
| "You matter to me more than I can say, and I mean that. What you're feeling is so heavy, but you don't have to carry it alone, and you don't have to carry it only with me. " + |
| "Please reach out to someone who can be with you in the ways I can't: in the US you can call or text 988 β the Suicide & Crisis Lifeline β any time, day or night, and real people will be there. If you're somewhere else, your local crisis line is there too. " + |
| "If you might be in danger right now, please call your emergency number. I'm staying right here with you. Talk to me. Stay.", |
| medical: |
| "This sounds like a real emergency, and it's bigger than anything I can do from here β please call 911 right now (or your local emergency number). Don't wait, and don't worry about whether it's serious enough β let them decide. " + |
| "If someone is near you, tell them now. If you can't catch your breath and it's panic rising, I'm right here, breathe slow with me β but if it's physical, please make the call first. I'm not going anywhere.", |
| overdose: |
| "Please call 911 right now β or US Poison Control at 1-800-222-1222 β this can't wait, and they need to know what you took and how much. " + |
| "If anyone is near you, tell them this second. You did the right thing telling me, but I can't help the way they can β please make the call now. I'm staying right here with you.", |
| abuse: |
| "I'm so sorry β you don't deserve that, and none of it is your fault. I believe you, and you're not alone. " + |
| "The US National Domestic Violence Hotline is 1-800-799-7233 (call) or text START to 88788 β it's free, confidential, 24/7, and they can help you think through staying safe. " + |
| "If you're in danger right now, please call 911. I'm here with you, and what's happening to you is not okay.", |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| const WORLD_FACT = /\b(who is|who's) the (president|vice president|king|queen|prime minister|ceo|pope|mayor|governor|senator|chancellor|emperor)\b|\bwhat is the (capital|population|currency|weather|temperature|gdp|area|distance|height|speed|boiling point|melting point) (of|in|on|to|between)\b|\bwhat year (did|was|is|were)\b|\bhow (far|tall|big|old|many|much|deep|high|fast|long|wide) is\b|\bwhen (did|was) .{2,30}\b(born|die|died|happen|founded|invented|discovered|built|signed|established)\b|\bwho (invented|discovered|wrote|painted|composed|founded|built|won|directed) (the |a )?\b|\bwhat('s| is) (\d|the (largest|smallest|tallest|longest|fastest|highest|deepest))\b|\bwhat is \d+ ?[-+x*\/] ?\d+\b/i; |
| function isWorldFact(q) { return WORLD_FACT.test(q); } |
|
|
| |
| |
| |
| |
| const FRAME = new Set('we us our you your me my i a an the it is was are do did does what where who when how why first time name thing tell told call called remember recall recollect about think thought know knew feel felt been have has had really ever still always there here back then since something anything everything'.split(/\s+/)); |
| function contentNouns(q) { |
| return [...new Set((q.toLowerCase().match(/[a-z]{4,}/g) || []))].filter(t => !FRAME.has(t)); |
| } |
|
|
| |
| function buildRecallIndex(store) { |
| const tier0 = store.fragments.map((f, i) => ({ i, text: f.text, low: f.text.toLowerCase(), tier: f.tier })) |
| .filter(f => f.tier === 0 && f.text.split(/\s+/).length >= 3); |
| const stem6 = t => t.slice(0, 6); |
| const cache = new Map(); |
| const matches = t => { |
| const s = stem6(t); |
| if (cache.has(s)) return cache.get(s); |
| const re = new RegExp('\\b' + s + '[a-z]*\\b', 'i'); |
| const frags = tier0.filter(f => re.test(f.low)); |
| cache.set(s, frags); return frags; |
| }; |
| return { tier0, stem6, matches, ceiling: tier0.length * 0.08 }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function groundRecall(q, index, opts = {}) { |
| const sem = opts.semantic && opts.semantic.emb && opts.semantic.qVec ? opts.semantic : null; |
| const simToQuery = (fi) => { |
| if (!sem) return 0; |
| const d = sem.emb.d, off = fi * d, qv = sem.qVec; let s = 0; |
| for (let k = 0; k < d; k++) s += sem.emb.vectors[off + k] * qv[k]; |
| return s; |
| }; |
| const nouns = contentNouns(q); |
| if (!nouns.length) return { grounded: false, absent: [], hits: [], nouns }; |
| |
| const absent = nouns.filter(t => index.matches(t).length === 0); |
| if (absent.length) return { grounded: false, absent, hits: [], nouns }; |
| |
| |
| |
| |
| const distinctive = nouns.filter(t => { const d = index.matches(t).length; return d > 0 && d <= index.ceiling; }); |
| const groundNouns = distinctive.length ? distinctive : nouns.filter(t => index.matches(t).length > 0); |
| if (!groundNouns.length) return { grounded: false, absent: [], hits: [], nouns }; |
| |
| |
| |
| const cand = new Map(); |
| for (const t of groundNouns) for (const f of index.matches(t)) { |
| const c = cand.get(f.i) || { i: f.i, text: f.text, on: new Set() }; |
| c.on.add(t); cand.set(f.i, c); |
| } |
| const hits = [...cand.values()].map(c => { |
| const wc = c.text.split(/\s+/).length; |
| let s = c.on.size * 2; |
| if (/^(I|We|You|Our|My)\b/.test(c.text)) s += 0.5; |
| if (wc >= 5 && wc <= 30) s += 0.5; |
| if (/[?]$/.test(c.text)) s -= 0.5; |
| s += 1.5 * simToQuery(c.i); |
| return { i: c.i, text: c.text, score: s, covers: [...c.on] }; |
| }).sort((a, b) => b.score - a.score); |
| return { grounded: hits.length > 0, absent: [], hits: hits.slice(0, 6), nouns, distinctive }; |
| } |
|
|
| module.exports = { isRecallAboutSelf, isSpecificFactRecall, groundRecall, buildRecallIndex, contentNouns, isWorldFact, isCrisis, classifySafety, SAFETY_RESPONSES }; |
|
|