Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/features/ai-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { config } from '../config';
import { createProvider } from './llm/provider-factory';
import { HistoryBuilder } from './history-builder';
import { executeTool } from './tool-executor';
import { markdownToSlack } from './utils';
import { markdownToSlack, isBinaryQuestion } from './utils';
import { Content, Part } from '@google/generative-ai';
import { LLMMessage, LLMTool, LLMToolCall } from './llm/providers/types';
import { registerEventListeners } from '../listeners/events';
Expand Down Expand Up @@ -553,9 +553,15 @@ If the user asks for a summary or current state, base it ONLY on the saved RPG c
if (calledUpdateRpg) msg.push('game state updated');
if (calledGenerateImage) msg.push('image request submitted');
const narrative = (currentResponse.text || '').trim();

// Check if it's a binary question
const currentQuestionText = typeof question === 'string' ? question : '';
const isBinary = isBinaryQuestion(currentQuestionText);
const binaryPrefix = isBinary ? 'Yes. ' : '';

const confirmation = narrative
? `Acknowledged: ${msg.join(' and ')}.\n\n${narrative} `
: `Acknowledged: ${msg.join(' and ')}.`;
? `${binaryPrefix}Acknowledged: ${msg.join(' and ')}.\n\n${narrative} `
: `${binaryPrefix}Acknowledged: ${msg.join(' and ')}.`;
return { text: confirmation, confidence: 1.0, totalTokens: totalTokens };
}

Expand All @@ -572,7 +578,7 @@ If the user asks for a summary or current state, base it ONLY on the saved RPG c
break;
}

const summary = await this.summarizer!.summarizeText(fetchedContent, typeof question === 'string' ? question : 'Image analysis request');
const summary = await this.summarizer!.summarizeText(fetchedContent, typeof question === 'string' ? question : 'Image analysis request', finalPrompt);
finalNarrative += (summary || '').trim() + '\n\n';
alreadySummarized = true;

Expand Down
4 changes: 2 additions & 2 deletions src/features/summarizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ ${conversationText}`;
}
}

public async summarizeText(text: string, originalQuestion: string): Promise<string> {
public async summarizeText(text: string, originalQuestion: string, systemPrompt?: string): Promise<string> {
// Simple character-based chunking with a safety margin.
const chunkSize = Math.floor(this.config.openai.maxContextSize * 2.5); // 2.5 chars/token is a safer estimate
const chunks: string[] = [];
Expand Down Expand Up @@ -112,7 +112,7 @@ ${conversationText}`;
const finalPrompt = `Based on the following summaries of a document, please provide a final answer to the user's original question.\n\nOriginal question: ${originalQuestion}\n\nSummaries:\n---\n${combinedSummaries}\n---`;

const finalResult = await this.provider.chat(finalPrompt, {
systemPrompt: "You are a helpful assistant that synthesizes information."
systemPrompt: systemPrompt || "You are a helpful assistant that synthesizes information."
});

return finalResult.text;
Expand Down
20 changes: 20 additions & 0 deletions src/features/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,24 @@ export function markdownToSlack(text: string): string {
out = out.replace(/^(\s*)([*+-])\s+/gm, '$1• ');

return out;
}

/**
* Detects if a question is a binary (Yes/No) question.
* @param text The question text, potentially containing Slack scaffolding.
*/
export function isBinaryQuestion(text: string): boolean {
if (!text) return false;

// 1. Strip Slack scaffolding: channel_id: ... | message: ...
const messageMatch = text.match(/\| message: (.*)$/);
const cleanedText = messageMatch ? messageMatch[1].trim() : text.trim();

if (!cleanedText) return false;

// 2. Check if the first word matches the list from the system prompt
const firstWord = cleanedText.split(/\s+/)[0].replace(/[^a-zA-Z]/g, '').toLowerCase();
const binaryIndicators = ['is', 'are', 'do', 'does', 'can', 'will', 'should', 'would', 'has', 'have', 'am'];

return binaryIndicators.includes(firstWord);
}