mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2025-11-30 03:18:02 +08:00
chore: fix chat model select and add test
This commit is contained in:
205
cypress/e2e/chat/model-selection-persistence.cy.ts
Normal file
205
cypress/e2e/chat/model-selection-persistence.cy.ts
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { AuthTestUtils } from '../../support/auth-utils';
|
||||||
|
import { TestTool } from '../../support/page-utils';
|
||||||
|
import { PageSelectors, SidebarSelectors, ModelSelectorSelectors } from '../../support/selectors';
|
||||||
|
|
||||||
|
describe('Chat Model Selection Persistence Tests', () => {
|
||||||
|
const APPFLOWY_BASE_URL = Cypress.env('APPFLOWY_BASE_URL');
|
||||||
|
const APPFLOWY_GOTRUE_BASE_URL = Cypress.env('APPFLOWY_GOTRUE_BASE_URL');
|
||||||
|
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
|
||||||
|
let testEmail: string;
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
// Log environment configuration for debugging
|
||||||
|
cy.task('log', `Test Environment Configuration:
|
||||||
|
- APPFLOWY_BASE_URL: ${APPFLOWY_BASE_URL}
|
||||||
|
- APPFLOWY_GOTRUE_BASE_URL: ${APPFLOWY_GOTRUE_BASE_URL}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Generate unique test data for each test
|
||||||
|
testEmail = generateRandomEmail();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Model Selection Persistence', () => {
|
||||||
|
it('should persist selected model after page reload', () => {
|
||||||
|
// Handle uncaught exceptions during workspace creation
|
||||||
|
cy.on('uncaught:exception', (err: Error) => {
|
||||||
|
if (err.message.includes('No workspace or service found')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (err.message.includes('View not found')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (err.message.includes('WebSocket') || err.message.includes('connection')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Step 1: Login
|
||||||
|
cy.task('log', '=== Step 1: Login ===');
|
||||||
|
cy.visit('/login', { failOnStatusCode: false });
|
||||||
|
cy.wait(2000);
|
||||||
|
|
||||||
|
const authUtils = new AuthTestUtils();
|
||||||
|
authUtils.signInWithTestUrl(testEmail).then(() => {
|
||||||
|
cy.url().should('include', '/app');
|
||||||
|
|
||||||
|
// Wait for the app to fully load
|
||||||
|
cy.task('log', 'Waiting for app to fully load...');
|
||||||
|
|
||||||
|
// Wait for the loading screen to disappear and main app to appear
|
||||||
|
cy.get('body', { timeout: 30000 }).should('not.contain', 'Welcome!');
|
||||||
|
|
||||||
|
// Wait for the sidebar to be visible (indicates app is loaded)
|
||||||
|
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
|
||||||
|
|
||||||
|
// Wait for at least one page to exist in the sidebar
|
||||||
|
PageSelectors.names().should('exist', { timeout: 30000 });
|
||||||
|
|
||||||
|
// Additional wait for stability
|
||||||
|
cy.wait(2000);
|
||||||
|
|
||||||
|
// Step 2: Create an AI Chat
|
||||||
|
cy.task('log', '=== Step 2: Creating AI Chat ===');
|
||||||
|
|
||||||
|
// Expand the first space to see its pages
|
||||||
|
TestTool.expandSpace();
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
// Find the first page item and hover over it to show actions
|
||||||
|
PageSelectors.items().first().then($page => {
|
||||||
|
cy.task('log', 'Hovering over first page to show action buttons...');
|
||||||
|
|
||||||
|
// Hover over the page to reveal the action buttons
|
||||||
|
cy.wrap($page)
|
||||||
|
.trigger('mouseenter', { force: true })
|
||||||
|
.trigger('mouseover', { force: true });
|
||||||
|
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
// Click the inline add button (plus icon)
|
||||||
|
cy.wrap($page).within(() => {
|
||||||
|
cy.get('[data-testid="inline-add-page"]')
|
||||||
|
.first()
|
||||||
|
.should('be.visible')
|
||||||
|
.click({ force: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for the dropdown menu to appear
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
// Click on the AI Chat option from the dropdown
|
||||||
|
cy.get('[data-testid="add-ai-chat-button"]')
|
||||||
|
.should('be.visible')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.task('log', 'Created AI Chat');
|
||||||
|
|
||||||
|
// Wait for navigation to the AI chat page
|
||||||
|
cy.wait(3000);
|
||||||
|
|
||||||
|
// Step 3: Open model selector and select a model
|
||||||
|
cy.task('log', '=== Step 3: Selecting a Model ===');
|
||||||
|
|
||||||
|
// Wait for the chat interface to load
|
||||||
|
cy.wait(2000);
|
||||||
|
|
||||||
|
// Click on the model selector button
|
||||||
|
ModelSelectorSelectors.button()
|
||||||
|
.should('be.visible', { timeout: 10000 })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.task('log', 'Opened model selector dropdown');
|
||||||
|
|
||||||
|
// Wait for the dropdown to appear and models to load
|
||||||
|
cy.wait(2000);
|
||||||
|
|
||||||
|
// Select a specific model (we'll select the first non-Auto model if available)
|
||||||
|
ModelSelectorSelectors.options()
|
||||||
|
.then($options => {
|
||||||
|
// Find a model that's not "Auto"
|
||||||
|
const nonAutoOptions = $options.filter((i, el) => {
|
||||||
|
const testId = el.getAttribute('data-testid');
|
||||||
|
return testId && !testId.includes('model-option-Auto');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nonAutoOptions.length > 0) {
|
||||||
|
// Click the first non-Auto model
|
||||||
|
const selectedModel = nonAutoOptions[0].getAttribute('data-testid')?.replace('model-option-', '');
|
||||||
|
cy.task('log', `Selecting model: ${selectedModel}`);
|
||||||
|
cy.wrap(nonAutoOptions[0]).click();
|
||||||
|
|
||||||
|
// Store the selected model name for verification
|
||||||
|
cy.wrap(selectedModel).as('selectedModel');
|
||||||
|
} else {
|
||||||
|
// If only Auto is available, select it explicitly
|
||||||
|
cy.task('log', 'Only Auto model available, selecting it');
|
||||||
|
ModelSelectorSelectors.optionByName('Auto').click();
|
||||||
|
cy.wrap('Auto').as('selectedModel');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for the selection to be applied
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
// Verify the model is selected by checking the button text
|
||||||
|
cy.get('@selectedModel').then((modelName) => {
|
||||||
|
cy.task('log', `Verifying model ${modelName} is displayed in button`);
|
||||||
|
ModelSelectorSelectors.button()
|
||||||
|
.should('contain.text', modelName);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Step 4: Save the current URL for reload
|
||||||
|
cy.task('log', '=== Step 4: Saving current URL ===');
|
||||||
|
cy.url().then(url => {
|
||||||
|
cy.wrap(url).as('chatUrl');
|
||||||
|
cy.task('log', `Current chat URL: ${url}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Step 5: Reload the page
|
||||||
|
cy.task('log', '=== Step 5: Reloading page ===');
|
||||||
|
cy.reload();
|
||||||
|
|
||||||
|
// Wait for the page to reload completely
|
||||||
|
cy.wait(3000);
|
||||||
|
|
||||||
|
// Step 6: Verify the model selection persisted
|
||||||
|
cy.task('log', '=== Step 6: Verifying Model Selection Persisted ===');
|
||||||
|
|
||||||
|
// Wait for the model selector button to be visible again
|
||||||
|
ModelSelectorSelectors.button()
|
||||||
|
.should('be.visible', { timeout: 10000 });
|
||||||
|
|
||||||
|
// Verify the previously selected model is still displayed
|
||||||
|
cy.get('@selectedModel').then((modelName) => {
|
||||||
|
cy.task('log', `Checking if model ${modelName} is still selected after reload`);
|
||||||
|
ModelSelectorSelectors.button()
|
||||||
|
.should('contain.text', modelName);
|
||||||
|
cy.task('log', `✓ Model ${modelName} persisted after page reload!`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Optional: Open the dropdown again to verify the selection visually
|
||||||
|
cy.task('log', '=== Step 7: Double-checking selection in dropdown ===');
|
||||||
|
ModelSelectorSelectors.button().click();
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
// Verify the selected model has the selected styling
|
||||||
|
cy.get('@selectedModel').then((modelName) => {
|
||||||
|
ModelSelectorSelectors.optionByName(modelName as string)
|
||||||
|
.should('have.class', 'bg-fill-content-select');
|
||||||
|
cy.task('log', `✓ Model ${modelName} shows as selected in dropdown`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close the dropdown
|
||||||
|
cy.get('body').click(0, 0);
|
||||||
|
|
||||||
|
// Final verification
|
||||||
|
cy.task('log', '=== Test completed successfully! ===');
|
||||||
|
cy.task('log', '✓✓✓ Model selection persisted after page reload');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -184,6 +184,27 @@ export const SidebarSelectors = {
|
|||||||
pageHeader: () => cy.get(byTestId('sidebar-page-header')),
|
pageHeader: () => cy.get(byTestId('sidebar-page-header')),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat Model Selector-related selectors
|
||||||
|
* Used for testing AI model selection in chat interface
|
||||||
|
*/
|
||||||
|
export const ModelSelectorSelectors = {
|
||||||
|
// Model selector button
|
||||||
|
button: () => cy.get(byTestId('model-selector-button')),
|
||||||
|
|
||||||
|
// Model search input
|
||||||
|
searchInput: () => cy.get(byTestId('model-search-input')),
|
||||||
|
|
||||||
|
// Get all model options
|
||||||
|
options: () => cy.get('[data-testid^="model-option-"]'),
|
||||||
|
|
||||||
|
// Get specific model option by name
|
||||||
|
optionByName: (modelName: string) => cy.get(byTestId(`model-option-${modelName}`)),
|
||||||
|
|
||||||
|
// Get selected model option (has the selected class)
|
||||||
|
selectedOption: () => cy.get('[data-testid^="model-option-"]').filter('.bg-fill-content-select'),
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database Grid-related selectors
|
* Database Grid-related selectors
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,46 +1,71 @@
|
|||||||
// Code: Chat main component
|
// Code: Chat main component
|
||||||
import { ChatContext } from './context';
|
|
||||||
import { ChatInput } from '@/components/chat/components/chat-input';
|
import { ChatInput } from '@/components/chat/components/chat-input';
|
||||||
import { ChatMessages } from '@/components/chat/components/chat-messages';
|
import { ChatMessages } from '@/components/chat/components/chat-messages';
|
||||||
|
import { ModelSelectorContext } from '@/components/chat/contexts/model-selector-context';
|
||||||
import { cn } from '@/components/chat/lib/utils';
|
import { cn } from '@/components/chat/lib/utils';
|
||||||
import { MessageAnimationProvider } from '@/components/chat/provider/message-animation-provider';
|
|
||||||
import { EditorProvider } from '@/components/chat/provider/editor-provider';
|
import { EditorProvider } from '@/components/chat/provider/editor-provider';
|
||||||
import { MessagesHandlerProvider } from '@/components/chat/provider/messages-handler-provider';
|
import { MessageAnimationProvider } from '@/components/chat/provider/message-animation-provider';
|
||||||
|
import { MessagesHandlerProvider, useMessagesHandlerContext } from '@/components/chat/provider/messages-handler-provider';
|
||||||
import { ChatMessagesProvider } from '@/components/chat/provider/messages-provider';
|
import { ChatMessagesProvider } from '@/components/chat/provider/messages-provider';
|
||||||
import { PromptModalProvider } from '@/components/chat/provider/prompt-modal-provider';
|
import { PromptModalProvider } from '@/components/chat/provider/prompt-modal-provider';
|
||||||
import { ResponseFormatProvider } from '@/components/chat/provider/response-format-provider';
|
import { ResponseFormatProvider } from '@/components/chat/provider/response-format-provider';
|
||||||
import { SelectionModeProvider } from '@/components/chat/provider/selection-mode-provider';
|
import { SelectionModeProvider } from '@/components/chat/provider/selection-mode-provider';
|
||||||
import { SuggestionsProvider } from '@/components/chat/provider/suggestions-provider';
|
import { SuggestionsProvider } from '@/components/chat/provider/suggestions-provider';
|
||||||
import { ChatProps } from '@/components/chat/types';
|
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
|
||||||
import { ViewLoaderProvider } from '@/components/chat/provider/view-loader-provider';
|
import { ViewLoaderProvider } from '@/components/chat/provider/view-loader-provider';
|
||||||
import { ModelSelectorContext } from '@/components/chat/contexts/model-selector-context';
|
import { ChatProps, User } from '@/components/chat/types';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { ChatContext, useChatContext } from './context';
|
||||||
|
|
||||||
function Main(props: ChatProps) {
|
// Component to bridge ModelSelector with MessagesHandler
|
||||||
const { currentUser, selectionMode } = props;
|
function ChatContentWithModelSync({ currentUser, selectionMode }: { currentUser?: User; selectionMode?: boolean }) {
|
||||||
|
const { selectedModelName, setSelectedModelName } = useMessagesHandlerContext();
|
||||||
|
const { requestInstance, chatId } = useChatContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatContext.Provider value={props}>
|
|
||||||
<ModelSelectorContext.Provider
|
<ModelSelectorContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
selectedModelName,
|
||||||
|
setSelectedModelName,
|
||||||
requestInstance: {
|
requestInstance: {
|
||||||
getModelList: () => props.requestInstance.getModelList(),
|
getModelList: () => requestInstance.getModelList(),
|
||||||
getCurrentModel: async () => {
|
getCurrentModel: async () => {
|
||||||
const settings = await props.requestInstance.getChatSettings();
|
const settings = await requestInstance.getChatSettings();
|
||||||
|
|
||||||
return settings.metadata?.ai_model as string | undefined || '';
|
return settings.metadata?.ai_model as string | undefined || '';
|
||||||
},
|
},
|
||||||
setCurrentModel: async (modelName: string) => {
|
setCurrentModel: async (modelName: string) => {
|
||||||
await props.requestInstance.updateChatSettings({
|
await requestInstance.updateChatSettings({
|
||||||
metadata: {
|
metadata: {
|
||||||
ai_model: modelName
|
ai_model: modelName
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chatId: props.chatId,
|
chatId,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div className={'w-full relative h-full flex flex-col'}>
|
||||||
|
<ChatMessages currentUser={currentUser} />
|
||||||
|
<motion.div
|
||||||
|
layout
|
||||||
|
className={cn(
|
||||||
|
'w-full relative flex pb-6 justify-center max-sm:hidden',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AnimatePresence mode='wait'>
|
||||||
|
{!selectionMode && <ChatInput />}
|
||||||
|
</AnimatePresence>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</ModelSelectorContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Main(props: ChatProps) {
|
||||||
|
const { currentUser, selectionMode } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChatContext.Provider value={props}>
|
||||||
<ChatMessagesProvider>
|
<ChatMessagesProvider>
|
||||||
<MessageAnimationProvider>
|
<MessageAnimationProvider>
|
||||||
<SuggestionsProvider>
|
<SuggestionsProvider>
|
||||||
@@ -61,19 +86,7 @@ function Main(props: ChatProps) {
|
|||||||
testDatabasePromptConfig={props.testDatabasePromptConfig}
|
testDatabasePromptConfig={props.testDatabasePromptConfig}
|
||||||
>
|
>
|
||||||
<MessagesHandlerProvider>
|
<MessagesHandlerProvider>
|
||||||
<div className={'w-full relative h-full flex flex-col'}>
|
<ChatContentWithModelSync currentUser={currentUser} selectionMode={selectionMode} />
|
||||||
<ChatMessages currentUser={currentUser} />
|
|
||||||
<motion.div
|
|
||||||
layout
|
|
||||||
className={cn(
|
|
||||||
'w-full relative flex pb-6 justify-center max-sm:hidden',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<AnimatePresence mode='wait'>
|
|
||||||
{!selectionMode && <ChatInput />}
|
|
||||||
</AnimatePresence>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</MessagesHandlerProvider>
|
</MessagesHandlerProvider>
|
||||||
</PromptModalProvider>
|
</PromptModalProvider>
|
||||||
</ResponseFormatProvider>
|
</ResponseFormatProvider>
|
||||||
@@ -83,7 +96,6 @@ function Main(props: ChatProps) {
|
|||||||
</SuggestionsProvider>
|
</SuggestionsProvider>
|
||||||
</MessageAnimationProvider>
|
</MessageAnimationProvider>
|
||||||
</ChatMessagesProvider>
|
</ChatMessagesProvider>
|
||||||
</ModelSelectorContext.Provider>
|
|
||||||
</ChatContext.Provider>
|
</ChatContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ export function ModelSelector({ className, disabled }: ModelSelectorProps) {
|
|||||||
className={cn('h-7 gap-1 px-2 text-xs font-normal text-text-secondary', className)}
|
className={cn('h-7 gap-1 px-2 text-xs font-normal text-text-secondary', className)}
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
data-testid="model-selector-button"
|
||||||
title={hasContext ? 'Select AI Model' : 'Model selector (offline mode)'}
|
title={hasContext ? 'Select AI Model' : 'Model selector (offline mode)'}
|
||||||
>
|
>
|
||||||
{AISparksIcon ? <AISparksIcon className='h-5 w-5 text-icon-secondary' /> : <span className='text-[10px]'>🤖</span>}
|
{AISparksIcon ? <AISparksIcon className='h-5 w-5 text-icon-secondary' /> : <span className='text-[10px]'>🤖</span>}
|
||||||
@@ -229,6 +230,7 @@ export function ModelSelector({ className, disabled }: ModelSelectorProps) {
|
|||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className='w-full bg-transparent px-2 py-1 text-sm outline-none placeholder:text-text-placeholder'
|
className='w-full bg-transparent px-2 py-1 text-sm outline-none placeholder:text-text-placeholder'
|
||||||
|
data-testid="model-search-input"
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -238,7 +240,7 @@ export function ModelSelector({ className, disabled }: ModelSelectorProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Models List */}
|
{/* Models List */}
|
||||||
<div className='max-h-[380px] overflow-y-auto py-1'>
|
<div className='appflowy-scrollbar max-h-[380px] overflow-y-auto py-1'>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className='px-3 py-8 text-center text-sm text-text-secondary'>Loading models...</div>
|
<div className='px-3 py-8 text-center text-sm text-text-secondary'>Loading models...</div>
|
||||||
) : filteredModels.length === 0 ? (
|
) : filteredModels.length === 0 ? (
|
||||||
@@ -260,6 +262,7 @@ export function ModelSelector({ className, disabled }: ModelSelectorProps) {
|
|||||||
'focus:bg-fill-content-hover focus:outline-none',
|
'focus:bg-fill-content-hover focus:outline-none',
|
||||||
isSelected && 'bg-fill-content-select'
|
isSelected && 'bg-fill-content-select'
|
||||||
)}
|
)}
|
||||||
|
data-testid={`model-option-${model.name}`}
|
||||||
>
|
>
|
||||||
<div className='min-w-0 flex-1'>
|
<div className='min-w-0 flex-1'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
|
|||||||
@@ -39,8 +39,29 @@ export const MessagesHandlerContext = createContext<MessagesHandlerContextTypes
|
|||||||
function useMessagesHandler() {
|
function useMessagesHandler() {
|
||||||
const { chatId, requestInstance, currentUser } = useChatContext();
|
const { chatId, requestInstance, currentUser } = useChatContext();
|
||||||
|
|
||||||
|
// Get the current model from chat settings
|
||||||
const [selectedModelName, setSelectedModelName] = useState<string>();
|
const [selectedModelName, setSelectedModelName] = useState<string>();
|
||||||
|
|
||||||
|
// Load initial model from settings
|
||||||
|
useEffect(() => {
|
||||||
|
const loadModel = async () => {
|
||||||
|
try {
|
||||||
|
const settings = await requestInstance.getChatSettings();
|
||||||
|
const model = settings.metadata?.ai_model as string | undefined;
|
||||||
|
|
||||||
|
if (model) {
|
||||||
|
setSelectedModelName(model);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load chat model settings:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (chatId) {
|
||||||
|
void loadModel();
|
||||||
|
}
|
||||||
|
}, [chatId, requestInstance]);
|
||||||
|
|
||||||
const { messageIds, addMessages, insertMessage, removeMessages, saveMessageContent, getMessage } =
|
const { messageIds, addMessages, insertMessage, removeMessages, saveMessageContent, getMessage } =
|
||||||
useChatMessagesContext();
|
useChatMessagesContext();
|
||||||
|
|
||||||
@@ -318,6 +339,20 @@ function useMessagesHandler() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Update local state and persist to chat settings
|
||||||
|
const updateSelectedModel = useCallback(async (modelName: string) => {
|
||||||
|
setSelectedModelName(modelName);
|
||||||
|
try {
|
||||||
|
await requestInstance.updateChatSettings({
|
||||||
|
metadata: {
|
||||||
|
ai_model: modelName
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update chat model settings:', error);
|
||||||
|
}
|
||||||
|
}, [requestInstance]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetchMessages,
|
fetchMessages,
|
||||||
submitQuestion,
|
submitQuestion,
|
||||||
@@ -327,7 +362,7 @@ function useMessagesHandler() {
|
|||||||
questionSending,
|
questionSending,
|
||||||
answerApplying,
|
answerApplying,
|
||||||
selectedModelName,
|
selectedModelName,
|
||||||
setSelectedModelName,
|
setSelectedModelName: updateSelectedModel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user