mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-10 07:27:57 -05:00
Lint
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import {
|
||||
createChat,
|
||||
generateChatTitle,
|
||||
generateDocsResponse,
|
||||
getChat,
|
||||
createChat,
|
||||
updateChat,
|
||||
generateChatTitle,
|
||||
} from '@/lib/copilot/service'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
|
||||
const logger = createLogger('CopilotDocsAPI')
|
||||
|
||||
@@ -254,4 +254,4 @@ export async function POST(req: NextRequest) {
|
||||
logger.error(`[${requestId}] Copilot docs error:`, error)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createChat, deleteChat, getChat, listChats, sendMessage } from '@/lib/copilot/service'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import {
|
||||
sendMessage,
|
||||
createChat,
|
||||
getChat,
|
||||
listChats,
|
||||
deleteChat,
|
||||
generateDocsResponse,
|
||||
type CopilotMessage,
|
||||
} from '@/lib/copilot/service'
|
||||
|
||||
const logger = createLogger('CopilotAPI')
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import { and, eq, sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { getCopilotConfig, getCopilotModel } from '@/lib/copilot/config'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { generateEmbeddings } from '@/app/api/knowledge/utils'
|
||||
import { db } from '@/db'
|
||||
import { copilotChats, docsEmbeddings } from '@/db/schema'
|
||||
import { executeProviderRequest } from '@/providers'
|
||||
import { getApiKey } from '@/providers/utils'
|
||||
import { getCopilotConfig, getCopilotModel } from '@/lib/copilot/config'
|
||||
|
||||
const logger = createLogger('DocsRAG')
|
||||
|
||||
@@ -33,7 +33,7 @@ async function generateChatTitle(userMessage: string): Promise<string> {
|
||||
let apiKey: string
|
||||
try {
|
||||
// Use rotating key directly for hosted providers
|
||||
if ((provider === 'openai' || provider === 'anthropic')) {
|
||||
if (provider === 'openai' || provider === 'anthropic') {
|
||||
const { getRotatingApiKey } = require('@/lib/utils')
|
||||
apiKey = getRotatingApiKey(provider)
|
||||
} else {
|
||||
@@ -120,7 +120,7 @@ async function generateResponse(
|
||||
conversationHistory: any[] = []
|
||||
): Promise<string | ReadableStream> {
|
||||
const config = getCopilotConfig()
|
||||
|
||||
|
||||
// Determine which provider and model to use - allow overrides
|
||||
const selectedProvider = provider || config.rag.defaultProvider
|
||||
const selectedModel = model || config.rag.defaultModel
|
||||
@@ -129,7 +129,7 @@ async function generateResponse(
|
||||
let apiKey: string
|
||||
try {
|
||||
// Use rotating key directly for hosted providers
|
||||
if ((selectedProvider === 'openai' || selectedProvider === 'anthropic')) {
|
||||
if (selectedProvider === 'openai' || selectedProvider === 'anthropic') {
|
||||
const { getRotatingApiKey } = require('@/lib/utils')
|
||||
apiKey = getRotatingApiKey(selectedProvider)
|
||||
} else {
|
||||
@@ -137,7 +137,9 @@ async function generateResponse(
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get API key for ${selectedProvider} ${selectedModel}:`, error)
|
||||
throw new Error(`API key not configured for ${selectedProvider}. Please set up API keys for this provider or use a different one.`)
|
||||
throw new Error(
|
||||
`API key not configured for ${selectedProvider}. Please set up API keys for this provider or use a different one.`
|
||||
)
|
||||
}
|
||||
|
||||
// Format chunks as context with numbered sources
|
||||
|
||||
@@ -21,9 +21,9 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useCopilotStore } from '@/stores/copilot/store'
|
||||
import type { CopilotMessage } from '@/stores/copilot/types'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { CopilotModal } from './components/copilot-modal/copilot-modal'
|
||||
|
||||
const logger = createLogger('Copilot')
|
||||
@@ -56,7 +56,7 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { activeWorkflowId } = useWorkflowRegistry()
|
||||
|
||||
|
||||
// Use the new copilot store
|
||||
const {
|
||||
currentChat,
|
||||
@@ -128,8 +128,8 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(
|
||||
const handleSubmit = useCallback(
|
||||
async (e: React.FormEvent, message?: string) => {
|
||||
e.preventDefault()
|
||||
|
||||
const query = message || (inputRef.current?.value?.trim() || '')
|
||||
|
||||
const query = message || inputRef.current?.value?.trim() || ''
|
||||
if (!query || isSendingMessage || !activeWorkflowId) return
|
||||
|
||||
// Clear input if using the form input
|
||||
@@ -408,12 +408,7 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(
|
||||
className='flex-1'
|
||||
autoComplete='off'
|
||||
/>
|
||||
<Button
|
||||
type='submit'
|
||||
size='icon'
|
||||
disabled={isSendingMessage}
|
||||
className='h-10 w-10'
|
||||
>
|
||||
<Button type='submit' size='icon' disabled={isSendingMessage} className='h-10 w-10'>
|
||||
{isSendingMessage ? (
|
||||
<Loader2 className='h-4 w-4 animate-spin' />
|
||||
) : (
|
||||
|
||||
@@ -72,7 +72,7 @@ When you reference information from documentation sources, use this format:
|
||||
- Each source should only be cited once in your response
|
||||
- Continue your full response after adding citations - don't stop mid-answer
|
||||
|
||||
IMPORTANT: Always provide complete, helpful responses. If you add citations, continue writing your full answer. Do not stop your response after adding a citation.`
|
||||
IMPORTANT: Always provide complete, helpful responses. If you add citations, continue writing your full answer. Do not stop your response after adding a citation.`,
|
||||
},
|
||||
rag: {
|
||||
defaultProvider: 'anthropic',
|
||||
@@ -81,13 +81,13 @@ 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.7,
|
||||
},
|
||||
general: {
|
||||
streamingEnabled: true,
|
||||
maxConversationHistory: 10,
|
||||
titleGenerationModel: 'claude-3-haiku-20240307' // Faster model for titles
|
||||
}
|
||||
titleGenerationModel: 'claude-3-haiku-20240307', // Faster model for titles
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +129,9 @@ export function getCopilotConfig(): CopilotConfig {
|
||||
config.rag.maxSources = Number.parseInt(process.env.COPILOT_RAG_MAX_SOURCES)
|
||||
}
|
||||
if (process.env.COPILOT_RAG_SIMILARITY_THRESHOLD) {
|
||||
config.rag.similarityThreshold = Number.parseFloat(process.env.COPILOT_RAG_SIMILARITY_THRESHOLD)
|
||||
config.rag.similarityThreshold = Number.parseFloat(
|
||||
process.env.COPILOT_RAG_SIMILARITY_THRESHOLD
|
||||
)
|
||||
}
|
||||
|
||||
// General configuration overrides
|
||||
@@ -137,7 +139,9 @@ export function getCopilotConfig(): CopilotConfig {
|
||||
config.general.streamingEnabled = process.env.COPILOT_STREAMING_ENABLED === 'true'
|
||||
}
|
||||
if (process.env.COPILOT_MAX_CONVERSATION_HISTORY) {
|
||||
config.general.maxConversationHistory = Number.parseInt(process.env.COPILOT_MAX_CONVERSATION_HISTORY)
|
||||
config.general.maxConversationHistory = Number.parseInt(
|
||||
process.env.COPILOT_MAX_CONVERSATION_HISTORY
|
||||
)
|
||||
}
|
||||
|
||||
logger.info('Copilot configuration loaded', {
|
||||
@@ -145,7 +149,7 @@ export function getCopilotConfig(): CopilotConfig {
|
||||
chatModel: config.chat.defaultModel,
|
||||
ragProvider: config.rag.defaultProvider,
|
||||
ragModel: config.rag.defaultModel,
|
||||
streamingEnabled: config.general.streamingEnabled
|
||||
streamingEnabled: config.general.streamingEnabled,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.warn('Error applying environment variable overrides, using defaults', { error })
|
||||
@@ -157,24 +161,27 @@ export function getCopilotConfig(): CopilotConfig {
|
||||
/**
|
||||
* Get the model to use for a specific copilot function
|
||||
*/
|
||||
export function getCopilotModel(type: 'chat' | 'rag' | 'title'): { provider: ProviderId; model: string } {
|
||||
export function getCopilotModel(type: 'chat' | 'rag' | 'title'): {
|
||||
provider: ProviderId
|
||||
model: string
|
||||
} {
|
||||
const config = getCopilotConfig()
|
||||
|
||||
|
||||
switch (type) {
|
||||
case 'chat':
|
||||
return {
|
||||
provider: config.chat.defaultProvider,
|
||||
model: config.chat.defaultModel
|
||||
model: config.chat.defaultModel,
|
||||
}
|
||||
case 'rag':
|
||||
return {
|
||||
provider: config.rag.defaultProvider,
|
||||
model: config.rag.defaultModel
|
||||
model: config.rag.defaultModel,
|
||||
}
|
||||
case 'title':
|
||||
return {
|
||||
provider: config.chat.defaultProvider, // Use same provider as chat
|
||||
model: config.general.titleGenerationModel
|
||||
model: config.general.titleGenerationModel,
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown copilot model type: ${type}`)
|
||||
@@ -184,7 +191,10 @@ export function getCopilotModel(type: 'chat' | 'rag' | 'title'): { provider: Pro
|
||||
/**
|
||||
* Validate that a provider/model combination is available
|
||||
*/
|
||||
export function validateCopilotConfig(config: CopilotConfig): { isValid: boolean; errors: string[] } {
|
||||
export function validateCopilotConfig(config: CopilotConfig): {
|
||||
isValid: boolean
|
||||
errors: string[]
|
||||
} {
|
||||
const errors: string[] = []
|
||||
|
||||
// Validate chat provider/model
|
||||
@@ -232,6 +242,6 @@ export function validateCopilotConfig(config: CopilotConfig): { isValid: boolean
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors
|
||||
errors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { getApiKey } from '@/providers/utils'
|
||||
import { executeProviderRequest } from '@/providers'
|
||||
import { db } from '@/db'
|
||||
import { copilotChats, embedding, knowledgeBase, document } from '@/db/schema'
|
||||
import type { ProviderId, ProviderToolConfig } from '@/providers/types'
|
||||
import { copilotChats } from '@/db/schema'
|
||||
import { executeProviderRequest } from '@/providers'
|
||||
import type { ProviderToolConfig } from '@/providers/types'
|
||||
import { getApiKey } from '@/providers/utils'
|
||||
import { getCopilotConfig, getCopilotModel } from './config'
|
||||
|
||||
const logger = createLogger('CopilotService')
|
||||
@@ -74,7 +74,7 @@ export async function generateChatTitle(userMessage: string): Promise<string> {
|
||||
let apiKey: string
|
||||
try {
|
||||
// Use rotating key directly for hosted providers
|
||||
if ((provider === 'openai' || provider === 'anthropic')) {
|
||||
if (provider === 'openai' || provider === 'anthropic') {
|
||||
const { getRotatingApiKey } = require('@/lib/utils')
|
||||
apiKey = getRotatingApiKey(provider)
|
||||
} else {
|
||||
@@ -87,7 +87,8 @@ export async function generateChatTitle(userMessage: string): Promise<string> {
|
||||
|
||||
const response = await executeProviderRequest(provider, {
|
||||
model,
|
||||
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}"\n\nReturn only the title text, nothing else.`,
|
||||
temperature: 0.3,
|
||||
maxTokens: 50,
|
||||
@@ -115,27 +116,29 @@ export async function searchDocumentation(
|
||||
topK?: number
|
||||
threshold?: number
|
||||
} = {}
|
||||
): Promise<Array<{
|
||||
id: number
|
||||
title: string
|
||||
url: string
|
||||
content: string
|
||||
similarity: number
|
||||
}>> {
|
||||
): Promise<
|
||||
Array<{
|
||||
id: number
|
||||
title: string
|
||||
url: string
|
||||
content: string
|
||||
similarity: number
|
||||
}>
|
||||
> {
|
||||
const { generateEmbeddings } = require('@/app/api/knowledge/utils')
|
||||
const { docsEmbeddings } = require('@/db/schema')
|
||||
const { sql } = require('drizzle-orm')
|
||||
|
||||
|
||||
const config = getCopilotConfig()
|
||||
const { topK = config.rag.maxSources, threshold = config.rag.similarityThreshold } = options
|
||||
|
||||
try {
|
||||
logger.info('Documentation search requested', { query, topK, threshold })
|
||||
|
||||
|
||||
// Generate embedding for the query
|
||||
const embeddings = await generateEmbeddings([query])
|
||||
const queryEmbedding = embeddings[0]
|
||||
|
||||
|
||||
if (!queryEmbedding || queryEmbedding.length === 0) {
|
||||
logger.warn('Failed to generate query embedding')
|
||||
return []
|
||||
@@ -157,12 +160,12 @@ export async function searchDocumentation(
|
||||
.limit(topK)
|
||||
|
||||
// Filter by similarity threshold
|
||||
const filteredResults = results.filter(result => result.similarity >= threshold)
|
||||
const filteredResults = results.filter((result) => result.similarity >= threshold)
|
||||
|
||||
logger.info(`Found ${filteredResults.length} relevant documentation chunks`, {
|
||||
totalResults: results.length,
|
||||
afterFiltering: filteredResults.length,
|
||||
threshold
|
||||
threshold,
|
||||
})
|
||||
|
||||
return filteredResults.map((result, index) => ({
|
||||
@@ -170,7 +173,7 @@ export async function searchDocumentation(
|
||||
title: String(result.headerText || 'Untitled Section'),
|
||||
url: String(result.sourceLink || '#'),
|
||||
content: String(result.chunkText || ''),
|
||||
similarity: result.similarity
|
||||
similarity: result.similarity,
|
||||
}))
|
||||
} catch (error) {
|
||||
logger.error('Failed to search documentation:', error)
|
||||
@@ -203,11 +206,11 @@ export async function generateDocsResponse(
|
||||
}> {
|
||||
const config = getCopilotConfig()
|
||||
const { provider, model } = getCopilotModel('rag')
|
||||
const {
|
||||
const {
|
||||
stream = config.general.streamingEnabled,
|
||||
topK = config.rag.maxSources,
|
||||
provider: overrideProvider,
|
||||
model: overrideModel
|
||||
model: overrideModel,
|
||||
} = options
|
||||
|
||||
const selectedProvider = overrideProvider || provider
|
||||
@@ -217,25 +220,31 @@ export async function generateDocsResponse(
|
||||
let apiKey: string
|
||||
try {
|
||||
// Use rotating key directly for hosted providers
|
||||
if ((selectedProvider === 'openai' || selectedProvider === 'anthropic')) {
|
||||
if (selectedProvider === 'openai' || selectedProvider === 'anthropic') {
|
||||
const { getRotatingApiKey } = require('@/lib/utils')
|
||||
apiKey = getRotatingApiKey(selectedProvider)
|
||||
} else {
|
||||
apiKey = getApiKey(selectedProvider, selectedModel)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get API key for docs response (${selectedProvider} ${selectedModel}):`, error)
|
||||
throw new Error(`API key not configured for ${selectedProvider}. Please set up API keys for this provider or use a different one.`)
|
||||
logger.error(
|
||||
`Failed to get API key for docs response (${selectedProvider} ${selectedModel}):`,
|
||||
error
|
||||
)
|
||||
throw new Error(
|
||||
`API key not configured for ${selectedProvider}. Please set up API keys for this provider or use a different one.`
|
||||
)
|
||||
}
|
||||
|
||||
// Search documentation
|
||||
const searchResults = await searchDocumentation(query, { topK })
|
||||
|
||||
|
||||
if (searchResults.length === 0) {
|
||||
const fallbackResponse = "I couldn't find any relevant documentation for your question. Please try rephrasing your query or check if you're asking about a feature that exists in Sim Studio."
|
||||
const fallbackResponse =
|
||||
"I couldn't find any relevant documentation for your question. Please try rephrasing your query or check if you're asking about a feature that exists in Sim Studio."
|
||||
return {
|
||||
response: fallbackResponse,
|
||||
sources: []
|
||||
sources: [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +298,9 @@ The sources are numbered [1] through [${searchResults.length}] in the context be
|
||||
Documentation Context:
|
||||
${context}`
|
||||
|
||||
logger.info(`Generating docs response using provider: ${selectedProvider}, model: ${selectedModel}`)
|
||||
logger.info(
|
||||
`Generating docs response using provider: ${selectedProvider}, model: ${selectedModel}`
|
||||
)
|
||||
|
||||
const response = await executeProviderRequest(selectedProvider, {
|
||||
model: selectedModel,
|
||||
@@ -333,11 +344,13 @@ ${context}`
|
||||
|
||||
return {
|
||||
response: cleanedContent,
|
||||
sources
|
||||
sources,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to generate docs response:', error)
|
||||
throw new Error(`Failed to generate docs response: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
throw new Error(
|
||||
`Failed to generate docs response: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +374,7 @@ export async function generateChatResponse(
|
||||
let apiKey: string
|
||||
try {
|
||||
// Use rotating key directly for hosted providers
|
||||
if ((provider === 'openai' || provider === 'anthropic')) {
|
||||
if (provider === 'openai' || provider === 'anthropic') {
|
||||
const { getRotatingApiKey } = require('@/lib/utils')
|
||||
apiKey = getRotatingApiKey(provider)
|
||||
} else {
|
||||
@@ -369,7 +382,9 @@ export async function generateChatResponse(
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get API key for chat (${provider} ${model}):`, error)
|
||||
throw new Error(`API key not configured for ${provider}. Please set up API keys for this provider or use a different one.`)
|
||||
throw new Error(
|
||||
`API key not configured for ${provider}. Please set up API keys for this provider or use a different one.`
|
||||
)
|
||||
}
|
||||
|
||||
// Build conversation context
|
||||
@@ -378,7 +393,7 @@ export async function generateChatResponse(
|
||||
// Add conversation history (limited by config)
|
||||
const historyLimit = config.general.maxConversationHistory
|
||||
const recentHistory = conversationHistory.slice(-historyLimit)
|
||||
|
||||
|
||||
for (const msg of recentHistory) {
|
||||
messages.push({
|
||||
role: msg.role as 'user' | 'assistant' | 'system',
|
||||
@@ -397,7 +412,8 @@ export async function generateChatResponse(
|
||||
{
|
||||
id: 'docs_search_internal',
|
||||
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: {},
|
||||
parameters: {
|
||||
type: 'object',
|
||||
@@ -441,7 +457,9 @@ export async function generateChatResponse(
|
||||
return 'Sorry, I could not generate a response.'
|
||||
} catch (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'}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +520,9 @@ export async function createChat(
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to create chat:', error)
|
||||
throw new Error(`Failed to create chat: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
throw new Error(
|
||||
`Failed to create chat: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,7 +578,7 @@ export async function listChats(
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
|
||||
return chats.map(chat => ({
|
||||
return chats.map((chat) => ({
|
||||
id: chat.id,
|
||||
title: chat.title,
|
||||
model: chat.model,
|
||||
@@ -713,4 +733,4 @@ export async function sendMessage(request: SendMessageRequest): Promise<{
|
||||
logger.error('Failed to send message:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export { useCopilotStore } from './store'
|
||||
export type {
|
||||
CopilotMessage,
|
||||
CopilotChat,
|
||||
CopilotState,
|
||||
CopilotActions,
|
||||
CopilotChat,
|
||||
CopilotMessage,
|
||||
CopilotState,
|
||||
CopilotStore,
|
||||
} from './types'
|
||||
} from './types'
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import {
|
||||
type CopilotChat,
|
||||
type CopilotMessage,
|
||||
createChat,
|
||||
deleteChat as deleteApiChat,
|
||||
getChat,
|
||||
listChats,
|
||||
sendStreamingMessage,
|
||||
sendStreamingDocsMessage,
|
||||
type CopilotChat,
|
||||
type CopilotMessage,
|
||||
sendStreamingMessage,
|
||||
} from '@/lib/copilot-api'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import type { CopilotStore } from './types'
|
||||
|
||||
const logger = createLogger('CopilotStore')
|
||||
@@ -48,7 +48,7 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
messages: [],
|
||||
error: null,
|
||||
})
|
||||
|
||||
|
||||
// Load chats for the new workflow
|
||||
if (workflowId) {
|
||||
get().loadChats()
|
||||
@@ -68,27 +68,27 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
|
||||
try {
|
||||
const result = await listChats(workflowId)
|
||||
|
||||
|
||||
if (result.success) {
|
||||
set({
|
||||
set({
|
||||
chats: result.chats,
|
||||
isLoadingChats: false,
|
||||
})
|
||||
|
||||
|
||||
// If no current chat and we have chats, optionally select the most recent one
|
||||
const { currentChat } = get()
|
||||
if (!currentChat && result.chats.length > 0) {
|
||||
// Auto-select most recent chat
|
||||
await get().selectChat(result.chats[0])
|
||||
}
|
||||
|
||||
|
||||
logger.info(`Loaded ${result.chats.length} chats for workflow ${workflowId}`)
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to load chats')
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to load chats:', error)
|
||||
set({
|
||||
set({
|
||||
error: error instanceof Error ? error.message : 'Failed to load chats',
|
||||
isLoadingChats: false,
|
||||
})
|
||||
@@ -101,21 +101,21 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
|
||||
try {
|
||||
const result = await getChat(chat.id)
|
||||
|
||||
|
||||
if (result.success && result.chat) {
|
||||
set({
|
||||
currentChat: result.chat,
|
||||
messages: result.chat.messages,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
|
||||
logger.info(`Selected chat: ${result.chat.title || 'Untitled'}`)
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to load chat')
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to select chat:', error)
|
||||
set({
|
||||
set({
|
||||
error: error instanceof Error ? error.message : 'Failed to load chat',
|
||||
isLoading: false,
|
||||
})
|
||||
@@ -134,24 +134,24 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
|
||||
try {
|
||||
const result = await createChat(workflowId, options)
|
||||
|
||||
|
||||
if (result.success && result.chat) {
|
||||
set({
|
||||
currentChat: result.chat,
|
||||
messages: result.chat.messages,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
|
||||
// Reload chats to include the new one
|
||||
await get().loadChats()
|
||||
|
||||
|
||||
logger.info(`Created new chat: ${result.chat.id}`)
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to create chat')
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to create new chat:', error)
|
||||
set({
|
||||
set({
|
||||
error: error instanceof Error ? error.message : 'Failed to create chat',
|
||||
isLoading: false,
|
||||
})
|
||||
@@ -162,15 +162,15 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
deleteChat: async (chatId: string) => {
|
||||
try {
|
||||
const result = await deleteApiChat(chatId)
|
||||
|
||||
|
||||
if (result.success) {
|
||||
const { currentChat } = get()
|
||||
|
||||
|
||||
// Remove from chats list
|
||||
set((state) => ({
|
||||
chats: state.chats.filter((chat) => chat.id !== chatId),
|
||||
}))
|
||||
|
||||
|
||||
// If this was the current chat, clear it
|
||||
if (currentChat?.id === chatId) {
|
||||
set({
|
||||
@@ -178,14 +178,14 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
messages: [],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
logger.info(`Deleted chat: ${chatId}`)
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to delete chat')
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to delete chat:', error)
|
||||
set({
|
||||
set({
|
||||
error: error instanceof Error ? error.message : 'Failed to delete chat',
|
||||
})
|
||||
}
|
||||
@@ -239,12 +239,13 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to send message:', error)
|
||||
|
||||
|
||||
// Replace streaming message with error
|
||||
const errorMessage: CopilotMessage = {
|
||||
id: streamingMessage.id,
|
||||
role: 'assistant',
|
||||
content: 'Sorry, I encountered an error while processing your message. Please try again.',
|
||||
content:
|
||||
'Sorry, I encountered an error while processing your message. Please try again.',
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
|
||||
@@ -307,12 +308,13 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to send docs message:', error)
|
||||
|
||||
|
||||
// Replace streaming message with error
|
||||
const errorMessage: CopilotMessage = {
|
||||
id: streamingMessage.id,
|
||||
role: 'assistant',
|
||||
content: 'Sorry, I encountered an error while searching the documentation. Please try again.',
|
||||
content:
|
||||
'Sorry, I encountered an error while searching the documentation. Please try again.',
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
|
||||
@@ -374,7 +376,8 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
? {
|
||||
...msg,
|
||||
content: accumulatedContent,
|
||||
citations: responseCitations.length > 0 ? responseCitations : undefined,
|
||||
citations:
|
||||
responseCitations.length > 0 ? responseCitations : undefined,
|
||||
}
|
||||
: msg
|
||||
),
|
||||
@@ -387,7 +390,8 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
? {
|
||||
...msg,
|
||||
content: accumulatedContent,
|
||||
citations: responseCitations.length > 0 ? responseCitations : undefined,
|
||||
citations:
|
||||
responseCitations.length > 0 ? responseCitations : undefined,
|
||||
}
|
||||
: msg
|
||||
),
|
||||
|
||||
@@ -33,21 +33,21 @@ export interface CopilotChat {
|
||||
export interface CopilotState {
|
||||
// Current active chat
|
||||
currentChat: CopilotChat | null
|
||||
|
||||
|
||||
// List of available chats for current workflow
|
||||
chats: CopilotChat[]
|
||||
|
||||
|
||||
// Current messages (from active chat)
|
||||
messages: CopilotMessage[]
|
||||
|
||||
|
||||
// Loading states
|
||||
isLoading: boolean
|
||||
isLoadingChats: boolean
|
||||
isSendingMessage: boolean
|
||||
|
||||
|
||||
// Error state
|
||||
error: string | null
|
||||
|
||||
|
||||
// Current workflow ID (for chat context)
|
||||
workflowId: string | null
|
||||
}
|
||||
@@ -62,16 +62,16 @@ export interface CopilotActions {
|
||||
selectChat: (chat: CopilotChat) => Promise<void>
|
||||
createNewChat: (options?: { title?: string; initialMessage?: string }) => Promise<void>
|
||||
deleteChat: (chatId: string) => Promise<void>
|
||||
|
||||
|
||||
// Message handling
|
||||
sendMessage: (message: string, options?: { stream?: boolean }) => Promise<void>
|
||||
sendDocsMessage: (query: string, options?: { stream?: boolean; topK?: number }) => Promise<void>
|
||||
|
||||
|
||||
// Utility actions
|
||||
clearMessages: () => void
|
||||
clearError: () => void
|
||||
reset: () => void
|
||||
|
||||
|
||||
// Internal helper (not exposed publicly)
|
||||
handleStreamingResponse: (stream: ReadableStream, messageId: string) => Promise<void>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user