mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
chore(studio): default assistant to GPT-5.6 Luna (#49749)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Feature / chore: hide assistant model selection in the UI and default chats to GPT-5.6 Luna. ## What is the current behavior? The assistant composer exposes a model picker. Paid orgs default to `gpt-5.3-codex`; everyone else defaults to `gpt-5.4-nano`. ## What is the new behavior? - The model picker is hidden in the assistant composer and Explorer home. - Chats default to `gpt-5.6-luna` with `reasoningEffort: medium`. - Model selection plumbing is kept (registry, entitlements, `setModel`, generate-v4 request body) so a requested model can still be honored when provided. - Other completion endpoints still use `gpt-5.4-nano`. ## Additional context Model selector UI can be re-enabled by passing `selectedModel` / `onSelectModel` to `AssistantChatForm`. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for the GPT-5.6 Luna model with medium reasoning capability. * Made GPT-5.6 Luna the default assistant model. * **Improvements** * Simplified assistant chat by removing model selection from the primary chat experience. * Updated model fallback behavior to use the standard assistant model. * Chat forms can now optionally display model selection when configured. * **Tests** * Updated model coverage and assistant chat tests for the new defaults and behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
4d2bd0eacf
commit
9b17ce8f2c
10 files changed
+46
-74
No files matched your search
@@ -5,7 +5,6 @@ import { useCreateChat, useCreateNotebook, useCreateQuery } from './hooks'
|
||||
import { CHAT_TEMPLATES, NOTEBOOK_TEMPLATES } from './templates'
|
||||
import { ActionCard } from '@/components/layouts/Tabs/ActionCard'
|
||||
import { AssistantChatForm } from '@/components/ui/AIAssistantPanel/AssistantChatForm'
|
||||
import type { AssistantModel } from '@/state/ai-assistant-state'
|
||||
|
||||
export const ExplorerHomeTab = () => {
|
||||
const { createNotebook } = useCreateNotebook()
|
||||
@@ -13,7 +12,6 @@ export const ExplorerHomeTab = () => {
|
||||
const { createChat } = useCreateChat()
|
||||
|
||||
const [value, setValue] = useState<string>('')
|
||||
const [selectedModel, setSelectedModal] = useState<AssistantModel>('gpt-5.4-nano')
|
||||
|
||||
return (
|
||||
<div className="bg-surface-100 h-full flex flex-col items-center justify-center">
|
||||
@@ -31,9 +29,7 @@ export const ExplorerHomeTab = () => {
|
||||
placeholder="Ask anything about your project"
|
||||
value={value}
|
||||
onValueChange={(e) => setValue(e.target.value)}
|
||||
selectedModel={selectedModel}
|
||||
onSelectModel={setSelectedModal}
|
||||
onSubmit={(message) => createChat({ initialMessage: message, model: selectedModel })}
|
||||
onSubmit={(message) => createChat({ initialMessage: message })}
|
||||
/>
|
||||
|
||||
<section className="mt-6">
|
||||
|
||||
@@ -9,7 +9,6 @@ const {
|
||||
mockPush,
|
||||
mockSelectChat,
|
||||
mockSetContext,
|
||||
mockSetModel,
|
||||
mockWhenInitialized,
|
||||
} = vi.hoisted(() => ({
|
||||
mockCreateChat: vi.fn(() => 'chat-2'),
|
||||
@@ -17,7 +16,6 @@ const {
|
||||
mockPush: vi.fn(),
|
||||
mockSelectChat: vi.fn(),
|
||||
mockSetContext: vi.fn(),
|
||||
mockSetModel: vi.fn(),
|
||||
mockWhenInitialized: vi.fn(() => Promise.resolve()),
|
||||
}))
|
||||
|
||||
@@ -41,7 +39,6 @@ vi.mock('@/state/ai-assistant-state', () => ({
|
||||
createChat: mockCreateChat,
|
||||
selectChat: mockSelectChat,
|
||||
setContext: mockSetContext,
|
||||
setModel: mockSetModel,
|
||||
}),
|
||||
whenAiAssistantInitialized: () => mockWhenInitialized(),
|
||||
}))
|
||||
@@ -59,7 +56,6 @@ describe('useCreateChat', () => {
|
||||
await result.current.createChat({
|
||||
name: 'Investigate errors',
|
||||
initialMessage: 'What happened?',
|
||||
model: 'gpt-5.4-nano',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -72,7 +68,6 @@ describe('useCreateChat', () => {
|
||||
name: 'Investigate errors',
|
||||
initialMessage: 'What happened?',
|
||||
})
|
||||
expect(mockSetModel).toHaveBeenCalledWith('gpt-5.4-nano')
|
||||
expect(mockPush).toHaveBeenCalledWith('/project/default/explorer/chat/chat-2')
|
||||
expect(mockSelectChat).not.toHaveBeenCalled()
|
||||
|
||||
@@ -82,7 +77,7 @@ describe('useCreateChat', () => {
|
||||
expect(mockSelectChat).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// Hydration replaces the chat map and the model wholesale, so a chat created mid-load would be
|
||||
// Hydration replaces the chat map wholesale, so a chat created mid-load would be
|
||||
// dropped the moment the persisted state lands
|
||||
it('waits for the assistant state to hydrate before creating the chat', async () => {
|
||||
let resolveHydration = () => {}
|
||||
@@ -97,11 +92,10 @@ describe('useCreateChat', () => {
|
||||
|
||||
let created: Promise<string | undefined> | undefined
|
||||
await act(async () => {
|
||||
created = result.current.createChat({ name: 'Investigate errors', model: 'gpt-5.4-nano' })
|
||||
created = result.current.createChat({ name: 'Investigate errors' })
|
||||
})
|
||||
|
||||
expect(mockCreateChat).not.toHaveBeenCalled()
|
||||
expect(mockSetModel).not.toHaveBeenCalled()
|
||||
expect(mockPush).not.toHaveBeenCalled()
|
||||
|
||||
await act(async () => {
|
||||
@@ -113,7 +107,6 @@ describe('useCreateChat', () => {
|
||||
name: 'Investigate errors',
|
||||
initialMessage: undefined,
|
||||
})
|
||||
expect(mockSetModel).toHaveBeenCalledWith('gpt-5.4-nano')
|
||||
expect(mockPush).toHaveBeenCalledWith('/project/default/explorer/chat/chat-2')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -32,23 +32,16 @@ import { Markdown } from '@/components/interfaces/Markdown'
|
||||
import { useCheckOpenAIKeyQuery } from '@/data/ai/check-api-key-query'
|
||||
import { useRateMessageMutation } from '@/data/ai/rate-message-mutation'
|
||||
import { useTablesQuery } from '@/data/tables/tables-query'
|
||||
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
|
||||
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
|
||||
import { useOrgAiOptInLevel } from '@/hooks/misc/useOrgOptedIntoAi'
|
||||
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
||||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
import type { AssistantMessageMetadata } from '@/lib/ai/assistant-message-metadata'
|
||||
import { getParallelApprovalIdsToReject } from '@/lib/ai/message-utils'
|
||||
import {
|
||||
DEFAULT_ASSISTANT_BASE_MODEL_ID,
|
||||
defaultAssistantModelId,
|
||||
isAssistantBaseModelId,
|
||||
isKnownAssistantModelId,
|
||||
} from '@/lib/ai/model.utils'
|
||||
import { IS_PLATFORM } from '@/lib/constants'
|
||||
import { uuidv4 } from '@/lib/helpers'
|
||||
import { useTrack } from '@/lib/telemetry/track'
|
||||
import type { AssistantModel, SqlSnippet } from '@/state/ai-assistant-state'
|
||||
import type { SqlSnippet } from '@/state/ai-assistant-state'
|
||||
import { useAiAssistantState, useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
|
||||
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
||||
import { useShortcut } from '@/state/shortcuts/useShortcut'
|
||||
@@ -109,26 +102,6 @@ export const AssistantChat = ({
|
||||
enabled: shortcutsEnabled,
|
||||
})
|
||||
|
||||
const { hasAccess: hasAccessToAdvanceModel, isLoading: isLoadingEntitlements } =
|
||||
useCheckEntitlements('assistant.advance_model')
|
||||
|
||||
const selectedModel = useMemo<AssistantModel>(() => {
|
||||
// While entitlements are loading, use the stored model without enforcing access
|
||||
if (isLoadingEntitlements) {
|
||||
return snap.model ?? DEFAULT_ASSISTANT_BASE_MODEL_ID
|
||||
}
|
||||
|
||||
const defaultModel = defaultAssistantModelId(hasAccessToAdvanceModel)
|
||||
const model = snap.model ?? defaultModel
|
||||
|
||||
if (!isKnownAssistantModelId(model)) return defaultModel
|
||||
if (!hasAccessToAdvanceModel && !isAssistantBaseModelId(model)) {
|
||||
return DEFAULT_ASSISTANT_BASE_MODEL_ID
|
||||
}
|
||||
|
||||
return model
|
||||
}, [isLoadingEntitlements, hasAccessToAdvanceModel, snap.model])
|
||||
|
||||
const [updatedOptInSinceMCP] = useLocalStorageQuery(
|
||||
LOCAL_STORAGE_KEYS.AI_ASSISTANT_MCP_OPT_IN,
|
||||
false
|
||||
@@ -652,7 +625,6 @@ export const AssistantChat = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{disablePrompts && (
|
||||
<Admonition
|
||||
showIcon={false}
|
||||
@@ -706,8 +678,6 @@ export const AssistantChat = ({
|
||||
composerContext?.onSetSqlSnippets?.(newSnippets)
|
||||
}}
|
||||
includeSnippetsInMessage={includeSnippetsInMessage}
|
||||
selectedModel={selectedModel}
|
||||
onSelectModel={(model) => snap.setModel(model)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,9 +46,9 @@ export interface FormProps {
|
||||
/* If currently editing an existing message */
|
||||
isEditing?: boolean
|
||||
/* The currently selected AI model */
|
||||
selectedModel: AssistantModelId
|
||||
selectedModel?: AssistantModelId
|
||||
/* Callback when a model is chosen */
|
||||
onSelectModel: (model: AssistantModelId) => void
|
||||
onSelectModel?: (model: AssistantModelId) => void
|
||||
}
|
||||
|
||||
const AssistantChatFormComponent = forwardRef<HTMLFormElement, FormProps>(
|
||||
@@ -99,6 +99,7 @@ const AssistantChatFormComponent = forwardRef<HTMLFormElement, FormProps>(
|
||||
}
|
||||
|
||||
const canSubmit = !disabled && !loading && !!value
|
||||
const showModelSelector = selectedModel !== undefined && onSelectModel !== undefined
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
@@ -131,10 +132,17 @@ const AssistantChatFormComponent = forwardRef<HTMLFormElement, FormProps>(
|
||||
onChange={(event) => onValueChange(event)}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<div className="absolute inset-x-1.5 bottom-1.5 flex items-center justify-between pointer-events-none">
|
||||
<div className="pointer-events-auto">
|
||||
<ModelSelector selectedModel={selectedModel} onSelectModel={onSelectModel} />
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'absolute inset-x-1.5 bottom-1.5 flex items-center pointer-events-none',
|
||||
showModelSelector ? 'justify-between' : 'justify-end'
|
||||
)}
|
||||
>
|
||||
{showModelSelector && (
|
||||
<div className="pointer-events-auto">
|
||||
<ModelSelector selectedModel={selectedModel} onSelectModel={onSelectModel} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 items-center pointer-events-auto">
|
||||
{loading ? (
|
||||
|
||||
@@ -70,9 +70,8 @@ describe('getModel', () => {
|
||||
expect(error).toEqual(new Error('OPENAI_API_KEY not available'))
|
||||
})
|
||||
|
||||
it('returns openai gpt-5.3-codex when hasAccessToAdvanceModel and not throttled', async () => {
|
||||
it('returns openai gpt-5.3-codex with reasoning effort', async () => {
|
||||
vi.stubEnv('OPENAI_API_KEY', 'test-key')
|
||||
vi.stubEnv('IS_THROTTLED', 'false')
|
||||
|
||||
const { modelParams, error } = await getModel({
|
||||
provider: 'openai',
|
||||
|
||||
@@ -47,8 +47,9 @@ describe('model.utils', () => {
|
||||
it('should have openai provider with models', () => {
|
||||
expect(PROVIDERS.openai).toBeDefined()
|
||||
expect(PROVIDERS.openai.models).toBeDefined()
|
||||
expect(Object.keys(PROVIDERS.openai.models)).toContain('gpt-5.3-codex')
|
||||
expect(Object.keys(PROVIDERS.openai.models)).toContain('gpt-5.6-luna')
|
||||
expect(Object.keys(PROVIDERS.openai.models)).toContain('gpt-5.4-nano')
|
||||
expect(Object.keys(PROVIDERS.openai.models)).toContain('gpt-5.3-codex')
|
||||
})
|
||||
|
||||
it('should have exactly one default model per provider', () => {
|
||||
@@ -111,20 +112,23 @@ describe('model.utils', () => {
|
||||
})
|
||||
|
||||
it('defaults should satisfy unions', () => {
|
||||
expect(DEFAULT_ASSISTANT_BASE_MODEL_ID).toBe('gpt-5.4-nano')
|
||||
expect(DEFAULT_ASSISTANT_BASE_MODEL_ID).toBe('gpt-5.6-luna')
|
||||
expect(DEFAULT_ASSISTANT_ADVANCE_MODEL_ID).toBe('gpt-5.3-codex')
|
||||
expect(defaultAssistantModelId(false)).toBe(DEFAULT_ASSISTANT_BASE_MODEL_ID)
|
||||
expect(defaultAssistantModelId(true)).toBe(DEFAULT_ASSISTANT_ADVANCE_MODEL_ID)
|
||||
expect(defaultAssistantModelId(true)).toBe(DEFAULT_ASSISTANT_BASE_MODEL_ID)
|
||||
})
|
||||
|
||||
it('isAssistantBaseModelId / isAdvanceOnlyModelId', () => {
|
||||
expect(isAssistantBaseModelId('gpt-5.6-luna')).toBe(true)
|
||||
expect(isAssistantBaseModelId('gpt-5.4-nano')).toBe(true)
|
||||
expect(isAssistantBaseModelId('gpt-5.3-codex')).toBe(false)
|
||||
expect(isAdvanceOnlyModelId('gpt-5.3-codex')).toBe(true)
|
||||
expect(isAdvanceOnlyModelId('gpt-5.4-nano')).toBe(false)
|
||||
expect(isAdvanceOnlyModelId('gpt-5.6-luna')).toBe(false)
|
||||
})
|
||||
|
||||
it('isKnownAssistantModelId', () => {
|
||||
expect(isKnownAssistantModelId('gpt-5.6-luna')).toBe(true)
|
||||
expect(isKnownAssistantModelId('gpt-5.4-nano')).toBe(true)
|
||||
expect(isKnownAssistantModelId('gpt-5.3-codex')).toBe(true)
|
||||
expect(isKnownAssistantModelId('gpt-5')).toBe(false)
|
||||
@@ -133,28 +137,27 @@ describe('model.utils', () => {
|
||||
})
|
||||
|
||||
it('getAssistantModelEntry returns config for known ids', () => {
|
||||
expect(getAssistantModelEntry('gpt-5.6-luna').reasoningEffort).toBe('medium')
|
||||
expect(getAssistantModelEntry('gpt-5.4-nano').reasoningEffort).toBe('low')
|
||||
expect(getAssistantModelEntry('gpt-5.3-codex').reasoningEffort).toBe('low')
|
||||
expect(getAssistantModelEntry('gpt-5.4-nano')).toEqual(
|
||||
ASSISTANT_MODELS.find((m) => m.id === 'gpt-5.4-nano')
|
||||
expect(getAssistantModelEntry('gpt-5.6-luna')).toEqual(
|
||||
ASSISTANT_MODELS.find((m) => m.id === 'gpt-5.6-luna')
|
||||
)
|
||||
})
|
||||
|
||||
it('DEFAULT_COMPLETION_MODEL is gpt-5.4-nano with no reasoning effort', () => {
|
||||
expect(DEFAULT_COMPLETION_MODEL.id).toBe(DEFAULT_ASSISTANT_BASE_MODEL_ID)
|
||||
expect(DEFAULT_COMPLETION_MODEL.id).toBe('gpt-5.4-nano')
|
||||
expect(DEFAULT_COMPLETION_MODEL.reasoningEffort).toBe('none')
|
||||
})
|
||||
|
||||
it('openaiModelEntry enforces valid reasoning effort at compile time', () => {
|
||||
// Valid: supported effort level
|
||||
const withEffort = openaiModelEntry({
|
||||
id: 'gpt-5.4-nano',
|
||||
id: 'gpt-5.6-luna',
|
||||
reasoningEffort: 'low',
|
||||
})
|
||||
expect(withEffort.reasoningEffort).toBe('low')
|
||||
|
||||
// Valid: no effort
|
||||
const withoutEffort = openaiModelEntry({ id: 'gpt-5.4-nano' })
|
||||
const withoutEffort = openaiModelEntry({ id: 'gpt-5.6-luna' })
|
||||
expect(withoutEffort.reasoningEffort).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,17 +2,19 @@ export type ProviderName = 'bedrock' | 'openai'
|
||||
|
||||
export type BedrockModel = 'anthropic.claude-3-7-sonnet-20250219-v1:0' | 'openai.gpt-oss-120b-1:0'
|
||||
|
||||
export type OpenAIModelId = 'gpt-5.4-nano' | 'gpt-5.3-codex'
|
||||
export type OpenAIModelId = 'gpt-5.4-nano' | 'gpt-5.3-codex' | 'gpt-5.6-luna'
|
||||
|
||||
// Source: https://developers.openai.com/api/docs/guides/reasoning + per-model pages
|
||||
export type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
|
||||
export type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'
|
||||
|
||||
// Per-model reasoning effort compatibility.
|
||||
// Sources: https://developers.openai.com/api/docs/models/gpt-5.4-nano
|
||||
// https://developers.openai.com/api/docs/models/gpt-5.3-codex
|
||||
// https://developers.openai.com/api/docs/models/gpt-5.6-luna
|
||||
type ModelReasoningSupport = {
|
||||
'gpt-5.4-nano': 'none' | 'low' | 'medium' | 'high' | 'xhigh'
|
||||
'gpt-5.3-codex': 'low' | 'medium' | 'high' | 'xhigh'
|
||||
'gpt-5.6-luna': 'none' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'
|
||||
}
|
||||
|
||||
type ReasoningEffortFor<ModelId extends OpenAIModelId> = ModelId extends keyof ModelReasoningSupport
|
||||
@@ -59,6 +61,11 @@ export const LOGS_REWRITE_MODEL = openaiModelEntry({
|
||||
// Single source of truth for all Assistant chat model variants and their reasoning levels.
|
||||
// Models with requiresAdvanceModelEntitlement false are available to all users; true requires the assistant.advance_model entitlement.
|
||||
export const ASSISTANT_MODELS = [
|
||||
openaiModelEntry({
|
||||
id: 'gpt-5.6-luna',
|
||||
requiresAdvanceModelEntitlement: false,
|
||||
reasoningEffort: 'medium',
|
||||
}),
|
||||
openaiModelEntry({
|
||||
id: 'gpt-5.4-nano',
|
||||
requiresAdvanceModelEntitlement: false,
|
||||
@@ -82,14 +89,12 @@ const ASSISTANT_MODELS_MAP = Object.fromEntries(ASSISTANT_MODELS.map((m) => [m.i
|
||||
(typeof ASSISTANT_MODELS)[number]
|
||||
>
|
||||
|
||||
export const DEFAULT_ASSISTANT_BASE_MODEL_ID = 'gpt-5.4-nano' satisfies AssistantBaseModelId
|
||||
export const DEFAULT_ASSISTANT_BASE_MODEL_ID = 'gpt-5.6-luna' satisfies AssistantBaseModelId
|
||||
|
||||
export const DEFAULT_ASSISTANT_ADVANCE_MODEL_ID = 'gpt-5.3-codex' satisfies AssistantModelId
|
||||
|
||||
export function defaultAssistantModelId(hasAccessToAdvanceModel: boolean): AssistantModelId {
|
||||
return hasAccessToAdvanceModel
|
||||
? DEFAULT_ASSISTANT_ADVANCE_MODEL_ID
|
||||
: DEFAULT_ASSISTANT_BASE_MODEL_ID
|
||||
export function defaultAssistantModelId(_hasAccessToAdvanceModel: boolean): AssistantModelId {
|
||||
return DEFAULT_ASSISTANT_BASE_MODEL_ID
|
||||
}
|
||||
|
||||
export function isKnownAssistantModelId(id: string): id is AssistantModelId {
|
||||
@@ -153,6 +158,7 @@ export const PROVIDERS: ProviderRegistry = {
|
||||
},
|
||||
openai: {
|
||||
models: {
|
||||
'gpt-5.6-luna': { default: false },
|
||||
'gpt-5.3-codex': { default: false },
|
||||
'gpt-5.4-nano': { default: true },
|
||||
},
|
||||
|
||||
@@ -18,7 +18,6 @@ import { generateAssistantResponse } from '@/lib/ai/generate-assistant-response'
|
||||
import { isExplorerEnabled } from '@/lib/ai/is-explorer-enabled'
|
||||
import { getModel } from '@/lib/ai/model'
|
||||
import {
|
||||
DEFAULT_ASSISTANT_ADVANCE_MODEL_ID,
|
||||
DEFAULT_ASSISTANT_BASE_MODEL_ID,
|
||||
getAssistantModelEntry,
|
||||
isAssistantBaseModelId,
|
||||
@@ -157,7 +156,7 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse, claims?: Jw
|
||||
|
||||
const envThrottled = process.env.IS_THROTTLED !== 'false'
|
||||
|
||||
let effectiveModel: AssistantModelId = requestedModel ?? DEFAULT_ASSISTANT_ADVANCE_MODEL_ID
|
||||
let effectiveModel: AssistantModelId = requestedModel ?? DEFAULT_ASSISTANT_BASE_MODEL_ID
|
||||
if (!hasAccessToAdvanceModel || (envThrottled && !isAssistantBaseModelId(effectiveModel))) {
|
||||
effectiveModel = DEFAULT_ASSISTANT_BASE_MODEL_ID
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ describe('useAiAssistantChatList', () => {
|
||||
state.loadPersistedState({
|
||||
projectRef: 'default',
|
||||
activeChatId: 'persisted-chat',
|
||||
model: state.model,
|
||||
chats: {
|
||||
'persisted-chat': {
|
||||
id: 'persisted-chat',
|
||||
|
||||
@@ -146,7 +146,6 @@ describe('whenAiAssistantInitialized', () => {
|
||||
state.loadPersistedState({
|
||||
projectRef: 'default',
|
||||
activeChatId: 'persisted-chat',
|
||||
model: state.model,
|
||||
chats: {
|
||||
'persisted-chat': {
|
||||
id: 'persisted-chat',
|
||||
|
||||
Reference in new issue
Block a user