Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fc9cb91
Add chat history to Assistant Ai
ravikiranvm Oct 31, 2025
c88d1e2
Merge branch 'main' into ops-2115
ravikiranvm Oct 31, 2025
01396f2
Fix history item, assistant width
ravikiranvm Nov 3, 2025
fe222df
Replace history panel with popover
ravikiranvm Nov 4, 2025
975da57
Improve history popover trigger
ravikiranvm Nov 4, 2025
761759d
Merge branch 'main' into ops-2115
ravikiranvm Nov 4, 2025
a200e4d
Simplify onHistoryOpenChange callback
ravikiranvm Nov 4, 2025
dc74a6c
Use chat name for Ai Assistant title
ravikiranvm Nov 4, 2025
5283659
Add animation to the newly available chat name
ravikiranvm Nov 4, 2025
61a5f16
Fix top bar buttons tooltip content
ravikiranvm Nov 4, 2025
8163b0c
Prevent tooltip rendering on clicking in history popover
ravikiranvm Nov 4, 2025
8b03b38
Fix designer comments
ravikiranvm Nov 4, 2025
da609f9
Prevent chat name from overflowing in the top bar
ravikiranvm Nov 4, 2025
95af9d0
Rename constant for better clarity
ravikiranvm Nov 4, 2025
f02d410
Add error handling for AI history hook
ravikiranvm Nov 5, 2025
67d3cb1
Remove redundant chatContextKey function
ravikiranvm Nov 5, 2025
7dd6e7a
Add tests for delete chat endpoint
ravikiranvm Nov 5, 2025
a73c4c0
Merge branch 'main' into ops-2115
ravikiranvm Nov 5, 2025
0141f86
Fix lint error
ravikiranvm Nov 5, 2025
aa04c00
Revert backend changes to open a new PR
ravikiranvm Nov 5, 2025
d8d3392
Merge branch 'main' into ops-2115
ravikiranvm Nov 6, 2025
8db6db8
Merge remote-tracking branch 'origin/main' into ops-2115
ravikiranvm Nov 10, 2025
fea2b08
Fix potential memory leak with animation effect
ravikiranvm Nov 10, 2025
840454d
Remove unnecessary use of timeout
ravikiranvm Nov 10, 2025
c57b6c0
Extract assistant history query key to keys file
ravikiranvm Nov 10, 2025
98f6b77
Refetch chat history on window focus
ravikiranvm Nov 10, 2025
4ec5fb3
Use better name for refetch function
ravikiranvm Nov 10, 2025
33c472a
Fix comments about tailwind classes
ravikiranvm Nov 10, 2025
bf86fd4
Fix tailwind classes
ravikiranvm Nov 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/react-ui/src/app/constants/query-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const QueryKeys = {
mcpSettings: 'mcp-settings',
aiSettingsProviders: 'ai-settings-providers',
aiProviderModels: 'ai-provider-models',
assistantHistory: 'assistant-history',

// Platform
organization: 'organization',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const AiChatResizablePanel = ({ onDragging }: AiChatResizablePanelProps) => {
order={2}
id={RESIZABLE_PANEL_IDS.AI_CHAT}
className={cn('duration-0 min-w-0 shadow-sidebar', {
'min-w-[300px] max-w-[500px] z-[11]': showChat,
'min-w-[388px] max-w-[500px] z-[11]': showChat,
})}
minSize={size}
maxSize={size}
Expand Down
104 changes: 86 additions & 18 deletions packages/react-ui/src/app/features/ai/assistant/assistant-ui-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import { AI_ASSISTANT_SS_KEY } from '@/app/constants/ai';
import { useAiModelSelector } from '@/app/features/ai/lib/ai-model-selector-hook';
import { useAssistantChat } from '@/app/features/ai/lib/assistant-ui-chat-hook';
import { useBuilderStoreOutsideProviderWithSubscription } from '@/app/features/builder/builder-state-provider';
import { AssistantUiChatContainer } from '@openops/components/ui';
import {
AssistantUiChatContainer,
AssistantUiHistory,
} from '@openops/components/ui';
import { SourceCode } from '@openops/shared';
import { createFrontendTools } from '@openops/ui-kit';
import { t } from 'i18next';
import { ReactNode, useCallback, useMemo, useState } from 'react';
import { useNetworkStatusWithWarning } from '../lib/hooks/use-network-status-with-warning';
import { ChatMode } from '../lib/types';
import { useAssistantChatHistory } from '../lib/use-ai-assistant-chat-history';

type AssistantUiChatProps = {
onClose: () => void;
Expand All @@ -27,6 +31,7 @@ const AssistantUiChat = ({
const toolComponents = useMemo(() => {
return createFrontendTools();
}, []);
const [showHistory, setShowHistory] = useState(false);

const [chatId, setChatId] = useState<string | null>(
sessionStorage.getItem(AI_ASSISTANT_SS_KEY),
Expand Down Expand Up @@ -67,9 +72,56 @@ const AssistantUiChat = ({
isLoading: isModelSelectorLoading,
} = useAiModelSelector({ chatId, provider, model });

const {
chats,
isLoading: isHistoryLoading,
deleteChat,
renameChat,
refetchChatList,
} = useAssistantChatHistory();

const { isShowingSlowWarning, connectionError } =
useNetworkStatusWithWarning(chatStatus);

const currentChatTitle = useMemo(() => {
if (chatId) {
const currentChat = chats.find((chat) => chat.id === chatId);
return currentChat?.displayName || title;
}
return title;
}, [chatId, chats, title]);

const onChatSelected = useCallback(
(id: string) => {
onChatIdChange(id);
setShowHistory(false);
},
[onChatIdChange],
);

const onChatDeleted = useCallback(
async (id: string) => {
await deleteChat(id);
if (chatId === id) {
onChatIdChange(null);
}
},
[chatId, deleteChat, onChatIdChange],
);

const onChatRenamed = useCallback(
async (id: string, newName: string) => {
await renameChat({ chatId: id, chatName: newName });
},
[renameChat],
);

const onNewChatClick = useCallback(async () => {
await createNewChat();
refetchChatList();
setShowHistory(false);
}, [createNewChat, refetchChatList]);

if (isLoading) {
return (
<div className="w-full flex h-full items-center justify-center bg-background">
Expand All @@ -81,23 +133,39 @@ const AssistantUiChat = ({
}

return (
<AssistantUiChatContainer
onClose={onClose}
runtime={runtime}
onNewChat={createNewChat}
title={title}
availableModels={availableModels}
onModelSelected={onModelSelected}
isModelSelectorLoading={isModelSelectorLoading}
selectedModel={selectedModel}
theme={theme}
handleInject={handleInject}
toolComponents={toolComponents}
isShowingSlowWarning={isShowingSlowWarning}
connectionError={connectionError}
>
{children}
</AssistantUiChatContainer>
<div className="w-full h-full flex">
<AssistantUiChatContainer
onClose={onClose}
runtime={runtime}
onNewChat={onNewChatClick}
title={currentChatTitle}
availableModels={availableModels}
onModelSelected={onModelSelected}
isModelSelectorLoading={isModelSelectorLoading}
selectedModel={selectedModel}
theme={theme}
handleInject={handleInject}
toolComponents={toolComponents}
onHistoryOpenChange={setShowHistory}
isHistoryOpen={showHistory}
chatId={chatId}
isShowingSlowWarning={isShowingSlowWarning}
connectionError={connectionError}
>
{showHistory && (
<AssistantUiHistory
onNewChat={onNewChatClick}
newChatDisabled={isLoading || isHistoryLoading}
onChatSelected={onChatSelected}
onChatDeleted={onChatDeleted}
onChatRenamed={onChatRenamed}
chatItems={chats}
selectedItemId={chatId ?? undefined}
/>
)}
{children}
</AssistantUiChatContainer>
</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { api } from '@/app/lib/api';
import { ListChatsResponse } from '@openops/shared';

export const aiAssistantChatHistoryApi = {
list() {
return api.get<ListChatsResponse>('/v1/ai/conversation/all-chats');
},
delete(chatId: string) {
return api.delete<void>(`/v1/ai/conversation/${chatId}`);
},
generateName(chatId: string) {
return api.post<{ chatName: string }>('/v1/ai/conversation/chat-name', {
chatId,
});
},
rename(chatId: string, chatName: string) {
return api.patch<{ chatName: string }>(
`/v1/ai/conversation/${chatId}/name`,
{
chatName,
},
);
},
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { QueryKeys } from '@/app/constants/query-keys';
import { aiAssistantChatApi } from '@/app/features/ai/lib/ai-assistant-chat-api';
import { getActionName, getBlockName } from '@/app/features/blocks/lib/utils';
import { authenticationSession } from '@/app/lib/authentication-session';
Expand All @@ -7,17 +8,20 @@ import { useAISDKRuntime } from '@assistant-ui/react-ai-sdk';
import { toast } from '@openops/components/ui';
import { flowHelper } from '@openops/shared';
import { getFrontendToolDefinitions } from '@openops/ui-kit';
import { useQuery } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { DefaultChatTransport, ToolSet, UIMessage } from 'ai';
import { t } from 'i18next';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { aiChatApi } from '../../builder/ai-chat/lib/chat-api';
import { getBuilderStore } from '../../builder/builder-state-provider';
import { aiAssistantChatHistoryApi } from './ai-assistant-chat-history-api';
import { aiSettingsHooks } from './ai-settings-hooks';
import { buildQueryKey } from './chat-utils';
import { createAdditionalContext } from './enrich-context';
import { ChatMode, UseAssistantChatProps } from './types';

export const MIN_MESSAGES_BEFORE_NAME_GENERATION = 1;

export const useAssistantChat = ({
chatId,
onChatIdChange,
Expand All @@ -29,6 +33,8 @@ export const useAssistantChat = ({
() => getFrontendToolDefinitions() as ToolSet,
[],
);
const qc = useQueryClient();
const hasAttemptedNameGenerationRef = useRef<Record<string, boolean>>({});

const [provider, setProvider] = useState<string | undefined>();
const [model, setModel] = useState<string | undefined>();
Expand Down Expand Up @@ -229,6 +235,23 @@ export const useAssistantChat = ({
};
toast(errorToast);
},
onFinish: async () => {
if (!chatId || hasAttemptedNameGenerationRef.current[chatId]) {
return;
}

if (messagesRef.current.length >= MIN_MESSAGES_BEFORE_NAME_GENERATION) {
try {
hasAttemptedNameGenerationRef.current[chatId] = true;
await aiAssistantChatHistoryApi.generateName(chatId);
qc.invalidateQueries({ queryKey: [QueryKeys.assistantHistory] });
} catch (error) {
console.error('Failed to generate chat name', error);
hasAttemptedNameGenerationRef.current[chatId] = false;
qc.invalidateQueries({ queryKey: [QueryKeys.assistantHistory] });
}
}
},
// https://github.com/assistant-ui/assistant-ui/issues/2327
// handle frontend tool calls manually until this is fixed
onToolCall: async ({ toolCall }: { toolCall: any }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { QueryKeys } from '@/app/constants/query-keys';
import { toast } from '@openops/components/ui';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { t } from 'i18next';
import { aiAssistantChatHistoryApi } from './ai-assistant-chat-history-api';

export function useAssistantChatHistory() {
const qc = useQueryClient();

const { data, isLoading } = useQuery({
queryKey: [QueryKeys.assistantHistory],
queryFn: async () => {
const res = await aiAssistantChatHistoryApi.list();
return res.chats ?? [];
},
select: (chats) =>
chats.map((c) => ({
id: c.chatId,
displayName: c.chatName || 'New chat',
})),
});

const deleteMutation = useMutation({
mutationFn: (chatId: string) => aiAssistantChatHistoryApi.delete(chatId),
onSuccess: () =>
qc.invalidateQueries({ queryKey: [QueryKeys.assistantHistory] }),
onError: (error) => {
console.error('Failed to delete chat', error);
toast({
title: t('Error'),
variant: 'destructive',
description: t('Failed to delete chat. Please try again.'),
duration: 3000,
});
},
});

const renameMutation = useMutation({
mutationFn: ({ chatId, chatName }: { chatId: string; chatName: string }) =>
aiAssistantChatHistoryApi.rename(chatId, chatName),
onSuccess: () =>
qc.invalidateQueries({ queryKey: [QueryKeys.assistantHistory] }),
onError: (error) => {
console.error('Failed to rename chat', error);
toast({
title: t('Error'),
variant: 'destructive',
description: t('Failed to rename chat. Please try again.'),
duration: 3000,
});
},
});

return {
chats: data ?? [],
isLoading,
deleteChat: deleteMutation.mutateAsync,
renameChat: renameMutation.mutateAsync,
refetchChatList: () =>
qc.invalidateQueries({ queryKey: [QueryKeys.assistantHistory] }),
};
}
Loading
Loading