bookwiz.io / lib / utils / chatMessageUtils.ts
chatMessageUtils.ts
Raw
import { MessageUI } from '@/lib/types/database'
import { ToolResult } from '@/lib/services/tool-executor'

export interface ContextInfo {
  filesFound: number
  fileNames: string[]
}

export interface StreamingActivity {
  planning: string
  currentExecution: {
    name: string
    description: string
    args?: any
  } | null
  toolResults: Array<{
    name: string
    result: string
    success: boolean
  }>
}

/**
 * Generate a summary of tool operations for display
 */
export function generateToolSummary(results: ToolResult[]): string {
  let summary = "๐Ÿ› ๏ธ **File Operations Completed:**\n\n"
  results.forEach((result, i) => {
    if (result.name === 'search_files') {
      summary += `${i + 1}. ๐Ÿ” **Search Files**: Found ${(result as any).result?.length || 0} file(s)\n`
    } else if (result.name === 'read_file') {
      summary += `${i + 1}. ๐Ÿ“– **Read File**: ${(result as any).result?.name || 'file'}\n`
    } else if (result.name === 'update_file') {
      summary += `${i + 1}. โœ๏ธ **Updated File**: ${(result as any).result?.name || 'file'}\n   ๐Ÿ“ ${(result as any).result?.change_summary || 'updated'}\n`
    }
  })
  return summary
}

/**
 * Build comprehensive message content that includes all AI activity
 */
export function buildComprehensiveMessageContent(
  finalResponse: string,
  streamingActivity: StreamingActivity
): string {
  let comprehensiveContent = ''
  
  // Add planning phase if it occurred
  if (streamingActivity.planning) {
    comprehensiveContent += `๐Ÿ“‹ **AI Planning:** ${streamingActivity.planning}\n\n`
  }
  
  // Add tool execution results if any
  if (streamingActivity.toolResults.length > 0) {
    comprehensiveContent += `๐Ÿ› ๏ธ **Actions Performed:**\n`
    streamingActivity.toolResults.forEach((result, index) => {
      const status = result.success ? 'โœ…' : 'โŒ'
      comprehensiveContent += `${status} **${result.name}:** ${result.result}\n`
    })
    comprehensiveContent += '\n'
  }
  
  // Add the main AI response
  if (finalResponse.trim()) {
    comprehensiveContent += finalResponse
  }
  
  return comprehensiveContent
}

/**
 * Build content for stopped/interrupted execution
 */
export function buildStoppedMessageContent(
  partialContent: string,
  streamingActivity: StreamingActivity
): string {
  let stoppedContent = ''
  
  // Add planning phase if it occurred
  if (streamingActivity.planning) {
    stoppedContent += `๐Ÿ“‹ **AI Planning:** ${streamingActivity.planning}\n\n`
  }
  
  // Add current tool execution if it was in progress
  if (streamingActivity.currentExecution) {
    stoppedContent += `โš™๏ธ **Was Executing:** ${streamingActivity.currentExecution.description} *(interrupted)*\n\n`
  }
  
  // Add completed tool execution results if any
  if (streamingActivity.toolResults.length > 0) {
    stoppedContent += `๐Ÿ› ๏ธ **Actions Performed Before Stop:**\n`
    streamingActivity.toolResults.forEach((result, index) => {
      const status = result.success ? 'โœ…' : 'โŒ'
      stoppedContent += `${status} **${result.name}:** ${result.result}\n`
    })
    stoppedContent += '\n'
  }
  
  // Add the partial content
  if (partialContent) {
    stoppedContent += partialContent + '\n\n'
  }
  
  // Add stop indicator
  stoppedContent += 'โน๏ธ *Execution stopped by user*'
  
  return stoppedContent
}

/**
 * Compose all display messages including streaming content
 */
export function composeDisplayMessages(
  messages: MessageUI[],
  isStreaming: boolean,
  streamingMessage: string,
  aiPlanning: string,
  currentToolExecution: {
    name: string
    description: string
    args?: any
  } | null,
  toolExecutionResults: Array<{
    name: string
    result: string
    success: boolean
  }>
): (MessageUI & { _isStreaming?: boolean })[] {
  // Enhanced message composition that avoids duplicates
  const baseMessages = [...messages]
  
  // Only add streaming message if we have content and are actively streaming
  if (isStreaming && (streamingMessage || aiPlanning || currentToolExecution)) {
    // Create a comprehensive streaming message that includes all current AI activity
    let streamingContent = ''
    
    if (aiPlanning) {
      streamingContent += `๐Ÿ“‹ **Planning:** ${aiPlanning}\n\n`
    }
    
    if (currentToolExecution) {
      streamingContent += `โš™๏ธ **Executing:** ${currentToolExecution.description}\n\n`
    }
    
    // Add tool execution results
    if (toolExecutionResults.length > 0) {
      streamingContent += `๐Ÿ“Š **Tool Results:**\n`
      toolExecutionResults.forEach((result, index) => {
        const status = result.success ? 'โœ…' : 'โŒ'
        streamingContent += `${status} ${result.name}: ${result.result}\n`
      })
      streamingContent += '\n'
    }
    
    // Add the main content with proper separation
    if (streamingMessage) {
      // If we have tool activity and then content, add proper separation
      if ((aiPlanning || currentToolExecution || toolExecutionResults.length > 0) && !streamingMessage.startsWith('\n')) {
        streamingContent += '\n'
      }
      streamingContent += streamingMessage
    }
    
    // Only add streaming message if we have any content
    if (streamingContent.trim()) {
      baseMessages.push({
        id: 'streaming-temp', // Use a consistent ID to prevent React key issues
        type: 'ai' as const,
        content: streamingContent,
        timestamp: new Date(),
        // Add temporary property for streaming identification
        _isStreaming: true
      } as MessageUI & { _isStreaming: boolean })
    }
  }
  
  return baseMessages
}

/**
 * Check if tool results contain file-modifying operations
 */
export function hasFileModifyingOperations(toolResults: ToolResult[]): boolean {
  return toolResults.some((result: ToolResult) => 
    ['update_file', 'create_file', 'delete_file', 'move_file'].includes(result.name)
  )
}

/**
 * Create error message for display
 */
export function createErrorMessage(): MessageUI {
  return {
    id: Date.now() + 1,
    type: 'ai',
    content: 'Sorry, I encountered an error while processing your request. Please try again.',
    timestamp: new Date()
  }
}

// Shared utility for deleting messages
export const deleteMessage = async (messageId: string): Promise<void> => {
  // Get auth headers with session token
  const { data: { session } } = await import('@/lib/supabase').then(m => m.supabase.auth.getSession())
  const headers: Record<string, string> = { 'Content-Type': 'application/json' }
  
  if (session?.access_token) {
    headers['Authorization'] = `Bearer ${session.access_token}`
  }

  const response = await fetch(`/api/chat/messages/${messageId}`, {
    method: 'DELETE',
    headers,
  })

  if (!response.ok) {
    const errorData = await response.json()
    throw new Error(errorData.error || 'Failed to delete message')
  }
}