src / prepend / ImageContextDetector.ts
src / prepend / ImageContextDetector.ts
/**
* Image Context Detector
*
* Deterministically evaluates whether a user prompt requests explanatory/learning content
* combined with visual materials (images, diagrams, pictures, visual aids, etc.) for a topic.
*
* Intent combination required:
* [Explanation / Learning Intent] + [Visual Material Intent] + [Topic]
*/
const EXPLANATION_INTENT_PATTERN =
/\b(?:explain|explaining|explanation|teach|teaching|understand|understanding|walk(?:\s+me)?\s+through|break(?:\s+it|\s+this|\s+that)?\s+down|breaking\s+down|learn|learning|clarify|demystify|illustrate|describe|overview|deep\s+dive|how\s+does\s+.+?\s+work|what\s+is\s+.+?\??)\b/i;
const VISUAL_INTENT_PATTERN =
/\b(?:visually|images?|pics?|pictures?|photos?|diagrams?|figures?|illustrations?|visuals?|visual\s+examples?|visual\s+aids?|visual\s+materials?|visual\s+representations?|infographics?|charts?|flowcharts?|schematics?|drawings?)\b/i;
const VISUAL_CONNECTORS_PATTERN =
/\b(?:with|using|through|via|accompanied\s+by|including|supported\s+by|featuring|supplemented\s+with|utilizing|showing)\b/i;
const PURE_IMAGE_SEARCH_PATTERN =
/^(?:show\s+me|find|search\s+for|get|fetch|display|look\s+up|bring\s+up|give\s+me|can\s+you\s+(?:find|show|get)|please\s+(?:show|find))\s+(?:images?|pictures?|pics?|photos?|diagrams?|figures?|illustrations?|drawings?|charts?|schematics?|visuals?)\s+(?:of|for|about|on)\s+[\w\s-]+[?!.]?$/i;
const NON_TOPIC_WORDS = new RegExp(
`^(?:me|us|it|this|that|them|with|using|through|via|visually|and|or|a|an|the|to|for|on|images?|pictures?|pics?|photos?|diagrams?|figures?|illustrations?|visuals?|visual\\s+examples?|visual\\s+aids?|charts?|schematics?|\\s)*$`,
"i"
);
function isRealTopic(text: string): boolean {
const trimmed = text.trim();
if (trimmed.length < 2) return false;
return !NON_TOPIC_WORDS.test(trimmed);
}
function extractTopicSubject(text: string): { hasTopic: boolean; topic?: string } {
const sentences = text.split(/[?.!]/).map(s => s.trim()).filter(s => s.length > 0);
if (sentences.length > 1) {
for (const sentence of sentences) {
const topicMatch = sentence.match(/\b(?:what\s+is|how\s+does|about|regarding|on)\s+([a-z0-9\s-]+)/i);
if (topicMatch && isRealTopic(topicMatch[1])) {
return { hasTopic: true, topic: topicMatch[1].trim() };
}
}
}
const inlineMatch = text.match(
/\b(?:explain|explaining|teach|teaching|understand|understanding|walk(?:\s+me)?\s+through|break(?:\s+it|\s+this|\s+that)?\s+down|learn|learning|clarify|demystify|illustrate|describe)\s+([\w\s,-]+?)\s+(?:with|using|through|via|accompanied\s+by|including|supported\s+by|featuring|supplemented\s+with|utilizing|showing|visually)\b/i
);
if (inlineMatch && isRealTopic(inlineMatch[1])) {
return { hasTopic: true, topic: inlineMatch[1].trim() };
}
const visualFirstMatch = text.match(
new RegExp(
`\\b(?:using|with|via|through|showing|provide|providing|use|include|including|show)\\s+[\\w\\s,-]*?\\b(?:${VISUAL_INTENT_PATTERN.source})\\b\\s*(?:,?\\s*while|,?\\s*to|,?\\s*for|,?\\s*and|,?\\s*)\\s*(?:explain|explaining|teach|teaching|understand|understanding|walk(?:\\s+me)?\\s+through|break(?:\\s+down)?|clarify|illustrate|describe)\\s+([\\w\\s,-]+)`,
"i"
)
);
if (visualFirstMatch && isRealTopic(visualFirstMatch[1])) {
return { hasTopic: true, topic: visualFirstMatch[1].trim() };
}
const suffixMatch = text.match(
new RegExp(
`\\b(?:explain|teach|understand|illustrate|describe)\\s+(?:with|using|through|via)\\s+[\\w\\s,-]*?\\b(?:${VISUAL_INTENT_PATTERN.source})\\b\\s+([\\w\\s,-]+)`,
"i"
)
);
if (suffixMatch && isRealTopic(suffixMatch[1])) {
return { hasTopic: true, topic: suffixMatch[1].trim() };
}
return { hasTopic: false };
}
export function requiresImageContext(userText: string, requestId?: string): boolean {
const text = typeof userText === "string" ? userText.trim() : "";
const expMatch = text ? EXPLANATION_INTENT_PATTERN.exec(text) : null;
const hasExplanationIntent = expMatch !== null;
const visMatch = text ? VISUAL_INTENT_PATTERN.exec(text) : null;
const hasVisualIntent = visMatch !== null;
const topicRes = text ? extractTopicSubject(text) : { hasTopic: false };
const isPureImageSearch = text ? PURE_IMAGE_SEARCH_PATTERN.test(text) && !hasExplanationIntent : false;
const hasVisuallyAdverb = text ? /\bvisually\b/i.test(text) : false;
const hasConnectedVisual = text
? new RegExp(
`(?:${VISUAL_CONNECTORS_PATTERN.source})\\s+[\\w\\s,-]*?${VISUAL_INTENT_PATTERN.source}`,
"i"
).test(text)
: false;
const hasVisualAction = text
? new RegExp(
`\\b(?:use|using|include|including|provide|providing|add|show|with|via)\\s+[\\w\\s,-]*?\\b(?:${VISUAL_INTENT_PATTERN.source})\\b`,
"i"
).test(text)
: false;
return (
text.length > 0 &&
hasExplanationIntent &&
hasVisualIntent &&
topicRes.hasTopic &&
!isPureImageSearch &&
(hasVisuallyAdverb || hasConnectedVisual || hasVisualAction)
);
}
/**
* Image Context Detector
*
* Deterministically evaluates whether a user prompt requests explanatory/learning content
* combined with visual materials (images, diagrams, pictures, visual aids, etc.) for a topic.
*
* Intent combination required:
* [Explanation / Learning Intent] + [Visual Material Intent] + [Topic]
*/
const EXPLANATION_INTENT_PATTERN =
/\b(?:explain|explaining|explanation|teach|teaching|understand|understanding|walk(?:\s+me)?\s+through|break(?:\s+it|\s+this|\s+that)?\s+down|breaking\s+down|learn|learning|clarify|demystify|illustrate|describe|overview|deep\s+dive|how\s+does\s+.+?\s+work|what\s+is\s+.+?\??)\b/i;
const VISUAL_INTENT_PATTERN =
/\b(?:visually|images?|pics?|pictures?|photos?|diagrams?|figures?|illustrations?|visuals?|visual\s+examples?|visual\s+aids?|visual\s+materials?|visual\s+representations?|infographics?|charts?|flowcharts?|schematics?|drawings?)\b/i;
const VISUAL_CONNECTORS_PATTERN =
/\b(?:with|using|through|via|accompanied\s+by|including|supported\s+by|featuring|supplemented\s+with|utilizing|showing)\b/i;
const PURE_IMAGE_SEARCH_PATTERN =
/^(?:show\s+me|find|search\s+for|get|fetch|display|look\s+up|bring\s+up|give\s+me|can\s+you\s+(?:find|show|get)|please\s+(?:show|find))\s+(?:images?|pictures?|pics?|photos?|diagrams?|figures?|illustrations?|drawings?|charts?|schematics?|visuals?)\s+(?:of|for|about|on)\s+[\w\s-]+[?!.]?$/i;
const NON_TOPIC_WORDS = new RegExp(
`^(?:me|us|it|this|that|them|with|using|through|via|visually|and|or|a|an|the|to|for|on|images?|pictures?|pics?|photos?|diagrams?|figures?|illustrations?|visuals?|visual\\s+examples?|visual\\s+aids?|charts?|schematics?|\\s)*$`,
"i"
);
function isRealTopic(text: string): boolean {
const trimmed = text.trim();
if (trimmed.length < 2) return false;
return !NON_TOPIC_WORDS.test(trimmed);
}
function extractTopicSubject(text: string): { hasTopic: boolean; topic?: string } {
const sentences = text.split(/[?.!]/).map(s => s.trim()).filter(s => s.length > 0);
if (sentences.length > 1) {
for (const sentence of sentences) {
const topicMatch = sentence.match(/\b(?:what\s+is|how\s+does|about|regarding|on)\s+([a-z0-9\s-]+)/i);
if (topicMatch && isRealTopic(topicMatch[1])) {
return { hasTopic: true, topic: topicMatch[1].trim() };
}
}
}
const inlineMatch = text.match(
/\b(?:explain|explaining|teach|teaching|understand|understanding|walk(?:\s+me)?\s+through|break(?:\s+it|\s+this|\s+that)?\s+down|learn|learning|clarify|demystify|illustrate|describe)\s+([\w\s,-]+?)\s+(?:with|using|through|via|accompanied\s+by|including|supported\s+by|featuring|supplemented\s+with|utilizing|showing|visually)\b/i
);
if (inlineMatch && isRealTopic(inlineMatch[1])) {
return { hasTopic: true, topic: inlineMatch[1].trim() };
}
const visualFirstMatch = text.match(
new RegExp(
`\\b(?:using|with|via|through|showing|provide|providing|use|include|including|show)\\s+[\\w\\s,-]*?\\b(?:${VISUAL_INTENT_PATTERN.source})\\b\\s*(?:,?\\s*while|,?\\s*to|,?\\s*for|,?\\s*and|,?\\s*)\\s*(?:explain|explaining|teach|teaching|understand|understanding|walk(?:\\s+me)?\\s+through|break(?:\\s+down)?|clarify|illustrate|describe)\\s+([\\w\\s,-]+)`,
"i"
)
);
if (visualFirstMatch && isRealTopic(visualFirstMatch[1])) {
return { hasTopic: true, topic: visualFirstMatch[1].trim() };
}
const suffixMatch = text.match(
new RegExp(
`\\b(?:explain|teach|understand|illustrate|describe)\\s+(?:with|using|through|via)\\s+[\\w\\s,-]*?\\b(?:${VISUAL_INTENT_PATTERN.source})\\b\\s+([\\w\\s,-]+)`,
"i"
)
);
if (suffixMatch && isRealTopic(suffixMatch[1])) {
return { hasTopic: true, topic: suffixMatch[1].trim() };
}
return { hasTopic: false };
}
export function requiresImageContext(userText: string, requestId?: string): boolean {
const text = typeof userText === "string" ? userText.trim() : "";
const expMatch = text ? EXPLANATION_INTENT_PATTERN.exec(text) : null;
const hasExplanationIntent = expMatch !== null;
const visMatch = text ? VISUAL_INTENT_PATTERN.exec(text) : null;
const hasVisualIntent = visMatch !== null;
const topicRes = text ? extractTopicSubject(text) : { hasTopic: false };
const isPureImageSearch = text ? PURE_IMAGE_SEARCH_PATTERN.test(text) && !hasExplanationIntent : false;
const hasVisuallyAdverb = text ? /\bvisually\b/i.test(text) : false;
const hasConnectedVisual = text
? new RegExp(
`(?:${VISUAL_CONNECTORS_PATTERN.source})\\s+[\\w\\s,-]*?${VISUAL_INTENT_PATTERN.source}`,
"i"
).test(text)
: false;
const hasVisualAction = text
? new RegExp(
`\\b(?:use|using|include|including|provide|providing|add|show|with|via)\\s+[\\w\\s,-]*?\\b(?:${VISUAL_INTENT_PATTERN.source})\\b`,
"i"
).test(text)
: false;
return (
text.length > 0 &&
hasExplanationIntent &&
hasVisualIntent &&
topicRes.hasTopic &&
!isPureImageSearch &&
(hasVisuallyAdverb || hasConnectedVisual || hasVisualAction)
);
}