mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
Lint
This commit is contained in:
@@ -1,14 +1,13 @@
|
|||||||
import { eq, and } from 'drizzle-orm'
|
import { and, eq } from 'drizzle-orm'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { getSession } from '@/lib/auth'
|
||||||
import { createLogger } from '@/lib/logs/console-logger'
|
import { createLogger } from '@/lib/logs/console-logger'
|
||||||
import { getRotatingApiKey } from '@/lib/utils'
|
import { getRotatingApiKey } from '@/lib/utils'
|
||||||
import { getSession } from '@/lib/auth'
|
|
||||||
import { db } from '@/db'
|
import { db } from '@/db'
|
||||||
import { copilotChats } from '@/db/schema'
|
import { copilotChats } from '@/db/schema'
|
||||||
import { executeProviderRequest } from '@/providers'
|
import { executeProviderRequest } from '@/providers'
|
||||||
import type { Message } from '@/providers/types'
|
import type { Message } from '@/providers/types'
|
||||||
import { executeTool } from '@/tools'
|
|
||||||
|
|
||||||
const logger = createLogger('CopilotChat')
|
const logger = createLogger('CopilotChat')
|
||||||
|
|
||||||
@@ -34,10 +33,11 @@ const CopilotChatSchema = z.object({
|
|||||||
async function generateChatTitle(userMessage: string): Promise<string> {
|
async function generateChatTitle(userMessage: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const apiKey = getRotatingApiKey('anthropic')
|
const apiKey = getRotatingApiKey('anthropic')
|
||||||
|
|
||||||
const response = await executeProviderRequest('anthropic', {
|
const response = await executeProviderRequest('anthropic', {
|
||||||
model: 'claude-3-haiku-20240307',
|
model: 'claude-3-haiku-20240307',
|
||||||
systemPrompt: 'You are a helpful assistant that generates concise, descriptive titles for chat conversations. Create a title that captures the main topic or question being discussed. Keep it under 50 characters and make it specific and clear.',
|
systemPrompt:
|
||||||
|
'You are a helpful assistant that generates concise, descriptive titles for chat conversations. Create a title that captures the main topic or question being discussed. Keep it under 50 characters and make it specific and clear.',
|
||||||
context: `Generate a concise title for a conversation that starts with this user message: "${userMessage}"
|
context: `Generate a concise title for a conversation that starts with this user message: "${userMessage}"
|
||||||
|
|
||||||
Return only the title text, nothing else.`,
|
Return only the title text, nothing else.`,
|
||||||
@@ -58,8 +58,6 @@ Return only the title text, nothing else.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate chat response with tool calling support
|
* Generate chat response with tool calling support
|
||||||
*/
|
*/
|
||||||
@@ -84,8 +82,8 @@ function extractCitationsFromResponse(response: any): Array<{
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const docsSearchResult = response.toolResults.find((result: any) =>
|
const docsSearchResult = response.toolResults.find(
|
||||||
result.sources && Array.isArray(result.sources)
|
(result: any) => result.sources && Array.isArray(result.sources)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!docsSearchResult || !docsSearchResult.sources) {
|
if (!docsSearchResult || !docsSearchResult.sources) {
|
||||||
@@ -109,15 +107,16 @@ async function generateChatResponse(
|
|||||||
|
|
||||||
// Build conversation context
|
// Build conversation context
|
||||||
const messages: Message[] = []
|
const messages: Message[] = []
|
||||||
|
|
||||||
// Add conversation history
|
// Add conversation history
|
||||||
for (const msg of conversationHistory.slice(-10)) { // Keep last 10 messages
|
for (const msg of conversationHistory.slice(-10)) {
|
||||||
|
// Keep last 10 messages
|
||||||
messages.push({
|
messages.push({
|
||||||
role: msg.role as 'user' | 'assistant' | 'system',
|
role: msg.role as 'user' | 'assistant' | 'system',
|
||||||
content: msg.content,
|
content: msg.content,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add current user message
|
// Add current user message
|
||||||
messages.push({
|
messages.push({
|
||||||
role: 'user',
|
role: 'user',
|
||||||
@@ -170,7 +169,8 @@ MAKE SURE YOU FULLY ANSWER THE USER'S QUESTION.
|
|||||||
{
|
{
|
||||||
id: 'docs_search_internal',
|
id: 'docs_search_internal',
|
||||||
name: 'Search Documentation',
|
name: 'Search Documentation',
|
||||||
description: 'Search Sim Studio documentation for information about features, tools, workflows, and functionality',
|
description:
|
||||||
|
'Search Sim Studio documentation for information about features, tools, workflows, and functionality',
|
||||||
params: {},
|
params: {},
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -204,43 +204,42 @@ MAKE SURE YOU FULLY ANSWER THE USER'S QUESTION.
|
|||||||
stream: false, // Always start with non-streaming to handle tool calls
|
stream: false, // Always start with non-streaming to handle tool calls
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// If this is a streaming request and we got a regular response,
|
||||||
|
|
||||||
// If this is a streaming request and we got a regular response,
|
|
||||||
// we need to create a streaming response from the content
|
// we need to create a streaming response from the content
|
||||||
if (stream && typeof response === 'object' && 'content' in response) {
|
if (stream && typeof response === 'object' && 'content' in response) {
|
||||||
const content = response.content || 'Sorry, I could not generate a response.'
|
const content = response.content || 'Sorry, I could not generate a response.'
|
||||||
|
|
||||||
// Extract citations from the provider response for later use
|
// Extract citations from the provider response for later use
|
||||||
const responseCitations = extractCitationsFromResponse(response)
|
const responseCitations = extractCitationsFromResponse(response)
|
||||||
|
|
||||||
// Create a ReadableStream that emits the content in character chunks
|
// Create a ReadableStream that emits the content in character chunks
|
||||||
const streamResponse = new ReadableStream({
|
const streamResponse = new ReadableStream({
|
||||||
start(controller) {
|
start(controller) {
|
||||||
// Use character-based streaming for more reliable transmission
|
// Use character-based streaming for more reliable transmission
|
||||||
const chunkSize = 8 // Stream 8 characters at a time for smooth experience
|
const chunkSize = 8 // Stream 8 characters at a time for smooth experience
|
||||||
let index = 0
|
let index = 0
|
||||||
|
|
||||||
const pushNext = () => {
|
const pushNext = () => {
|
||||||
if (index < content.length) {
|
if (index < content.length) {
|
||||||
const chunk = content.slice(index, index + chunkSize)
|
const chunk = content.slice(index, index + chunkSize)
|
||||||
controller.enqueue(new TextEncoder().encode(chunk))
|
controller.enqueue(new TextEncoder().encode(chunk))
|
||||||
index += chunkSize
|
index += chunkSize
|
||||||
|
|
||||||
// Add a small delay to simulate streaming
|
// Add a small delay to simulate streaming
|
||||||
setTimeout(pushNext, 25)
|
setTimeout(pushNext, 25)
|
||||||
} else {
|
} else {
|
||||||
controller.close()
|
controller.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pushNext()
|
pushNext()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Store citations for later use in the main streaming handler
|
// Store citations for later use in the main streaming handler
|
||||||
|
|
||||||
;(streamResponse as any)._citations = responseCitations
|
;(streamResponse as any)._citations = responseCitations
|
||||||
|
|
||||||
return streamResponse
|
return streamResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +251,9 @@ MAKE SURE YOU FULLY ANSWER THE USER'S QUESTION.
|
|||||||
return 'Sorry, I could not generate a response.'
|
return 'Sorry, I could not generate a response.'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to generate chat response:', error)
|
logger.error('Failed to generate chat response:', error)
|
||||||
throw new Error(`Failed to generate response: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
throw new Error(
|
||||||
|
`Failed to generate response: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +269,7 @@ export async function POST(req: NextRequest) {
|
|||||||
const { message, chatId, workflowId, createNewChat, stream } = CopilotChatSchema.parse(body)
|
const { message, chatId, workflowId, createNewChat, stream } = CopilotChatSchema.parse(body)
|
||||||
|
|
||||||
const session = await getSession()
|
const session = await getSession()
|
||||||
|
|
||||||
logger.info(`[${requestId}] Copilot chat message: "${message}"`, {
|
logger.info(`[${requestId}] Copilot chat message: "${message}"`, {
|
||||||
chatId,
|
chatId,
|
||||||
workflowId,
|
workflowId,
|
||||||
@@ -285,12 +286,7 @@ export async function POST(req: NextRequest) {
|
|||||||
const [existingChat] = await db
|
const [existingChat] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(copilotChats)
|
.from(copilotChats)
|
||||||
.where(
|
.where(and(eq(copilotChats.id, chatId), eq(copilotChats.userId, session.user.id)))
|
||||||
and(
|
|
||||||
eq(copilotChats.id, chatId),
|
|
||||||
eq(copilotChats.userId, session.user.id)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1)
|
.limit(1)
|
||||||
|
|
||||||
if (existingChat) {
|
if (existingChat) {
|
||||||
@@ -326,11 +322,11 @@ export async function POST(req: NextRequest) {
|
|||||||
const encoder = new TextEncoder()
|
const encoder = new TextEncoder()
|
||||||
// Extract citations from the stream object if available
|
// Extract citations from the stream object if available
|
||||||
const citations = (response as any)._citations || []
|
const citations = (response as any)._citations || []
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
new ReadableStream({
|
new ReadableStream({
|
||||||
async start(controller) {
|
async start(controller) {
|
||||||
const reader = response.getReader()
|
const reader = response.getReader()
|
||||||
let accumulatedResponse = ''
|
let accumulatedResponse = ''
|
||||||
|
|
||||||
// Send initial metadata
|
// Send initial metadata
|
||||||
@@ -352,7 +348,7 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
const chunkText = new TextDecoder().decode(value)
|
const chunkText = new TextDecoder().decode(value)
|
||||||
accumulatedResponse += chunkText
|
accumulatedResponse += chunkText
|
||||||
|
|
||||||
const contentChunk = {
|
const contentChunk = {
|
||||||
type: 'content',
|
type: 'content',
|
||||||
content: chunkText,
|
content: chunkText,
|
||||||
@@ -430,14 +426,22 @@ export async function POST(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract citations from response if available
|
// Extract citations from response if available
|
||||||
const citations = typeof response === 'object' && 'citations' in response ? response.citations :
|
const citations =
|
||||||
typeof response === 'object' && 'toolResults' in response ? extractCitationsFromResponse(response) : []
|
typeof response === 'object' && 'citations' in response
|
||||||
|
? response.citations
|
||||||
|
: typeof response === 'object' && 'toolResults' in response
|
||||||
|
? extractCitationsFromResponse(response)
|
||||||
|
: []
|
||||||
|
|
||||||
const assistantMessage = {
|
const assistantMessage = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: typeof response === 'string' ? response :
|
content:
|
||||||
'content' in response ? response.content : '[Error generating response]',
|
typeof response === 'string'
|
||||||
|
? response
|
||||||
|
: 'content' in response
|
||||||
|
? response.content
|
||||||
|
: '[Error generating response]',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
citations: citations.length > 0 ? citations : undefined,
|
citations: citations.length > 0 ? citations : undefined,
|
||||||
}
|
}
|
||||||
@@ -466,8 +470,12 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
response: typeof response === 'string' ? response :
|
response:
|
||||||
'content' in response ? response.content : '[Error generating response]',
|
typeof response === 'string'
|
||||||
|
? response
|
||||||
|
: 'content' in response
|
||||||
|
? response.content
|
||||||
|
: '[Error generating response]',
|
||||||
chatId: currentChat?.id,
|
chatId: currentChat?.id,
|
||||||
metadata: {
|
metadata: {
|
||||||
requestId,
|
requestId,
|
||||||
@@ -485,4 +493,4 @@ export async function POST(req: NextRequest) {
|
|||||||
logger.error(`[${requestId}] Copilot chat error:`, error)
|
logger.error(`[${requestId}] Copilot chat error:`, error)
|
||||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,10 +93,18 @@ export async function POST(req: NextRequest) {
|
|||||||
// Step 3: Format the response with context and sources
|
// Step 3: Format the response with context and sources
|
||||||
const context = chunks
|
const context = chunks
|
||||||
.map((chunk, index) => {
|
.map((chunk, index) => {
|
||||||
const headerText = typeof chunk.headerText === 'string' ? chunk.headerText : String(chunk.headerText || 'Untitled Section')
|
const headerText =
|
||||||
const sourceDocument = typeof chunk.sourceDocument === 'string' ? chunk.sourceDocument : String(chunk.sourceDocument || 'Unknown Document')
|
typeof chunk.headerText === 'string'
|
||||||
const sourceLink = typeof chunk.sourceLink === 'string' ? chunk.sourceLink : String(chunk.sourceLink || '#')
|
? chunk.headerText
|
||||||
const chunkText = typeof chunk.chunkText === 'string' ? chunk.chunkText : String(chunk.chunkText || '')
|
: 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}
|
return `[${index + 1}] ${headerText}
|
||||||
Document: ${sourceDocument}
|
Document: ${sourceDocument}
|
||||||
@@ -138,4 +146,4 @@ Content: ${chunkText}`
|
|||||||
logger.error(`[${requestId}] Docs search error:`, error)
|
logger.error(`[${requestId}] Docs search error:`, error)
|
||||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -208,7 +208,7 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(
|
|||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
let accumulatedContent = ''
|
let accumulatedContent = ''
|
||||||
let newChatId: string | undefined
|
let newChatId: string | undefined
|
||||||
let responseCitations: Array<{id: number, title: string, url: string}> = []
|
let responseCitations: Array<{ id: number; title: string; url: string }> = []
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read()
|
const { done, value } = await reader.read()
|
||||||
@@ -241,7 +241,8 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(
|
|||||||
? {
|
? {
|
||||||
...msg,
|
...msg,
|
||||||
content: accumulatedContent,
|
content: accumulatedContent,
|
||||||
citations: responseCitations.length > 0 ? responseCitations : undefined,
|
citations:
|
||||||
|
responseCitations.length > 0 ? responseCitations : undefined,
|
||||||
}
|
}
|
||||||
: msg
|
: msg
|
||||||
)
|
)
|
||||||
@@ -254,12 +255,13 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(
|
|||||||
? {
|
? {
|
||||||
...msg,
|
...msg,
|
||||||
content: accumulatedContent,
|
content: accumulatedContent,
|
||||||
citations: responseCitations.length > 0 ? responseCitations : undefined,
|
citations:
|
||||||
|
responseCitations.length > 0 ? responseCitations : undefined,
|
||||||
}
|
}
|
||||||
: msg
|
: msg
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Update current chat state with the chatId from response
|
// Update current chat state with the chatId from response
|
||||||
if (newChatId && !currentChat) {
|
if (newChatId && !currentChat) {
|
||||||
// For new chats, create a temporary chat object and reload the full chat list
|
// For new chats, create a temporary chat object and reload the full chat list
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ export const docsSearchTool: ToolConfig<DocsSearchParams, DocsSearchResponse> =
|
|||||||
},
|
},
|
||||||
|
|
||||||
transformError: (error) => {
|
transformError: (error) => {
|
||||||
return error instanceof Error ? error.message : 'An error occurred while searching documentation'
|
return error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'An error occurred while searching documentation'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { env } from '@/lib/env'
|
|||||||
import { createLogger } from '@/lib/logs/console-logger'
|
import { createLogger } from '@/lib/logs/console-logger'
|
||||||
import { useCustomToolsStore } from '@/stores/custom-tools/store'
|
import { useCustomToolsStore } from '@/stores/custom-tools/store'
|
||||||
import { useEnvironmentStore } from '@/stores/settings/environment/store'
|
import { useEnvironmentStore } from '@/stores/settings/environment/store'
|
||||||
import { tools } from './registry'
|
|
||||||
import { docsSearchTool } from './docs/search'
|
import { docsSearchTool } from './docs/search'
|
||||||
|
import { tools } from './registry'
|
||||||
import type { TableRow, ToolConfig, ToolResponse } from './types'
|
import type { TableRow, ToolConfig, ToolResponse } from './types'
|
||||||
|
|
||||||
const logger = createLogger('ToolsUtils')
|
const logger = createLogger('ToolsUtils')
|
||||||
|
|||||||
Reference in New Issue
Block a user