in frontend/apps/quantgrid/src/app/components/ChatWrapper/useOverlay/useOverlay.ts [76:298]
export function useOverlay(containerRef: RefObject<HTMLDivElement>) {
const {
projectSheets,
sheetName,
projectName,
projectPath,
projectBucket,
selectedCell,
} = useContext(ProjectContext);
const { inputs } = useContext(InputsContext);
const { userBucket } = useContext(ApiContext);
const { theme, canvasSpreadsheetMode } = useContext(AppContext);
const gridApi = useGridApi();
const subscriptions = useRef<(() => void)[]>([]);
const [overlay, setOverlay] = useState<ChatOverlay | null>(null);
const [GPTSuggestions, setGPTSuggestions] = useState<GPTSuggestion[] | null>(
null
);
const [lastStageCompleted, setLastStageCompleted] = useState(false);
const clearSuggestions = useCallback(() => {
setGPTSuggestions(null);
setLastStageCompleted(false);
}, []);
const updateSuggestions = useCallback(
async (overlayInstance: ChatOverlay) => {
if (!overlayInstance) return;
const { messages } =
(await overlayInstance.getMessages()) as GetMessagesResponse;
const { isCompleted, suggestions } = getSuggestions(messages);
setLastStageCompleted(isCompleted);
setGPTSuggestions(suggestions);
},
[]
);
const handleInitOverlay = useCallback(
async (overlay: ChatOverlay) => {
if (!projectName || !projectBucket || !overlay) return;
const savedSelectedConversations = getProjectSelectedConversations(
projectName,
projectBucket,
projectPath
);
const { conversations } = await overlay.getConversations();
const allConversationsIds = conversations.map(({ id }) => id);
const isAllExists = savedSelectedConversations.every((id) =>
allConversationsIds.includes(id)
);
const projectConversationPath = constructPath([
bindConversationsRootFolder,
projectPath,
projectName,
]);
if (savedSelectedConversations.length > 0 && isAllExists) {
const selectedConversationId = savedSelectedConversations[0];
overlay.selectConversation(selectedConversationId);
setSelectedConversations(
[selectedConversationId],
projectName,
projectBucket,
projectPath
);
return;
}
const conversationsFolderId = constructPath([
'conversations',
projectBucket,
projectConversationPath,
]);
const projectConversations = conversations.filter(
({ folderId }) => folderId === conversationsFolderId
);
if (projectConversations.length) {
overlay.selectConversation(projectConversations[0].id);
setSelectedConversations(
[projectConversations[0].id],
projectName,
projectBucket,
projectPath
);
return;
}
if (userBucket === projectBucket) {
await overlay.createConversation(projectConversationPath);
return;
}
},
[projectBucket, projectName, projectPath, userBucket]
);
useEffect(() => {
if (!projectName || !projectBucket || !overlay) return;
handleInitOverlay(overlay);
const unsubscribeStartGenerating = overlay.subscribe(
`@DIAL_OVERLAY/GPT_START_GENERATING`,
clearSuggestions
);
const unsubscribeEndGenerating = overlay.subscribe(
`@DIAL_OVERLAY/GPT_END_GENERATING`,
async () => updateSuggestions(overlay)
);
const unsubscribeSelectConversation = overlay.subscribe(
`@DIAL_OVERLAY/SELECTED_CONVERSATION_LOADED`,
async (payload: unknown) => {
const { selectedConversationIds } =
payload as SelectedConversationLoadedResponse;
setSelectedConversations(
selectedConversationIds,
projectName,
projectBucket,
projectPath
);
clearSuggestions();
updateSuggestions(overlay);
}
);
subscriptions.current?.push(
unsubscribeEndGenerating,
unsubscribeStartGenerating,
unsubscribeSelectConversation
);
return () => {
subscriptions.current.forEach((unsubscribe) => unsubscribe());
subscriptions.current = [];
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [overlay, projectName]);
// Initialize the overlay and subscribe to events
useEffect(() => {
if (!containerRef.current) return;
const options = getOverlayOptions(theme);
const newOverlay = new ChatOverlay(containerRef.current, options);
newOverlay.ready().then(() => {
setOverlay(newOverlay);
});
return () => {
subscriptions.current.forEach((unsubscribe) => unsubscribe());
subscriptions.current = [];
newOverlay.destroy();
setOverlay(null);
};
}, [containerRef, theme]);
// Set sheets state from the current project into the system prompt, so the gpt knows about sheets
useEffect(() => {
if (!overlay || !projectSheets || !sheetName || !projectName || !gridApi)
return;
const sheets: { [key: string]: string } = {};
for (const sheet of projectSheets) {
sheets[sheet.sheetName] = sheet.content;
}
const handleUpdate = () => {
const selection = gridApi.selection$.getValue();
const state = {
sheets,
inputs,
currentSheet: sheetName,
currentProjectName: projectName,
selection,
selectedTableName: selectedCell?.tableName,
};
overlay.setSystemPrompt(JSON.stringify(state));
};
handleUpdate();
const subscription = gridApi.selection$
.pipe(debounceTime(selectionUpdateDebounceTime))
.subscribe(handleUpdate);
return () => {
subscription.unsubscribe();
};
}, [
projectSheets,
overlay,
inputs,
sheetName,
projectName,
gridApi,
selectedCell,
canvasSpreadsheetMode,
]);
return { GPTSuggestions, lastStageCompleted };
}