Skip to content

Commit ccf5c2f

Browse files
committed
Works?
1 parent 02c4112 commit ccf5c2f

File tree

9 files changed

+283
-151
lines changed

9 files changed

+283
-151
lines changed

apps/sim/app/api/copilot/route.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,36 @@ export async function POST(req: NextRequest) {
7575
userId: session.user.id,
7676
})
7777

78-
// Handle streaming response
78+
// Handle streaming response (ReadableStream or StreamingExecution)
79+
let streamToRead: ReadableStream | null = null
80+
81+
// Debug logging to see what we actually got
82+
logger.info(`[${requestId}] Response type analysis:`, {
83+
responseType: typeof result.response,
84+
isReadableStream: result.response instanceof ReadableStream,
85+
hasStreamProperty: typeof result.response === 'object' && result.response && 'stream' in result.response,
86+
hasExecutionProperty: typeof result.response === 'object' && result.response && 'execution' in result.response,
87+
responseKeys: typeof result.response === 'object' && result.response ? Object.keys(result.response) : [],
88+
})
89+
7990
if (result.response instanceof ReadableStream) {
91+
logger.info(`[${requestId}] Direct ReadableStream detected`)
92+
streamToRead = result.response
93+
} else if (typeof result.response === 'object' && result.response && 'stream' in result.response && 'execution' in result.response) {
94+
// Handle StreamingExecution (from providers with tool calls)
95+
logger.info(`[${requestId}] StreamingExecution detected`)
96+
streamToRead = (result.response as any).stream
97+
}
98+
99+
if (streamToRead) {
80100
logger.info(`[${requestId}] Returning streaming response`)
81101

82102
const encoder = new TextEncoder()
83103

84104
return new Response(
85105
new ReadableStream({
86106
async start(controller) {
87-
const reader = (result.response as ReadableStream).getReader()
107+
const reader = streamToRead!.getReader()
88108
let accumulatedResponse = ''
89109

90110
// Send initial metadata
Lines changed: 21 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,41 @@
1-
import { sql } from 'drizzle-orm'
21
import { type NextRequest, NextResponse } from 'next/server'
32
import { z } from 'zod'
3+
import { searchDocumentation } from '@/lib/copilot/service'
44
import { createLogger } from '@/lib/logs/console-logger'
5-
import { generateEmbeddings } from '@/app/api/knowledge/utils'
6-
import { db } from '@/db'
7-
import { docsEmbeddings } from '@/db/schema'
85

9-
const logger = createLogger('DocsSearch')
6+
const logger = createLogger('DocsSearchAPI')
107

11-
const DocsSearchSchema = z.object({
8+
const SearchSchema = z.object({
129
query: z.string().min(1, 'Query is required'),
13-
topK: z.number().min(1).max(10).default(5),
10+
topK: z.number().min(1).max(20).default(5),
1411
})
1512

16-
/**
17-
* Generate embedding for search query
18-
*/
19-
async function generateSearchEmbedding(query: string): Promise<number[]> {
20-
try {
21-
const embeddings = await generateEmbeddings([query])
22-
return embeddings[0] || []
23-
} catch (error) {
24-
logger.error('Failed to generate search embedding:', error)
25-
throw new Error('Failed to generate search embedding')
26-
}
27-
}
28-
29-
/**
30-
* Search docs embeddings using vector similarity
31-
*/
32-
async function searchDocs(queryEmbedding: number[], topK: number) {
33-
try {
34-
const results = await db
35-
.select({
36-
chunkId: docsEmbeddings.chunkId,
37-
chunkText: docsEmbeddings.chunkText,
38-
sourceDocument: docsEmbeddings.sourceDocument,
39-
sourceLink: docsEmbeddings.sourceLink,
40-
headerText: docsEmbeddings.headerText,
41-
headerLevel: docsEmbeddings.headerLevel,
42-
similarity: sql<number>`1 - (${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`,
43-
})
44-
.from(docsEmbeddings)
45-
.orderBy(sql`${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`)
46-
.limit(topK)
47-
48-
return results
49-
} catch (error) {
50-
logger.error('Failed to search docs:', error)
51-
throw new Error('Failed to search docs')
52-
}
53-
}
54-
5513
/**
5614
* POST /api/docs/search
57-
* Search Sim Studio documentation using vector similarity
15+
* Search documentation for copilot tools
5816
*/
5917
export async function POST(req: NextRequest) {
6018
const requestId = crypto.randomUUID()
6119

6220
try {
6321
const body = await req.json()
64-
const { query, topK } = DocsSearchSchema.parse(body)
65-
66-
logger.info(`[${requestId}] 🔍 DOCS SEARCH TOOL CALLED - Query: "${query}"`, { topK })
67-
68-
// Step 1: Generate embedding for the query
69-
logger.info(`[${requestId}] Generating query embedding...`)
70-
const queryEmbedding = await generateSearchEmbedding(query)
71-
72-
if (queryEmbedding.length === 0) {
73-
return NextResponse.json({ error: 'Failed to generate query embedding' }, { status: 500 })
74-
}
75-
76-
// Step 2: Search for relevant docs chunks
77-
logger.info(`[${requestId}] Searching docs for top ${topK} chunks...`)
78-
const chunks = await searchDocs(queryEmbedding, topK)
79-
80-
if (chunks.length === 0) {
81-
return NextResponse.json({
82-
success: true,
83-
response: "I couldn't find any relevant documentation for that query.",
84-
sources: [],
85-
metadata: {
86-
requestId,
87-
chunksFound: 0,
88-
query,
89-
},
90-
})
91-
}
92-
93-
// Step 3: Format the response with context and sources
94-
const context = chunks
95-
.map((chunk, index) => {
96-
const headerText =
97-
typeof chunk.headerText === 'string'
98-
? chunk.headerText
99-
: String(chunk.headerText || 'Untitled Section')
100-
const sourceDocument =
101-
typeof chunk.sourceDocument === 'string'
102-
? chunk.sourceDocument
103-
: String(chunk.sourceDocument || 'Unknown Document')
104-
const sourceLink =
105-
typeof chunk.sourceLink === 'string' ? chunk.sourceLink : String(chunk.sourceLink || '#')
106-
const chunkText =
107-
typeof chunk.chunkText === 'string' ? chunk.chunkText : String(chunk.chunkText || '')
22+
const { query, topK } = SearchSchema.parse(body)
10823

109-
return `[${index + 1}] ${headerText}
110-
Document: ${sourceDocument}
111-
URL: ${sourceLink}
112-
Content: ${chunkText}`
113-
})
114-
.join('\n\n')
24+
logger.info(`[${requestId}] Documentation search request: "${query}"`, { topK })
11525

116-
// Step 4: Format sources for response
117-
const sources = chunks.map((chunk, index) => ({
118-
id: index + 1,
119-
title: chunk.headerText,
120-
document: chunk.sourceDocument,
121-
link: chunk.sourceLink,
122-
similarity: Math.round(chunk.similarity * 100) / 100,
123-
}))
26+
const results = await searchDocumentation(query, { topK })
12427

125-
logger.info(`[${requestId}] Found ${chunks.length} relevant chunks`)
28+
logger.info(`[${requestId}] Found ${results.length} documentation results`, { query })
12629

12730
return NextResponse.json({
12831
success: true,
129-
response: context,
130-
sources,
32+
results,
33+
query,
34+
totalResults: results.length,
13135
metadata: {
13236
requestId,
133-
chunksFound: chunks.length,
13437
query,
135-
topSimilarity: sources[0]?.similarity,
38+
topK,
13639
},
13740
})
13841
} catch (error) {
@@ -143,7 +46,13 @@ Content: ${chunkText}`
14346
)
14447
}
14548

146-
logger.error(`[${requestId}] Docs search error:`, error)
147-
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
49+
logger.error(`[${requestId}] Documentation search error:`, error)
50+
return NextResponse.json(
51+
{
52+
error: 'Failed to search documentation',
53+
details: error instanceof Error ? error.message : 'Unknown error'
54+
},
55+
{ status: 500 }
56+
)
14857
}
14958
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/copilot.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(
7171
selectChat,
7272
createNewChat,
7373
deleteChat,
74-
sendDocsMessage,
74+
sendMessage,
7575
clearMessages,
7676
clearError,
7777
} = useCopilotStore()
@@ -138,13 +138,13 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(
138138
}
139139

140140
try {
141-
await sendDocsMessage(query, { stream: true })
142-
logger.info('Sent docs query:', query)
141+
await sendMessage(query, { stream: true })
142+
logger.info('Sent message:', query)
143143
} catch (error) {
144-
logger.error('Failed to send docs message:', error)
144+
logger.error('Failed to send message:', error)
145145
}
146146
},
147-
[isSendingMessage, activeWorkflowId, sendDocsMessage]
147+
[isSendingMessage, activeWorkflowId, sendMessage]
148148
)
149149

150150
// Format timestamp for display

apps/sim/lib/copilot-api.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,26 +251,44 @@ export async function sendStreamingMessage(request: SendMessageRequest): Promise
251251
error?: string
252252
}> {
253253
try {
254+
console.log('[CopilotAPI] Sending streaming message request:', {
255+
message: request.message,
256+
stream: true,
257+
hasWorkflowId: !!request.workflowId
258+
})
259+
254260
const response = await fetch('/api/copilot', {
255261
method: 'POST',
256262
headers: { 'Content-Type': 'application/json' },
257263
body: JSON.stringify({ ...request, stream: true }),
258264
})
259265

266+
console.log('[CopilotAPI] Fetch response received:', {
267+
ok: response.ok,
268+
status: response.status,
269+
statusText: response.statusText,
270+
hasBody: !!response.body,
271+
contentType: response.headers.get('content-type')
272+
})
273+
260274
if (!response.ok) {
261275
const errorData = await response.json()
276+
console.error('[CopilotAPI] Error response:', errorData)
262277
throw new Error(errorData.error || 'Failed to send streaming message')
263278
}
264279

265280
if (!response.body) {
281+
console.error('[CopilotAPI] No response body received')
266282
throw new Error('No response body received')
267283
}
268284

285+
console.log('[CopilotAPI] Successfully received stream')
269286
return {
270287
success: true,
271288
stream: response.body,
272289
}
273290
} catch (error) {
291+
console.error('[CopilotAPI] Failed to send streaming message:', error)
274292
logger.error('Failed to send streaming message:', error)
275293
return {
276294
success: false,
@@ -332,26 +350,40 @@ export async function sendStreamingDocsMessage(request: DocsQueryRequest): Promi
332350
error?: string
333351
}> {
334352
try {
353+
console.log('[CopilotAPI] sendStreamingDocsMessage called with:', request)
354+
335355
const response = await fetch('/api/copilot/docs', {
336356
method: 'POST',
337357
headers: { 'Content-Type': 'application/json' },
338358
body: JSON.stringify({ ...request, stream: true }),
339359
})
340360

361+
console.log('[CopilotAPI] Fetch response received:', {
362+
status: response.status,
363+
statusText: response.statusText,
364+
headers: Object.fromEntries(response.headers.entries()),
365+
ok: response.ok,
366+
hasBody: !!response.body
367+
})
368+
341369
if (!response.ok) {
342370
const errorData = await response.json()
371+
console.error('[CopilotAPI] API error response:', errorData)
343372
throw new Error(errorData.error || 'Failed to send streaming docs message')
344373
}
345374

346375
if (!response.body) {
376+
console.error('[CopilotAPI] No response body received')
347377
throw new Error('No response body received')
348378
}
349379

380+
console.log('[CopilotAPI] Returning successful result with stream')
350381
return {
351382
success: true,
352383
stream: response.body,
353384
}
354385
} catch (error) {
386+
console.error('[CopilotAPI] Error in sendStreamingDocsMessage:', error)
355387
logger.error('Failed to send streaming docs message:', error)
356388
return {
357389
success: false,

apps/sim/lib/copilot/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ IMPORTANT: Always provide complete, helpful responses. If you add citations, con
8181
maxTokens: 2000,
8282
embeddingModel: 'text-embedding-3-small',
8383
maxSources: 5,
84-
similarityThreshold: 0.7,
84+
similarityThreshold: 0.5,
8585
},
8686
general: {
8787
streamingEnabled: true,

0 commit comments

Comments
 (0)