mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-10 14:45:16 -05:00
Compare commits
1 Commits
improvemen
...
feat/copil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59cb2f0d2d |
@@ -10,7 +10,7 @@ import { resolveOrCreateChat } from '@/lib/copilot/chat-lifecycle'
|
||||
import { buildCopilotRequestPayload } from '@/lib/copilot/chat-payload'
|
||||
import { generateChatTitle } from '@/lib/copilot/chat-title'
|
||||
import { getCopilotModel } from '@/lib/copilot/config'
|
||||
import { COPILOT_MODEL_IDS, COPILOT_REQUEST_MODES } from '@/lib/copilot/models'
|
||||
import { COPILOT_REQUEST_MODES } from '@/lib/copilot/models'
|
||||
import { orchestrateCopilotStream } from '@/lib/copilot/orchestrator'
|
||||
import {
|
||||
createStreamEventWriter,
|
||||
@@ -43,7 +43,7 @@ const ChatMessageSchema = z.object({
|
||||
chatId: z.string().optional(),
|
||||
workflowId: z.string().optional(),
|
||||
workflowName: z.string().optional(),
|
||||
model: z.enum(COPILOT_MODEL_IDS).optional().default('claude-4.6-opus'),
|
||||
model: z.string().optional().default('claude-4.6-opus'),
|
||||
mode: z.enum(COPILOT_REQUEST_MODES).optional().default('agent'),
|
||||
prefetch: z.boolean().optional(),
|
||||
createNewChat: z.boolean().optional().default(false),
|
||||
|
||||
68
apps/sim/app/api/copilot/models/route.ts
Normal file
68
apps/sim/app/api/copilot/models/route.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { SIM_AGENT_API_URL } from '@/lib/copilot/constants'
|
||||
import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request-helpers'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import type { AvailableModel } from '@/lib/copilot/types'
|
||||
|
||||
const logger = createLogger('CopilotModelsAPI')
|
||||
|
||||
export async function GET(_req: NextRequest) {
|
||||
const { userId, isAuthenticated } = await authenticateCopilotRequestSessionOnly()
|
||||
if (!isAuthenticated || !userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
if (env.COPILOT_API_KEY) {
|
||||
headers['x-api-key'] = env.COPILOT_API_KEY
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${SIM_AGENT_API_URL}/api/get-available-models`, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
})
|
||||
|
||||
const payload = await response.json().catch(() => ({}))
|
||||
if (!response.ok) {
|
||||
logger.warn('Failed to fetch available models from copilot backend', {
|
||||
status: response.status,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: payload?.error || 'Failed to fetch available models',
|
||||
models: [],
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const rawModels = Array.isArray(payload?.models) ? payload.models : []
|
||||
const models: AvailableModel[] = rawModels
|
||||
.filter((item: any) => item && typeof item.id === 'string')
|
||||
.map((item: any) => ({
|
||||
id: item.id,
|
||||
friendlyName: item.friendlyName || item.displayName || item.id,
|
||||
provider: item.provider || 'unknown',
|
||||
}))
|
||||
|
||||
return NextResponse.json({ success: true, models })
|
||||
} catch (error) {
|
||||
logger.error('Error fetching available models', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Failed to fetch available models',
|
||||
models: [],
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
Badge,
|
||||
Popover,
|
||||
@@ -9,8 +9,14 @@ import {
|
||||
PopoverItem,
|
||||
PopoverScrollArea,
|
||||
} from '@/components/emcn'
|
||||
import { getProviderIcon } from '@/providers/utils'
|
||||
import { MODEL_OPTIONS } from '../../constants'
|
||||
import {
|
||||
AnthropicIcon,
|
||||
AzureIcon,
|
||||
BedrockIcon,
|
||||
GeminiIcon,
|
||||
OpenAIIcon,
|
||||
} from '@/components/icons'
|
||||
import { useCopilotStore } from '@/stores/panel'
|
||||
|
||||
interface ModelSelectorProps {
|
||||
/** Currently selected model */
|
||||
@@ -22,14 +28,22 @@ interface ModelSelectorProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate icon component for a model
|
||||
* Map a provider string (from the available-models API) to its icon component.
|
||||
* Falls back to null when the provider is unrecognised.
|
||||
*/
|
||||
function getModelIconComponent(modelValue: string) {
|
||||
const IconComponent = getProviderIcon(modelValue)
|
||||
if (!IconComponent) {
|
||||
return null
|
||||
}
|
||||
return <IconComponent className='h-3.5 w-3.5' />
|
||||
const PROVIDER_ICON_MAP: Record<string, React.ComponentType<{ className?: string }>> = {
|
||||
anthropic: AnthropicIcon,
|
||||
openai: OpenAIIcon,
|
||||
gemini: GeminiIcon,
|
||||
google: GeminiIcon,
|
||||
bedrock: BedrockIcon,
|
||||
azure: AzureIcon,
|
||||
'azure-openai': AzureIcon,
|
||||
'azure-anthropic': AzureIcon,
|
||||
}
|
||||
|
||||
function getIconForProvider(provider: string): React.ComponentType<{ className?: string }> | null {
|
||||
return PROVIDER_ICON_MAP[provider] ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,17 +57,31 @@ export function ModelSelector({ selectedModel, isNearTop, onModelSelect }: Model
|
||||
const [open, setOpen] = useState(false)
|
||||
const triggerRef = useRef<HTMLDivElement>(null)
|
||||
const popoverRef = useRef<HTMLDivElement>(null)
|
||||
const availableModels = useCopilotStore((state) => state.availableModels)
|
||||
|
||||
const modelOptions = useMemo(() => {
|
||||
return availableModels.map((model) => ({
|
||||
value: model.id,
|
||||
label: model.friendlyName || model.id,
|
||||
provider: model.provider,
|
||||
}))
|
||||
}, [availableModels])
|
||||
|
||||
/** Look up the provider for a model id from the available-models list */
|
||||
const getProviderForModel = (modelId: string): string | undefined => {
|
||||
return availableModels.find((m) => m.id === modelId)?.provider
|
||||
}
|
||||
|
||||
const getCollapsedModeLabel = () => {
|
||||
const model = MODEL_OPTIONS.find((m) => m.value === selectedModel)
|
||||
return model ? model.label : 'claude-4.5-sonnet'
|
||||
const model = modelOptions.find((m) => m.value === selectedModel)
|
||||
return model?.label || selectedModel || 'No models available'
|
||||
}
|
||||
|
||||
const getModelIcon = () => {
|
||||
const IconComponent = getProviderIcon(selectedModel)
|
||||
if (!IconComponent) {
|
||||
return null
|
||||
}
|
||||
const provider = getProviderForModel(selectedModel)
|
||||
if (!provider) return null
|
||||
const IconComponent = getIconForProvider(provider)
|
||||
if (!IconComponent) return null
|
||||
return (
|
||||
<span className='flex-shrink-0'>
|
||||
<IconComponent className='h-3 w-3' />
|
||||
@@ -61,6 +89,14 @@ export function ModelSelector({ selectedModel, isNearTop, onModelSelect }: Model
|
||||
)
|
||||
}
|
||||
|
||||
const getModelIconComponent = (modelValue: string) => {
|
||||
const provider = getProviderForModel(modelValue)
|
||||
if (!provider) return null
|
||||
const IconComponent = getIconForProvider(provider)
|
||||
if (!IconComponent) return null
|
||||
return <IconComponent className='h-3.5 w-3.5' />
|
||||
}
|
||||
|
||||
const handleSelect = (modelValue: string) => {
|
||||
onModelSelect(modelValue)
|
||||
setOpen(false)
|
||||
@@ -124,16 +160,20 @@ export function ModelSelector({ selectedModel, isNearTop, onModelSelect }: Model
|
||||
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||
>
|
||||
<PopoverScrollArea className='space-y-[2px]'>
|
||||
{MODEL_OPTIONS.map((option) => (
|
||||
<PopoverItem
|
||||
key={option.value}
|
||||
active={selectedModel === option.value}
|
||||
onClick={() => handleSelect(option.value)}
|
||||
>
|
||||
{getModelIconComponent(option.value)}
|
||||
<span>{option.label}</span>
|
||||
</PopoverItem>
|
||||
))}
|
||||
{modelOptions.length > 0 ? (
|
||||
modelOptions.map((option) => (
|
||||
<PopoverItem
|
||||
key={option.value}
|
||||
active={selectedModel === option.value}
|
||||
onClick={() => handleSelect(option.value)}
|
||||
>
|
||||
{getModelIconComponent(option.value)}
|
||||
<span>{option.label}</span>
|
||||
</PopoverItem>
|
||||
))
|
||||
) : (
|
||||
<div className='px-2 py-2 text-xs text-[var(--text-muted)]'>No models available</div>
|
||||
)}
|
||||
</PopoverScrollArea>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -242,19 +242,6 @@ export function getCommandDisplayLabel(commandId: string): string {
|
||||
return command?.label || commandId.charAt(0).toUpperCase() + commandId.slice(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Model configuration options
|
||||
*/
|
||||
export const MODEL_OPTIONS = [
|
||||
{ value: 'claude-4.6-opus', label: 'Claude 4.6 Opus' },
|
||||
{ value: 'claude-4.5-opus', label: 'Claude 4.5 Opus' },
|
||||
{ value: 'claude-4.5-sonnet', label: 'Claude 4.5 Sonnet' },
|
||||
{ value: 'claude-4.5-haiku', label: 'Claude 4.5 Haiku' },
|
||||
{ value: 'gpt-5.2-codex', label: 'GPT 5.2 Codex' },
|
||||
{ value: 'gpt-5.2-pro', label: 'GPT 5.2 Pro' },
|
||||
{ value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
|
||||
] as const
|
||||
|
||||
/**
|
||||
* Threshold for considering input "near top" of viewport (in pixels)
|
||||
*/
|
||||
|
||||
@@ -112,6 +112,7 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref
|
||||
closePlanTodos,
|
||||
clearPlanArtifact,
|
||||
savePlanArtifact,
|
||||
loadAvailableModels,
|
||||
loadAutoAllowedTools,
|
||||
resumeActiveStream,
|
||||
} = useCopilotStore()
|
||||
@@ -123,6 +124,7 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref
|
||||
chatsLoadedForWorkflow,
|
||||
setCopilotWorkflowId,
|
||||
loadChats,
|
||||
loadAvailableModels,
|
||||
loadAutoAllowedTools,
|
||||
currentChat,
|
||||
isSendingMessage,
|
||||
|
||||
@@ -11,6 +11,7 @@ interface UseCopilotInitializationProps {
|
||||
chatsLoadedForWorkflow: string | null
|
||||
setCopilotWorkflowId: (workflowId: string | null) => Promise<void>
|
||||
loadChats: (forceRefresh?: boolean) => Promise<void>
|
||||
loadAvailableModels: () => Promise<void>
|
||||
loadAutoAllowedTools: () => Promise<void>
|
||||
currentChat: any
|
||||
isSendingMessage: boolean
|
||||
@@ -30,6 +31,7 @@ export function useCopilotInitialization(props: UseCopilotInitializationProps) {
|
||||
chatsLoadedForWorkflow,
|
||||
setCopilotWorkflowId,
|
||||
loadChats,
|
||||
loadAvailableModels,
|
||||
loadAutoAllowedTools,
|
||||
currentChat,
|
||||
isSendingMessage,
|
||||
@@ -129,6 +131,17 @@ export function useCopilotInitialization(props: UseCopilotInitializationProps) {
|
||||
}
|
||||
}, [loadAutoAllowedTools])
|
||||
|
||||
/** Load available models once on mount */
|
||||
const hasLoadedModelsRef = useRef(false)
|
||||
useEffect(() => {
|
||||
if (!hasLoadedModelsRef.current) {
|
||||
hasLoadedModelsRef.current = true
|
||||
loadAvailableModels().catch((err) => {
|
||||
logger.warn('[Copilot] Failed to load available models', err)
|
||||
})
|
||||
}
|
||||
}, [loadAvailableModels])
|
||||
|
||||
return {
|
||||
isInitialized,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { processFileAttachments } from '@/lib/copilot/chat-context'
|
||||
import { getCopilotModel } from '@/lib/copilot/config'
|
||||
import { SIM_AGENT_VERSION } from '@/lib/copilot/constants'
|
||||
import { getCredentialsServerTool } from '@/lib/copilot/tools/server/user/get-credentials'
|
||||
import type { CopilotProviderConfig } from '@/lib/copilot/types'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { tools } from '@/tools/registry'
|
||||
import { getLatestVersionTools, stripVersionSuffix } from '@/tools/utils'
|
||||
|
||||
@@ -46,57 +43,12 @@ interface CredentialsPayload {
|
||||
}
|
||||
}
|
||||
|
||||
function buildProviderConfig(selectedModel: string): CopilotProviderConfig | undefined {
|
||||
const defaults = getCopilotModel('chat')
|
||||
const envModel = env.COPILOT_MODEL || defaults.model
|
||||
const providerEnv = env.COPILOT_PROVIDER
|
||||
|
||||
if (!providerEnv) return undefined
|
||||
|
||||
if (providerEnv === 'azure-openai') {
|
||||
return {
|
||||
provider: 'azure-openai',
|
||||
model: envModel,
|
||||
apiKey: env.AZURE_OPENAI_API_KEY,
|
||||
apiVersion: 'preview',
|
||||
endpoint: env.AZURE_OPENAI_ENDPOINT,
|
||||
}
|
||||
}
|
||||
|
||||
if (providerEnv === 'azure-anthropic') {
|
||||
return {
|
||||
provider: 'azure-anthropic',
|
||||
model: envModel,
|
||||
apiKey: env.AZURE_ANTHROPIC_API_KEY,
|
||||
apiVersion: env.AZURE_ANTHROPIC_API_VERSION,
|
||||
endpoint: env.AZURE_ANTHROPIC_ENDPOINT,
|
||||
}
|
||||
}
|
||||
|
||||
if (providerEnv === 'vertex') {
|
||||
return {
|
||||
provider: 'vertex',
|
||||
model: envModel,
|
||||
apiKey: env.COPILOT_API_KEY,
|
||||
vertexProject: env.VERTEX_PROJECT,
|
||||
vertexLocation: env.VERTEX_LOCATION,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
provider: providerEnv as Exclude<string, 'azure-openai' | 'vertex'>,
|
||||
model: selectedModel,
|
||||
apiKey: env.COPILOT_API_KEY,
|
||||
} as CopilotProviderConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the request payload for the copilot backend.
|
||||
*/
|
||||
export async function buildCopilotRequestPayload(
|
||||
params: BuildPayloadParams,
|
||||
options: {
|
||||
providerConfig?: CopilotProviderConfig
|
||||
selectedModel: string
|
||||
}
|
||||
): Promise<Record<string, unknown>> {
|
||||
@@ -113,7 +65,6 @@ export async function buildCopilotRequestPayload(
|
||||
} = params
|
||||
|
||||
const selectedModel = options.selectedModel
|
||||
const providerConfig = options.providerConfig ?? buildProviderConfig(selectedModel)
|
||||
|
||||
const effectiveMode = mode === 'agent' ? 'build' : mode
|
||||
const transportMode = effectiveMode === 'build' ? 'agent' : effectiveMode
|
||||
@@ -198,7 +149,6 @@ export async function buildCopilotRequestPayload(
|
||||
mode: transportMode,
|
||||
messageId: userMessageId,
|
||||
version: SIM_AGENT_VERSION,
|
||||
...(providerConfig ? { provider: providerConfig } : {}),
|
||||
...(contexts && contexts.length > 0 ? { context: contexts } : {}),
|
||||
...(chatId ? { chatId } : {}),
|
||||
...(processedFileContents.length > 0 ? { fileAttachments: processedFileContents } : {}),
|
||||
|
||||
@@ -104,6 +104,9 @@ export const COPILOT_CHECKPOINTS_REVERT_API_PATH = '/api/copilot/checkpoints/rev
|
||||
/** GET/POST/DELETE — manage auto-allowed tools. */
|
||||
export const COPILOT_AUTO_ALLOWED_TOOLS_API_PATH = '/api/copilot/auto-allowed-tools'
|
||||
|
||||
/** GET — fetch dynamically available copilot models. */
|
||||
export const COPILOT_MODELS_API_PATH = '/api/copilot/models'
|
||||
|
||||
/** GET — fetch user credentials for masking. */
|
||||
export const COPILOT_CREDENTIALS_API_PATH = '/api/copilot/credentials'
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export const COPILOT_MODEL_IDS = [
|
||||
'gemini-3-pro',
|
||||
] as const
|
||||
|
||||
export type CopilotModelId = (typeof COPILOT_MODEL_IDS)[number]
|
||||
export type CopilotModelId = string
|
||||
|
||||
export const COPILOT_MODES = ['ask', 'build', 'plan'] as const
|
||||
export type CopilotMode = (typeof COPILOT_MODES)[number]
|
||||
|
||||
@@ -11,6 +11,12 @@ export type NotificationStatus =
|
||||
|
||||
export type { CopilotToolCall, ToolState }
|
||||
|
||||
export interface AvailableModel {
|
||||
id: string
|
||||
friendlyName: string
|
||||
provider: string
|
||||
}
|
||||
|
||||
// Provider configuration for Sim Agent requests.
|
||||
// This type is only for the `provider` field in requests sent to the Sim Agent.
|
||||
export type CopilotProviderConfig =
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
COPILOT_CONFIRM_API_PATH,
|
||||
COPILOT_CREDENTIALS_API_PATH,
|
||||
COPILOT_DELETE_CHAT_API_PATH,
|
||||
COPILOT_MODELS_API_PATH,
|
||||
MAX_RESUME_ATTEMPTS,
|
||||
OPTIMISTIC_TITLE_MAX_LENGTH,
|
||||
QUEUE_PROCESS_DELAY_MS,
|
||||
@@ -41,6 +42,7 @@ import {
|
||||
saveMessageCheckpoint,
|
||||
} from '@/lib/copilot/messages'
|
||||
import type { CopilotTransportMode } from '@/lib/copilot/models'
|
||||
import type { AvailableModel } from '@/lib/copilot/types'
|
||||
import { parseSSEStream } from '@/lib/copilot/orchestrator/sse-parser'
|
||||
import {
|
||||
abortAllInProgressTools,
|
||||
@@ -913,6 +915,8 @@ const initialState = {
|
||||
selectedModel: 'claude-4.6-opus' as CopilotStore['selectedModel'],
|
||||
agentPrefetch: false,
|
||||
enabledModels: null as string[] | null, // Null means not loaded yet, empty array means all disabled
|
||||
availableModels: [] as AvailableModel[],
|
||||
isLoadingModels: false,
|
||||
isCollapsed: false,
|
||||
currentChat: null as CopilotChat | null,
|
||||
chats: [] as CopilotChat[],
|
||||
@@ -979,6 +983,8 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
selectedModel: get().selectedModel,
|
||||
agentPrefetch: get().agentPrefetch,
|
||||
enabledModels: get().enabledModels,
|
||||
availableModels: get().availableModels,
|
||||
isLoadingModels: get().isLoadingModels,
|
||||
autoAllowedTools: get().autoAllowedTools,
|
||||
autoAllowedToolsLoaded: get().autoAllowedToolsLoaded,
|
||||
})
|
||||
@@ -2191,6 +2197,49 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
},
|
||||
setAgentPrefetch: (prefetch) => set({ agentPrefetch: prefetch }),
|
||||
setEnabledModels: (models) => set({ enabledModels: models }),
|
||||
loadAvailableModels: async () => {
|
||||
set({ isLoadingModels: true })
|
||||
try {
|
||||
const response = await fetch(COPILOT_MODELS_API_PATH, { method: 'GET' })
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch available models: ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const models: unknown[] = Array.isArray(data?.models) ? data.models : []
|
||||
|
||||
const normalizedModels: AvailableModel[] = models
|
||||
.filter((model: unknown): model is AvailableModel => {
|
||||
return (
|
||||
typeof model === 'object' &&
|
||||
model !== null &&
|
||||
'id' in model &&
|
||||
typeof (model as { id: unknown }).id === 'string'
|
||||
)
|
||||
})
|
||||
.map((model: AvailableModel) => ({
|
||||
id: model.id,
|
||||
friendlyName: model.friendlyName || model.id,
|
||||
provider: model.provider || 'unknown',
|
||||
}))
|
||||
|
||||
const { selectedModel } = get()
|
||||
const selectedModelExists = normalizedModels.some((model) => model.id === selectedModel)
|
||||
const nextSelectedModel =
|
||||
selectedModelExists || normalizedModels.length === 0 ? selectedModel : normalizedModels[0].id
|
||||
|
||||
set({
|
||||
availableModels: normalizedModels,
|
||||
selectedModel: nextSelectedModel as CopilotStore['selectedModel'],
|
||||
isLoadingModels: false,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.warn('[Copilot] Failed to load available models', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
set({ isLoadingModels: false })
|
||||
}
|
||||
},
|
||||
|
||||
loadAutoAllowedTools: async () => {
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CopilotMode, CopilotModelId } from '@/lib/copilot/models'
|
||||
import type { AvailableModel } from '@/lib/copilot/types'
|
||||
|
||||
export type { CopilotMode, CopilotModelId } from '@/lib/copilot/models'
|
||||
|
||||
@@ -116,6 +117,8 @@ export interface CopilotState {
|
||||
selectedModel: CopilotModelId
|
||||
agentPrefetch: boolean
|
||||
enabledModels: string[] | null // Null means not loaded yet, array of model IDs when loaded
|
||||
availableModels: AvailableModel[]
|
||||
isLoadingModels: boolean
|
||||
isCollapsed: boolean
|
||||
|
||||
currentChat: CopilotChat | null
|
||||
@@ -184,6 +187,7 @@ export interface CopilotActions {
|
||||
setSelectedModel: (model: CopilotStore['selectedModel']) => Promise<void>
|
||||
setAgentPrefetch: (prefetch: boolean) => void
|
||||
setEnabledModels: (models: string[] | null) => void
|
||||
loadAvailableModels: () => Promise<void>
|
||||
|
||||
setWorkflowId: (workflowId: string | null) => Promise<void>
|
||||
validateCurrentChat: () => boolean
|
||||
|
||||
Reference in New Issue
Block a user