/ src / server / chat / slash-command-web-handlers.ts
slash-command-web-handlers.ts
  1  import type { DuckDuckGoSearchType } from '@/lib/shared/chat'
  2  import type { CommandExecutionTextResult } from '@/lib/shared/command-execution'
  3  import {
  4    executeWebFetchTool,
  5    executeWebSearchTool,
  6  } from '@/server/tools/web-tool-execution'
  7  
  8  export async function runWebSearchSlashCommand(input: {
  9    query: string
 10    searchType?: DuckDuckGoSearchType
 11    attachmentNote: string
 12  }): Promise<CommandExecutionTextResult> {
 13    const execution = await executeWebSearchTool({
 14      query: input.query,
 15      searchType: input.searchType,
 16    })
 17  
 18    if (!execution.ok) {
 19      return {
 20        text: `Web search failed: ${execution.error}${input.attachmentNote}`,
 21        provider: 'web-search',
 22        mocked: false,
 23      }
 24    }
 25  
 26    const lines: string[] = []
 27    const typeLabel =
 28      input.searchType && input.searchType !== 'web'
 29        ? ` (${input.searchType})`
 30        : ''
 31    lines.push(`**Web Search Results**${typeLabel} (${execution.response.provider})`)
 32    lines.push('')
 33  
 34    for (const result of execution.results.slice(0, 5)) {
 35      lines.push(`- **${result.title}**`)
 36      lines.push(`  ${result.url}`)
 37      if (result.imageUrl) {
 38        lines.push(`  ![image](${result.thumbnailUrl || result.imageUrl})`)
 39      }
 40      if (result.snippet) {
 41        lines.push(
 42          `  ${result.snippet.slice(0, 200)}${result.snippet.length > 200 ? '...' : ''}`,
 43        )
 44      }
 45      lines.push('')
 46    }
 47  
 48    if (execution.response.cached) {
 49      lines.push('*Results from cache*')
 50    }
 51  
 52    return {
 53      text: lines.join('\n') + input.attachmentNote,
 54      provider: 'web-search',
 55      mocked: false,
 56    }
 57  }
 58  
 59  export async function runWebFetchSlashCommand(input: {
 60    url: string
 61    attachmentNote: string
 62  }): Promise<CommandExecutionTextResult> {
 63    const execution = await executeWebFetchTool({ url: input.url })
 64  
 65    if (!execution.ok) {
 66      return {
 67        text: `Web fetch failed: ${execution.error}${input.attachmentNote}`,
 68        provider: 'web-fetch',
 69        mocked: false,
 70      }
 71    }
 72  
 73    const lines: string[] = []
 74    lines.push(`**${execution.response.result.title}**`)
 75    lines.push(`URL: ${execution.response.result.url}`)
 76    lines.push('')
 77  
 78    const contentPreview = execution.response.result.content.slice(0, 7500)
 79    lines.push(contentPreview)
 80  
 81    if (execution.response.result.content.length > 7500) {
 82      lines.push('')
 83      lines.push('*... content truncated*')
 84    }
 85  
 86    if (execution.response.result.truncated) {
 87      lines.push('')
 88      lines.push(
 89        `*Content was truncated to ${execution.response.result.bytesRead} bytes*`,
 90      )
 91    }
 92  
 93    if (execution.response.cached) {
 94      lines.push('')
 95      lines.push('*Results from cache*')
 96    }
 97  
 98    return {
 99      text: lines.join('\n') + input.attachmentNote,
100      provider: 'web-fetch',
101      mocked: false,
102    }
103  }
104  
105  export function buildWebHelpSlashCommandResult(input: {
106    attachmentNote: string
107  }): CommandExecutionTextResult {
108    const helpText = `**Web Tools Help**
109  
110  Available commands:
111  - \`/websearch <query>\` - Search the web for information (default)
112  - \`/websearch --image <query>\` - Search for images
113  - \`/websearch --news <query>\` - Search for news articles
114  - \`/websearch --video <query>\` - Search for videos
115  - \`/webfetch <url>\` - Fetch and extract content from a URL
116  - \`/help web\` - Show this help message
117  
118  Examples:
119  - \`/websearch current weather in San Francisco\`
120  - \`/websearch --image cute kittens\`
121  - \`/websearch --news technology\`
122  - \`/webfetch https://example.com\`
123  
124  Note: DuckDuckGo is used as the default provider (no API key required).${input.attachmentNote}`
125  
126    return {
127      text: helpText,
128      provider: 'web-help',
129      mocked: false,
130    }
131  }