mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Works?
This commit is contained in:
@@ -75,8 +75,28 @@ export async function POST(req: NextRequest) {
|
||||
userId: session.user.id,
|
||||
})
|
||||
|
||||
// Handle streaming response
|
||||
// Handle streaming response (ReadableStream or StreamingExecution)
|
||||
let streamToRead: ReadableStream | null = null
|
||||
|
||||
// Debug logging to see what we actually got
|
||||
logger.info(`[${requestId}] Response type analysis:`, {
|
||||
responseType: typeof result.response,
|
||||
isReadableStream: result.response instanceof ReadableStream,
|
||||
hasStreamProperty: typeof result.response === 'object' && result.response && 'stream' in result.response,
|
||||
hasExecutionProperty: typeof result.response === 'object' && result.response && 'execution' in result.response,
|
||||
responseKeys: typeof result.response === 'object' && result.response ? Object.keys(result.response) : [],
|
||||
})
|
||||
|
||||
if (result.response instanceof ReadableStream) {
|
||||
logger.info(`[${requestId}] Direct ReadableStream detected`)
|
||||
streamToRead = result.response
|
||||
} else if (typeof result.response === 'object' && result.response && 'stream' in result.response && 'execution' in result.response) {
|
||||
// Handle StreamingExecution (from providers with tool calls)
|
||||
logger.info(`[${requestId}] StreamingExecution detected`)
|
||||
streamToRead = (result.response as any).stream
|
||||
}
|
||||
|
||||
if (streamToRead) {
|
||||
logger.info(`[${requestId}] Returning streaming response`)
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
@@ -84,7 +104,7 @@ export async function POST(req: NextRequest) {
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
const reader = (result.response as ReadableStream).getReader()
|
||||
const reader = streamToRead!.getReader()
|
||||
let accumulatedResponse = ''
|
||||
|
||||
// Send initial metadata
|
||||
|
||||
@@ -1,138 +1,41 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { searchDocumentation } from '@/lib/copilot/service'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { generateEmbeddings } from '@/app/api/knowledge/utils'
|
||||
import { db } from '@/db'
|
||||
import { docsEmbeddings } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('DocsSearch')
|
||||
const logger = createLogger('DocsSearchAPI')
|
||||
|
||||
const DocsSearchSchema = z.object({
|
||||
const SearchSchema = z.object({
|
||||
query: z.string().min(1, 'Query is required'),
|
||||
topK: z.number().min(1).max(10).default(5),
|
||||
topK: z.number().min(1).max(20).default(5),
|
||||
})
|
||||
|
||||
/**
|
||||
* Generate embedding for search query
|
||||
*/
|
||||
async function generateSearchEmbedding(query: string): Promise<number[]> {
|
||||
try {
|
||||
const embeddings = await generateEmbeddings([query])
|
||||
return embeddings[0] || []
|
||||
} catch (error) {
|
||||
logger.error('Failed to generate search embedding:', error)
|
||||
throw new Error('Failed to generate search embedding')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search docs embeddings using vector similarity
|
||||
*/
|
||||
async function searchDocs(queryEmbedding: number[], topK: number) {
|
||||
try {
|
||||
const results = await db
|
||||
.select({
|
||||
chunkId: docsEmbeddings.chunkId,
|
||||
chunkText: docsEmbeddings.chunkText,
|
||||
sourceDocument: docsEmbeddings.sourceDocument,
|
||||
sourceLink: docsEmbeddings.sourceLink,
|
||||
headerText: docsEmbeddings.headerText,
|
||||
headerLevel: docsEmbeddings.headerLevel,
|
||||
similarity: sql<number>`1 - (${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`,
|
||||
})
|
||||
.from(docsEmbeddings)
|
||||
.orderBy(sql`${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`)
|
||||
.limit(topK)
|
||||
|
||||
return results
|
||||
} catch (error) {
|
||||
logger.error('Failed to search docs:', error)
|
||||
throw new Error('Failed to search docs')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/docs/search
|
||||
* Search Sim Studio documentation using vector similarity
|
||||
* Search documentation for copilot tools
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
const requestId = crypto.randomUUID()
|
||||
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { query, topK } = DocsSearchSchema.parse(body)
|
||||
const { query, topK } = SearchSchema.parse(body)
|
||||
|
||||
logger.info(`[${requestId}] 🔍 DOCS SEARCH TOOL CALLED - Query: "${query}"`, { topK })
|
||||
logger.info(`[${requestId}] Documentation search request: "${query}"`, { topK })
|
||||
|
||||
// Step 1: Generate embedding for the query
|
||||
logger.info(`[${requestId}] Generating query embedding...`)
|
||||
const queryEmbedding = await generateSearchEmbedding(query)
|
||||
const results = await searchDocumentation(query, { topK })
|
||||
|
||||
if (queryEmbedding.length === 0) {
|
||||
return NextResponse.json({ error: 'Failed to generate query embedding' }, { status: 500 })
|
||||
}
|
||||
|
||||
// Step 2: Search for relevant docs chunks
|
||||
logger.info(`[${requestId}] Searching docs for top ${topK} chunks...`)
|
||||
const chunks = await searchDocs(queryEmbedding, topK)
|
||||
|
||||
if (chunks.length === 0) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
response: "I couldn't find any relevant documentation for that query.",
|
||||
sources: [],
|
||||
metadata: {
|
||||
requestId,
|
||||
chunksFound: 0,
|
||||
query,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Step 3: Format the response with context and sources
|
||||
const context = chunks
|
||||
.map((chunk, index) => {
|
||||
const headerText =
|
||||
typeof chunk.headerText === 'string'
|
||||
? chunk.headerText
|
||||
: String(chunk.headerText || 'Untitled Section')
|
||||
const sourceDocument =
|
||||
typeof chunk.sourceDocument === 'string'
|
||||
? chunk.sourceDocument
|
||||
: String(chunk.sourceDocument || 'Unknown Document')
|
||||
const sourceLink =
|
||||
typeof chunk.sourceLink === 'string' ? chunk.sourceLink : String(chunk.sourceLink || '#')
|
||||
const chunkText =
|
||||
typeof chunk.chunkText === 'string' ? chunk.chunkText : String(chunk.chunkText || '')
|
||||
|
||||
return `[${index + 1}] ${headerText}
|
||||
Document: ${sourceDocument}
|
||||
URL: ${sourceLink}
|
||||
Content: ${chunkText}`
|
||||
})
|
||||
.join('\n\n')
|
||||
|
||||
// Step 4: Format sources for response
|
||||
const sources = chunks.map((chunk, index) => ({
|
||||
id: index + 1,
|
||||
title: chunk.headerText,
|
||||
document: chunk.sourceDocument,
|
||||
link: chunk.sourceLink,
|
||||
similarity: Math.round(chunk.similarity * 100) / 100,
|
||||
}))
|
||||
|
||||
logger.info(`[${requestId}] Found ${chunks.length} relevant chunks`)
|
||||
logger.info(`[${requestId}] Found ${results.length} documentation results`, { query })
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
response: context,
|
||||
sources,
|
||||
results,
|
||||
query,
|
||||
totalResults: results.length,
|
||||
metadata: {
|
||||
requestId,
|
||||
chunksFound: chunks.length,
|
||||
query,
|
||||
topSimilarity: sources[0]?.similarity,
|
||||
topK,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
@@ -143,7 +46,13 @@ Content: ${chunkText}`
|
||||
)
|
||||
}
|
||||
|
||||
logger.error(`[${requestId}] Docs search error:`, error)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
logger.error(`[${requestId}] Documentation search error:`, error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to search documentation',
|
||||
details: error instanceof Error ? error.message : 'Unknown error'
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(
|
||||
selectChat,
|
||||
createNewChat,
|
||||
deleteChat,
|
||||
sendDocsMessage,
|
||||
sendMessage,
|
||||
clearMessages,
|
||||
clearError,
|
||||
} = useCopilotStore()
|
||||
@@ -138,13 +138,13 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(
|
||||
}
|
||||
|
||||
try {
|
||||
await sendDocsMessage(query, { stream: true })
|
||||
logger.info('Sent docs query:', query)
|
||||
await sendMessage(query, { stream: true })
|
||||
logger.info('Sent message:', query)
|
||||
} catch (error) {
|
||||
logger.error('Failed to send docs message:', error)
|
||||
logger.error('Failed to send message:', error)
|
||||
}
|
||||
},
|
||||
[isSendingMessage, activeWorkflowId, sendDocsMessage]
|
||||
[isSendingMessage, activeWorkflowId, sendMessage]
|
||||
)
|
||||
|
||||
// Format timestamp for display
|
||||
|
||||
@@ -251,26 +251,44 @@ export async function sendStreamingMessage(request: SendMessageRequest): Promise
|
||||
error?: string
|
||||
}> {
|
||||
try {
|
||||
console.log('[CopilotAPI] Sending streaming message request:', {
|
||||
message: request.message,
|
||||
stream: true,
|
||||
hasWorkflowId: !!request.workflowId
|
||||
})
|
||||
|
||||
const response = await fetch('/api/copilot', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...request, stream: true }),
|
||||
})
|
||||
|
||||
console.log('[CopilotAPI] Fetch response received:', {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
hasBody: !!response.body,
|
||||
contentType: response.headers.get('content-type')
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
console.error('[CopilotAPI] Error response:', errorData)
|
||||
throw new Error(errorData.error || 'Failed to send streaming message')
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
console.error('[CopilotAPI] No response body received')
|
||||
throw new Error('No response body received')
|
||||
}
|
||||
|
||||
console.log('[CopilotAPI] Successfully received stream')
|
||||
return {
|
||||
success: true,
|
||||
stream: response.body,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[CopilotAPI] Failed to send streaming message:', error)
|
||||
logger.error('Failed to send streaming message:', error)
|
||||
return {
|
||||
success: false,
|
||||
@@ -332,26 +350,40 @@ export async function sendStreamingDocsMessage(request: DocsQueryRequest): Promi
|
||||
error?: string
|
||||
}> {
|
||||
try {
|
||||
console.log('[CopilotAPI] sendStreamingDocsMessage called with:', request)
|
||||
|
||||
const response = await fetch('/api/copilot/docs', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...request, stream: true }),
|
||||
})
|
||||
|
||||
console.log('[CopilotAPI] Fetch response received:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
ok: response.ok,
|
||||
hasBody: !!response.body
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
console.error('[CopilotAPI] API error response:', errorData)
|
||||
throw new Error(errorData.error || 'Failed to send streaming docs message')
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
console.error('[CopilotAPI] No response body received')
|
||||
throw new Error('No response body received')
|
||||
}
|
||||
|
||||
console.log('[CopilotAPI] Returning successful result with stream')
|
||||
return {
|
||||
success: true,
|
||||
stream: response.body,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[CopilotAPI] Error in sendStreamingDocsMessage:', error)
|
||||
logger.error('Failed to send streaming docs message:', error)
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -81,7 +81,7 @@ IMPORTANT: Always provide complete, helpful responses. If you add citations, con
|
||||
maxTokens: 2000,
|
||||
embeddingModel: 'text-embedding-3-small',
|
||||
maxSources: 5,
|
||||
similarityThreshold: 0.7,
|
||||
similarityThreshold: 0.5,
|
||||
},
|
||||
general: {
|
||||
streamingEnabled: true,
|
||||
|
||||
@@ -75,7 +75,7 @@ export async function generateChatTitle(userMessage: string): Promise<string> {
|
||||
try {
|
||||
// Use rotating key directly for hosted providers
|
||||
if (provider === 'openai' || provider === 'anthropic') {
|
||||
const { getRotatingApiKey } = require('@/lib/utils')
|
||||
const { getRotatingApiKey } = await import('@/lib/utils')
|
||||
apiKey = getRotatingApiKey(provider)
|
||||
} else {
|
||||
apiKey = getApiKey(provider, model)
|
||||
@@ -125,9 +125,9 @@ export async function searchDocumentation(
|
||||
similarity: number
|
||||
}>
|
||||
> {
|
||||
const { generateEmbeddings } = require('@/app/api/knowledge/utils')
|
||||
const { docsEmbeddings } = require('@/db/schema')
|
||||
const { sql } = require('drizzle-orm')
|
||||
const { generateEmbeddings } = await import('@/app/api/knowledge/utils')
|
||||
const { docsEmbeddings } = await import('@/db/schema')
|
||||
const { sql } = await import('drizzle-orm')
|
||||
|
||||
const config = getCopilotConfig()
|
||||
const { topK = config.rag.maxSources, threshold = config.rag.similarityThreshold } = options
|
||||
@@ -221,7 +221,7 @@ export async function generateDocsResponse(
|
||||
try {
|
||||
// Use rotating key directly for hosted providers
|
||||
if (selectedProvider === 'openai' || selectedProvider === 'anthropic') {
|
||||
const { getRotatingApiKey } = require('@/lib/utils')
|
||||
const { getRotatingApiKey } = await import('@/lib/utils')
|
||||
apiKey = getRotatingApiKey(selectedProvider)
|
||||
} else {
|
||||
apiKey = getApiKey(selectedProvider, selectedModel)
|
||||
@@ -375,7 +375,7 @@ export async function generateChatResponse(
|
||||
try {
|
||||
// Use rotating key directly for hosted providers
|
||||
if (provider === 'openai' || provider === 'anthropic') {
|
||||
const { getRotatingApiKey } = require('@/lib/utils')
|
||||
const { getRotatingApiKey } = await import('@/lib/utils')
|
||||
apiKey = getRotatingApiKey(provider)
|
||||
} else {
|
||||
apiKey = getApiKey(provider, model)
|
||||
@@ -444,12 +444,31 @@ export async function generateChatResponse(
|
||||
stream,
|
||||
})
|
||||
|
||||
// Handle tool calls if needed (documentation search)
|
||||
if (typeof response === 'object' && 'content' in response) {
|
||||
return response.content || 'Sorry, I could not generate a response.'
|
||||
// Handle StreamingExecution (from providers with tool calls)
|
||||
if (typeof response === 'object' && response && 'stream' in response && 'execution' in response) {
|
||||
logger.info('Detected StreamingExecution from provider')
|
||||
return (response as any).stream
|
||||
}
|
||||
|
||||
// Handle streaming response
|
||||
// Handle ProviderResponse (non-streaming with tool calls)
|
||||
if (typeof response === 'object' && 'content' in response) {
|
||||
const content = response.content || 'Sorry, I could not generate a response.'
|
||||
|
||||
// If streaming was requested, wrap the content in a ReadableStream
|
||||
if (stream) {
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
const encoder = new TextEncoder()
|
||||
controller.enqueue(encoder.encode(content))
|
||||
controller.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
// Handle direct ReadableStream response
|
||||
if (response instanceof ReadableStream) {
|
||||
return response
|
||||
}
|
||||
|
||||
116
apps/sim/lib/copilot/tools.ts
Normal file
116
apps/sim/lib/copilot/tools.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { searchDocumentation } from './service'
|
||||
|
||||
const logger = createLogger('CopilotTools')
|
||||
|
||||
// Interface for copilot tool execution results
|
||||
export interface CopilotToolResult {
|
||||
success: boolean
|
||||
data?: any
|
||||
error?: string
|
||||
}
|
||||
|
||||
// Interface for copilot tool definitions
|
||||
export interface CopilotTool {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
parameters: {
|
||||
type: 'object'
|
||||
properties: Record<string, any>
|
||||
required: string[]
|
||||
}
|
||||
execute: (args: Record<string, any>) => Promise<CopilotToolResult>
|
||||
}
|
||||
|
||||
// Documentation search tool for copilot
|
||||
const docsSearchTool: CopilotTool = {
|
||||
id: 'docs_search_internal',
|
||||
name: 'Search Documentation',
|
||||
description: 'Search Sim Studio documentation for information about features, tools, workflows, and functionality',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: 'The search query to find relevant documentation',
|
||||
},
|
||||
topK: {
|
||||
type: 'number',
|
||||
description: 'Number of results to return (default: 5, max: 10)',
|
||||
default: 5,
|
||||
},
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
execute: async (args: Record<string, any>): Promise<CopilotToolResult> => {
|
||||
try {
|
||||
const { query, topK = 5 } = args
|
||||
|
||||
logger.info('Executing documentation search', { query, topK })
|
||||
|
||||
const results = await searchDocumentation(query, { topK })
|
||||
|
||||
logger.info(`Found ${results.length} documentation results`, { query })
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
results,
|
||||
query,
|
||||
totalResults: results.length,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Documentation search failed', error)
|
||||
return {
|
||||
success: false,
|
||||
error: `Documentation search failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Copilot tools registry
|
||||
const copilotTools: Record<string, CopilotTool> = {
|
||||
docs_search_internal: docsSearchTool,
|
||||
}
|
||||
|
||||
// Get a copilot tool by ID
|
||||
export function getCopilotTool(toolId: string): CopilotTool | undefined {
|
||||
return copilotTools[toolId]
|
||||
}
|
||||
|
||||
// Execute a copilot tool
|
||||
export async function executeCopilotTool(
|
||||
toolId: string,
|
||||
args: Record<string, any>
|
||||
): Promise<CopilotToolResult> {
|
||||
const tool = getCopilotTool(toolId)
|
||||
|
||||
if (!tool) {
|
||||
logger.error(`Copilot tool not found: ${toolId}`)
|
||||
return {
|
||||
success: false,
|
||||
error: `Tool not found: ${toolId}`,
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info(`Executing copilot tool: ${toolId}`, { args })
|
||||
const result = await tool.execute(args)
|
||||
logger.info(`Copilot tool execution completed: ${toolId}`, { success: result.success })
|
||||
return result
|
||||
} catch (error) {
|
||||
logger.error(`Copilot tool execution failed: ${toolId}`, error)
|
||||
return {
|
||||
success: false,
|
||||
error: `Tool execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all available copilot tools (for tool definitions in LLM requests)
|
||||
export function getAllCopilotTools(): CopilotTool[] {
|
||||
return Object.values(copilotTools)
|
||||
}
|
||||
@@ -196,7 +196,15 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
const { workflowId, currentChat } = get()
|
||||
const { stream = true } = options
|
||||
|
||||
console.log('[CopilotStore] sendMessage called:', {
|
||||
message,
|
||||
workflowId,
|
||||
hasCurrentChat: !!currentChat,
|
||||
stream
|
||||
})
|
||||
|
||||
if (!workflowId) {
|
||||
console.warn('[CopilotStore] No workflow ID set')
|
||||
logger.warn('Cannot send message: no workflow ID set')
|
||||
return
|
||||
}
|
||||
@@ -219,11 +227,17 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
|
||||
console.log('[CopilotStore] Adding messages to state:', {
|
||||
userMessageId: userMessage.id,
|
||||
streamingMessageId: streamingMessage.id
|
||||
})
|
||||
|
||||
set((state) => ({
|
||||
messages: [...state.messages, userMessage, streamingMessage],
|
||||
}))
|
||||
|
||||
try {
|
||||
console.log('[CopilotStore] Requesting streaming response')
|
||||
const result = await sendStreamingMessage({
|
||||
message,
|
||||
chatId: currentChat?.id,
|
||||
@@ -232,9 +246,18 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
stream,
|
||||
})
|
||||
|
||||
console.log('[CopilotStore] Streaming result:', {
|
||||
success: result.success,
|
||||
hasStream: !!result.stream,
|
||||
error: result.error
|
||||
})
|
||||
|
||||
if (result.success && result.stream) {
|
||||
console.log('[CopilotStore] Starting stream processing')
|
||||
await get().handleStreamingResponse(result.stream, streamingMessage.id)
|
||||
console.log('[CopilotStore] Stream processing completed')
|
||||
} else {
|
||||
console.error('[CopilotStore] Stream request failed:', result.error)
|
||||
throw new Error(result.error || 'Failed to send message')
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -330,6 +353,8 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
|
||||
// Handle streaming response (shared by both message types)
|
||||
handleStreamingResponse: async (stream: ReadableStream, messageId: string) => {
|
||||
console.log('[CopilotStore] handleStreamingResponse started:', { messageId, hasStream: !!stream })
|
||||
|
||||
const reader = stream.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let accumulatedContent = ''
|
||||
@@ -340,6 +365,7 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
|
||||
if (done || streamComplete) break
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true })
|
||||
@@ -367,7 +393,9 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
}))
|
||||
}
|
||||
} else if (data.type === 'content') {
|
||||
console.log('[CopilotStore] Received content chunk:', data.content)
|
||||
accumulatedContent += data.content
|
||||
console.log('[CopilotStore] Accumulated content length:', accumulatedContent.length)
|
||||
|
||||
// Update the streaming message
|
||||
set((state) => ({
|
||||
@@ -382,7 +410,9 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
: msg
|
||||
),
|
||||
}))
|
||||
console.log('[CopilotStore] Updated message state with content')
|
||||
} else if (data.type === 'done' || data.type === 'complete') {
|
||||
console.log('[CopilotStore] Received completion marker:', data.type)
|
||||
// Final update
|
||||
set((state) => ({
|
||||
messages: state.messages.map((msg) =>
|
||||
@@ -400,24 +430,32 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
|
||||
// Handle new chat creation
|
||||
if (newChatId && !get().currentChat) {
|
||||
console.log('[CopilotStore] Reloading chats for new chat:', newChatId)
|
||||
// Reload chats to get the updated list
|
||||
await get().loadChats()
|
||||
}
|
||||
|
||||
streamComplete = true
|
||||
console.log('[CopilotStore] Stream marked as complete')
|
||||
break
|
||||
} else if (data.type === 'error') {
|
||||
console.error('[CopilotStore] Received error from stream:', data.error)
|
||||
throw new Error(data.error || 'Streaming error')
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.warn('[CopilotStore] Failed to parse SSE data:', parseError, 'Line:', line)
|
||||
logger.warn('Failed to parse SSE data:', parseError)
|
||||
}
|
||||
} else if (line.trim()) {
|
||||
console.log('[CopilotStore] Non-SSE line (ignored):', line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[CopilotStore] Stream processing completed successfully')
|
||||
logger.info(`Completed streaming response, content length: ${accumulatedContent.length}`)
|
||||
} catch (error) {
|
||||
console.error('[CopilotStore] Error handling streaming response:', error)
|
||||
logger.error('Error handling streaming response:', error)
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -6,23 +6,21 @@ export interface DocsSearchParams {
|
||||
}
|
||||
|
||||
export interface DocsSearchResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
response: string
|
||||
sources: Array<{
|
||||
title: string
|
||||
document: string
|
||||
link: string
|
||||
similarity: number
|
||||
}>
|
||||
}
|
||||
error?: string
|
||||
results: Array<{
|
||||
id: number
|
||||
title: string
|
||||
url: string
|
||||
content: string
|
||||
similarity: number
|
||||
}>
|
||||
query: string
|
||||
totalResults: number
|
||||
}
|
||||
|
||||
export const docsSearchTool: ToolConfig<DocsSearchParams, DocsSearchResponse> = {
|
||||
id: 'docs_search_internal',
|
||||
name: 'Search Documentation',
|
||||
description: 'Search Sim Studio documentation using vector similarity search',
|
||||
description: 'Search Sim Studio documentation for information about features, tools, workflows, and functionality',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
@@ -35,6 +33,7 @@ export const docsSearchTool: ToolConfig<DocsSearchParams, DocsSearchResponse> =
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Number of results to return (default: 5, max: 10)',
|
||||
default: 5,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -51,25 +50,24 @@ export const docsSearchTool: ToolConfig<DocsSearchParams, DocsSearchResponse> =
|
||||
isInternalRoute: true,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
transformResponse: async (response: Response): Promise<any> => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to search documentation')
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: data.error || 'Failed to search documentation',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
response: data.response,
|
||||
sources: data.sources || [],
|
||||
results: data.results || [],
|
||||
query: data.query || '',
|
||||
totalResults: data.totalResults || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
transformError: (error) => {
|
||||
return error instanceof Error
|
||||
? error.message
|
||||
: 'An error occurred while searching documentation'
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user