@sybilion/uilib 1.2.16 → 1.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/ui/Chat/ChatEmptyState/ChatEmptyState.js +2 -2
- package/dist/esm/components/ui/Chat/ChatEmptyState/ChatEmptyState.styl.js +1 -1
- package/dist/esm/components/ui/Chat/ChatSheet/ChatSheet.js +2 -1
- package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +5 -8
- package/dist/esm/components/ui/Page/PageContent/PageContent.styl.js +1 -1
- package/dist/esm/contexts/chat-context.js +11 -6
- package/dist/esm/index.js +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatEmptyState/ChatEmptyState.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatEmptyState/ChatEmptyState.types.d.ts +2 -0
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSheet.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +4 -1
- package/dist/esm/types/src/contexts/chat-context.d.ts +3 -0
- package/package.json +1 -1
- package/src/components/ui/Chat/ChatEmptyState/ChatEmptyState.styl +2 -1
- package/src/components/ui/Chat/ChatEmptyState/ChatEmptyState.tsx +2 -0
- package/src/components/ui/Chat/ChatEmptyState/ChatEmptyState.types.ts +2 -0
- package/src/components/ui/Chat/ChatSheet/ChatSheet.tsx +2 -0
- package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +13 -8
- package/src/components/ui/Page/PageContent/PageContent.styl +0 -1
- package/src/contexts/chat-context.tsx +13 -3
- package/src/docs/docsHeaderActions.tsx +8 -0
- package/src/docs/pages/CardPage.tsx +1 -1
- package/src/docs/pages/ChatPage.tsx +3 -0
|
@@ -2,8 +2,8 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import S from './ChatEmptyState.styl.js';
|
|
3
3
|
import SparklesIcon from './icons/sparkles.svg.js';
|
|
4
4
|
|
|
5
|
-
function ChatEmptyState({ icon = jsx(SparklesIcon, {}), title = 'Start a conversation', description, children, }) {
|
|
6
|
-
return (jsxs("div", { className: S.root, children: [icon && jsx("div", { className: S.icon, children: icon }), title && jsx("h2", { children: title }), description && jsx("p", { children: description }), children] }));
|
|
5
|
+
function ChatEmptyState({ icon = jsx(SparklesIcon, {}), title = 'Start a conversation', description, additionalContent, children, }) {
|
|
6
|
+
return (jsxs("div", { className: S.root, children: [icon && jsx("div", { className: S.icon, children: icon }), title && jsx("h2", { children: title }), description && jsx("p", { children: description }), additionalContent, children] }));
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export { ChatEmptyState };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = ".ChatEmptyState_root__j1n-C{align-items:center;display:flex;flex:1;flex-direction:column;gap:var(--p-10);justify-content:center;padding:var(--p-6);text-align:center;text-
|
|
3
|
+
var css_248z = ".ChatEmptyState_root__j1n-C{align-items:center;color:var(--text-secondary);display:flex;flex:1;flex-direction:column;font-size:var(--text-sm);gap:var(--p-10);justify-content:center;padding:var(--p-6);text-align:center;text-wrap:balance}.ChatEmptyState_icon__YSDgv,.ChatEmptyState_icon__YSDgv>svg{height:32px;width:32px}";
|
|
4
4
|
var S = {"root":"ChatEmptyState_root__j1n-C","icon":"ChatEmptyState_icon__YSDgv"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
@@ -4,7 +4,7 @@ import { Button } from '../../Button/Button.js';
|
|
|
4
4
|
import { ChatChrome } from '../ChatChrome/ChatChrome.js';
|
|
5
5
|
import { useChatPanelChromeModel } from './useChatPanelChromeModel.js';
|
|
6
6
|
|
|
7
|
-
function ChatSheet({ triggerLabel = 'Open Chat', triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, inline = false, }) {
|
|
7
|
+
function ChatSheet({ triggerLabel = 'Open Chat', triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, emptyState, inline = false, }) {
|
|
8
8
|
const model = useChatPanelChromeModel({
|
|
9
9
|
embedAsPage: inline,
|
|
10
10
|
presets,
|
|
@@ -13,6 +13,7 @@ function ChatSheet({ triggerLabel = 'Open Chat', triggerAriaLabel, actionsRef, r
|
|
|
13
13
|
onScriptComplete,
|
|
14
14
|
onGenerateDashboard,
|
|
15
15
|
renderMessageChart,
|
|
16
|
+
emptyState,
|
|
16
17
|
});
|
|
17
18
|
if (actionsRef) {
|
|
18
19
|
actionsRef.current = {
|
|
@@ -3,7 +3,7 @@ import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
|
3
3
|
import { MessageRole, GENERATING_DASHBOARD_SYSTEM_TEXT } from '../Chat.types.js';
|
|
4
4
|
import { isGraphIntakeAssistantStepComplete, matchUserTextToQuickReply, parseScriptLine, textHasQuickReplyMarkers, branchKeysUsedFromChatHistory, branchKeysUsedByUserMessages, extractQuickReplyLabelKeyPairsFromText, entryBranchKeyBeforeLastAssistant, isPresetScriptGraph, branchesFromPresetScriptGraph } from '../ChatMessage/presetScript.js';
|
|
5
5
|
import { usedPresetIdsFromMessages, formatChatTranscript } from '../chat-preset-utils.js';
|
|
6
|
-
import { useChatsForScopeId, useChat } from '../../../../contexts/chat-context.js';
|
|
6
|
+
import { useChatsForScopeId, useChat, isChatEmpty } from '../../../../contexts/chat-context.js';
|
|
7
7
|
import useEvent from '../../../../hooks/useEvent.js';
|
|
8
8
|
import { useIsMobile } from '../../../../hooks/useIsMobile.js';
|
|
9
9
|
import { useQueryParams } from '../../../../hooks/useQueryParams.js';
|
|
@@ -21,7 +21,7 @@ const CHAT_NAV_COLLAPSE_BREAKPOINT_PX = 1400;
|
|
|
21
21
|
const CHAT_QUERY_PARAM = 'chat';
|
|
22
22
|
const CHAT_OPEN_VALUE = 'open';
|
|
23
23
|
const PROMPT_QUERY_PARAM = 'prompt';
|
|
24
|
-
function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, }) {
|
|
24
|
+
function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, emptyState, }) {
|
|
25
25
|
const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
|
|
26
26
|
const isMobile = useIsMobile();
|
|
27
27
|
const { chatPanelContainer, isOpen: sidebarNavOpen, setOpen: setSidebarNavOpen, chatWidthPx, setChatWidthPx, getShellWidth, setChatPanelOpen, } = useSidebar();
|
|
@@ -145,6 +145,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
145
145
|
removeSearchParams(CHAT_QUERY_PARAM);
|
|
146
146
|
}
|
|
147
147
|
};
|
|
148
|
+
const isEmpty = isChatEmpty(chat) && !isLoading;
|
|
148
149
|
/**
|
|
149
150
|
* App link: `?prompt=…` — open panel, pre-fill composer, strip param (read once on mount
|
|
150
151
|
* from `location.search`). If the selected session already has messages, `newChat()` first.
|
|
@@ -168,12 +169,8 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
promptParamHandledInEffectRef.current = true;
|
|
171
|
-
const selected = currentChatId
|
|
172
|
-
? chats.find(c => c.session_id === currentChatId)
|
|
173
|
-
: undefined;
|
|
174
|
-
const selectedHasMessages = (selected?.messages?.length ?? 0) > 0;
|
|
175
172
|
const needsFirstSession = chats.length === 0;
|
|
176
|
-
if (
|
|
173
|
+
if (!isEmpty || needsFirstSession) {
|
|
177
174
|
const sessionId = newChat();
|
|
178
175
|
if (sessionId == null) {
|
|
179
176
|
logger.warn('Chat prompt link: sign in to use the assistant.');
|
|
@@ -641,7 +638,6 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
641
638
|
};
|
|
642
639
|
const isLastMessageFromUser = chat?.messages.length > 0 &&
|
|
643
640
|
chat.messages[chat.messages.length - 1]?.role === MessageRole.USER;
|
|
644
|
-
const isEmpty = !chat?.messages?.length && !isLoading;
|
|
645
641
|
const linearScriptActive = Boolean(currentChatId && scriptByChatId[currentChatId]);
|
|
646
642
|
const quickBranches = currentChatId
|
|
647
643
|
? quickReplyBranchesByChat[currentChatId]
|
|
@@ -771,6 +767,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
771
767
|
onPromptSubmit: handlePromptSubmit,
|
|
772
768
|
onChatDeleted: endLocalDemoFlow,
|
|
773
769
|
promptPrefill: promptLinkPrefill,
|
|
770
|
+
emptyState,
|
|
774
771
|
};
|
|
775
772
|
const toggleOpen = () => onOpenChange(!isOpen);
|
|
776
773
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.PageContent_root__caExB{display:flex;flex:1;flex-direction:column;max-width:var(--page-width);min-height:0;
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.PageContent_root__caExB{display:flex;flex:1;flex-direction:column;max-width:var(--page-width);min-height:0;width:100%}.PageContent_section__Wve-w{display:flex;flex-direction:column;min-height:0;padding-left:var(--page-x-padding);padding-right:var(--page-x-padding)}.PageContent_grow__Nkfqk{flex-grow:1}";
|
|
4
4
|
var S = {"root":"PageContent_root__caExB","section":"PageContent_section__Wve-w","grow":"PageContent_grow__Nkfqk"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { createContext, useState, useCallback, useEffect, useContext } from 'react';
|
|
2
|
+
import { createContext, useState, useCallback, useEffect, useContext, useMemo } from 'react';
|
|
3
3
|
import { MessageRole } from '../components/ui/Chat/Chat.types.js';
|
|
4
4
|
import { stripJsonDashboardFences } from '../lib/dashboard-spec/stripJsonDashboardFences.js';
|
|
5
5
|
import { LS } from '@homecode/ui';
|
|
@@ -255,6 +255,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
|
|
|
255
255
|
deleteChat,
|
|
256
256
|
}, children: children }));
|
|
257
257
|
}
|
|
258
|
+
const isChatEmpty = (chat) => chat?.messages.length === 0;
|
|
258
259
|
function useChats() {
|
|
259
260
|
const context = useContext(ChatContext);
|
|
260
261
|
if (context === undefined) {
|
|
@@ -264,17 +265,21 @@ function useChats() {
|
|
|
264
265
|
}
|
|
265
266
|
function useChat(scopeId, chatId) {
|
|
266
267
|
const { getChatsForScopeId } = useChats();
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
268
|
+
return useMemo(() => {
|
|
269
|
+
if (!scopeId || !chatId)
|
|
270
|
+
return null;
|
|
271
|
+
return (getChatsForScopeId(scopeId)?.find(chat => chat.session_id === chatId) ??
|
|
272
|
+
null);
|
|
273
|
+
}, [scopeId, chatId, getChatsForScopeId]);
|
|
271
274
|
}
|
|
272
275
|
function useChatsForScopeId(scopeId) {
|
|
273
276
|
const { getChatsForScopeId, getCurrentChatId, setCurrentChatId, newChat, addMessage, removeMessageById, sendMessage, deleteChat, } = useChats();
|
|
274
277
|
const chats = getChatsForScopeId(scopeId);
|
|
275
278
|
const currentChatId = getCurrentChatId(scopeId);
|
|
279
|
+
const currentChat = useChat(scopeId, currentChatId ?? undefined);
|
|
276
280
|
return {
|
|
277
281
|
chats,
|
|
282
|
+
currentChat,
|
|
278
283
|
currentChatId,
|
|
279
284
|
setCurrentChatId: (targetId) => setCurrentChatId(scopeId, targetId),
|
|
280
285
|
newChat: () => newChat(scopeId),
|
|
@@ -294,4 +299,4 @@ function useCurrentChat(scopeId) {
|
|
|
294
299
|
return useChat(scopeId, chatId ?? undefined);
|
|
295
300
|
}
|
|
296
301
|
|
|
297
|
-
export { ChatContext, ChatProvider, useChat, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat };
|
|
302
|
+
export { ChatContext, ChatProvider, isChatEmpty, useChat, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat };
|
package/dist/esm/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme.js';
|
|
|
4
4
|
export { SybilionAuthProvider, createSybilionApiFetch, getSybilionApiOriginFromSdk, sybilionApiFetch, useSybilionApiFetch, useSybilionAuth } from './sybilion-auth/SybilionAuthProvider.js';
|
|
5
5
|
export { SYBILION_AUTH_LOGIN_PATH, normalizeApiBaseUrl } from './sybilion-auth/authPaths.js';
|
|
6
6
|
export { exchangeAuth0AccessTokenForSybilionJwt } from './sybilion-auth/exchangeSybilionToken.js';
|
|
7
|
-
export { ChatContext, ChatProvider, useChat, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat } from './contexts/chat-context.js';
|
|
7
|
+
export { ChatContext, ChatProvider, isChatEmpty, useChat, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat } from './contexts/chat-context.js';
|
|
8
8
|
export { AnalysesSelector } from './components/ui/AnalysesSelector/AnalysesSelector.js';
|
|
9
9
|
export { AnalysisLineIcon } from './components/ui/AnalysisLineIcon/AnalysisLineIcon.js';
|
|
10
10
|
export { AppHeaderHost, AppHeaderPortal } from './components/ui/AppHeader/AppHeader.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { PropsWithChildren } from 'react';
|
|
2
2
|
import type { ChatEmptyStateProps } from './ChatEmptyState.types';
|
|
3
|
-
export declare function ChatEmptyState({ icon, title, description, children, }: PropsWithChildren<ChatEmptyStateProps>): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export declare function ChatEmptyState({ icon, title, description, additionalContent, children, }: PropsWithChildren<ChatEmptyStateProps>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -19,4 +19,4 @@ export interface ChatSheetProps extends Omit<UseChatPanelChromeModelInput, 'embe
|
|
|
19
19
|
*/
|
|
20
20
|
inline?: boolean;
|
|
21
21
|
}
|
|
22
|
-
export declare function ChatSheet({ triggerLabel, triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, inline, }: ChatSheetProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function ChatSheet({ triggerLabel, triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, emptyState, inline, }: ChatSheetProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ChatPreset, type ScriptCompletePayload } from '#uilib/components/ui/Chat/Chat.types';
|
|
2
2
|
import type { ChatChromeProps } from '../ChatChrome';
|
|
3
|
+
import type { ChatEmptyStateProps } from '../ChatEmptyState/ChatEmptyState.types';
|
|
3
4
|
export type UseChatPanelChromeModelInput = {
|
|
4
5
|
/** When true, skip sidebar chat slot, URL `?chat=`, and portal behavior (e.g. page main content). */
|
|
5
6
|
embedAsPage: boolean;
|
|
@@ -13,6 +14,8 @@ export type UseChatPanelChromeModelInput = {
|
|
|
13
14
|
onGenerateDashboard?: (transcript: string) => void | Promise<void>;
|
|
14
15
|
/** Renders `[CHART]` tokens in assistant messages. */
|
|
15
16
|
renderMessageChart?: () => React.ReactNode;
|
|
17
|
+
/** Forwarded to `ChatChrome` when the thread is empty. */
|
|
18
|
+
emptyState?: ChatEmptyStateProps;
|
|
16
19
|
};
|
|
17
20
|
export type UseChatPanelChromeModelResult = {
|
|
18
21
|
chromeProps: ChatChromeProps;
|
|
@@ -22,4 +25,4 @@ export type UseChatPanelChromeModelResult = {
|
|
|
22
25
|
newChat: () => void;
|
|
23
26
|
chatPanelContainer: HTMLElement | null;
|
|
24
27
|
};
|
|
25
|
-
export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
|
|
28
|
+
export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, emptyState, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
|
|
@@ -21,10 +21,12 @@ export interface ChatProviderProps {
|
|
|
21
21
|
sendChatMessage: SendChatMessageFn;
|
|
22
22
|
}
|
|
23
23
|
export declare function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessageFn, }: ChatProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare const isChatEmpty: (chat: Chat | null) => boolean;
|
|
24
25
|
export declare function useChats(): ChatContextType;
|
|
25
26
|
export declare function useChat(scopeId: string | undefined, chatId: string | undefined): Chat | null;
|
|
26
27
|
export declare function useChatsForScopeId(scopeId: string): {
|
|
27
28
|
chats: Chat[];
|
|
29
|
+
currentChat: Chat;
|
|
28
30
|
currentChatId: string;
|
|
29
31
|
setCurrentChatId: (targetId: string) => void;
|
|
30
32
|
newChat: () => string;
|
|
@@ -36,6 +38,7 @@ export declare function useChatsForScopeId(scopeId: string): {
|
|
|
36
38
|
/** @deprecated Use useChatsForScopeId */
|
|
37
39
|
export declare function useChatsForDataset(scopeId: string): {
|
|
38
40
|
chats: Chat[];
|
|
41
|
+
currentChat: Chat;
|
|
39
42
|
currentChatId: string;
|
|
40
43
|
setCurrentChatId: (targetId: string) => void;
|
|
41
44
|
newChat: () => string;
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ export function ChatEmptyState({
|
|
|
8
8
|
icon = <SparklesIcon />,
|
|
9
9
|
title = 'Start a conversation',
|
|
10
10
|
description,
|
|
11
|
+
additionalContent,
|
|
11
12
|
children,
|
|
12
13
|
}: PropsWithChildren<ChatEmptyStateProps>) {
|
|
13
14
|
return (
|
|
@@ -15,6 +16,7 @@ export function ChatEmptyState({
|
|
|
15
16
|
{icon && <div className={S.icon}>{icon}</div>}
|
|
16
17
|
{title && <h2>{title}</h2>}
|
|
17
18
|
{description && <p>{description}</p>}
|
|
19
|
+
{additionalContent}
|
|
18
20
|
{children}
|
|
19
21
|
</div>
|
|
20
22
|
);
|
|
@@ -43,6 +43,7 @@ export function ChatSheet({
|
|
|
43
43
|
onScriptComplete,
|
|
44
44
|
onGenerateDashboard,
|
|
45
45
|
renderMessageChart,
|
|
46
|
+
emptyState,
|
|
46
47
|
inline = false,
|
|
47
48
|
}: ChatSheetProps) {
|
|
48
49
|
const model = useChatPanelChromeModel({
|
|
@@ -53,6 +54,7 @@ export function ChatSheet({
|
|
|
53
54
|
onScriptComplete,
|
|
54
55
|
onGenerateDashboard,
|
|
55
56
|
renderMessageChart,
|
|
57
|
+
emptyState,
|
|
56
58
|
});
|
|
57
59
|
|
|
58
60
|
if (actionsRef) {
|
|
@@ -23,7 +23,11 @@ import {
|
|
|
23
23
|
formatChatTranscript,
|
|
24
24
|
usedPresetIdsFromMessages,
|
|
25
25
|
} from '#uilib/components/ui/Chat/chat-preset-utils';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
isChatEmpty,
|
|
28
|
+
useChat,
|
|
29
|
+
useChatsForScopeId,
|
|
30
|
+
} from '#uilib/contexts/chat-context';
|
|
27
31
|
import useEvent from '#uilib/hooks/useEvent';
|
|
28
32
|
import { useIsMobile } from '#uilib/hooks/useIsMobile';
|
|
29
33
|
import { useQueryParams } from '#uilib/hooks/useQueryParams';
|
|
@@ -34,6 +38,7 @@ import { ScrollRef } from '@homecode/ui';
|
|
|
34
38
|
import { useSidebar } from '../../Sidebar/Sidebar';
|
|
35
39
|
import { Chat } from '../Chat';
|
|
36
40
|
import type { ChatChromeProps } from '../ChatChrome';
|
|
41
|
+
import type { ChatEmptyStateProps } from '../ChatEmptyState/ChatEmptyState.types';
|
|
37
42
|
|
|
38
43
|
export type UseChatPanelChromeModelInput = {
|
|
39
44
|
/** When true, skip sidebar chat slot, URL `?chat=`, and portal behavior (e.g. page main content). */
|
|
@@ -48,6 +53,8 @@ export type UseChatPanelChromeModelInput = {
|
|
|
48
53
|
onGenerateDashboard?: (transcript: string) => void | Promise<void>;
|
|
49
54
|
/** Renders `[CHART]` tokens in assistant messages. */
|
|
50
55
|
renderMessageChart?: () => React.ReactNode;
|
|
56
|
+
/** Forwarded to `ChatChrome` when the thread is empty. */
|
|
57
|
+
emptyState?: ChatEmptyStateProps;
|
|
51
58
|
};
|
|
52
59
|
|
|
53
60
|
export type UseChatPanelChromeModelResult = {
|
|
@@ -92,6 +99,7 @@ export function useChatPanelChromeModel({
|
|
|
92
99
|
onScriptComplete,
|
|
93
100
|
onGenerateDashboard,
|
|
94
101
|
renderMessageChart,
|
|
102
|
+
emptyState,
|
|
95
103
|
}: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult {
|
|
96
104
|
const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
|
|
97
105
|
const isMobile = useIsMobile();
|
|
@@ -264,6 +272,8 @@ export function useChatPanelChromeModel({
|
|
|
264
272
|
}
|
|
265
273
|
};
|
|
266
274
|
|
|
275
|
+
const isEmpty = isChatEmpty(chat) && !isLoading;
|
|
276
|
+
|
|
267
277
|
/**
|
|
268
278
|
* App link: `?prompt=…` — open panel, pre-fill composer, strip param (read once on mount
|
|
269
279
|
* from `location.search`). If the selected session already has messages, `newChat()` first.
|
|
@@ -290,13 +300,9 @@ export function useChatPanelChromeModel({
|
|
|
290
300
|
}
|
|
291
301
|
promptParamHandledInEffectRef.current = true;
|
|
292
302
|
|
|
293
|
-
const selected = currentChatId
|
|
294
|
-
? chats.find(c => c.session_id === currentChatId)
|
|
295
|
-
: undefined;
|
|
296
|
-
const selectedHasMessages = (selected?.messages?.length ?? 0) > 0;
|
|
297
303
|
const needsFirstSession = chats.length === 0;
|
|
298
304
|
|
|
299
|
-
if (
|
|
305
|
+
if (!isEmpty || needsFirstSession) {
|
|
300
306
|
const sessionId = newChat();
|
|
301
307
|
if (sessionId == null) {
|
|
302
308
|
logger.warn('Chat prompt link: sign in to use the assistant.');
|
|
@@ -816,8 +822,6 @@ export function useChatPanelChromeModel({
|
|
|
816
822
|
chat?.messages.length > 0 &&
|
|
817
823
|
chat.messages[chat.messages.length - 1]?.role === MessageRole.USER;
|
|
818
824
|
|
|
819
|
-
const isEmpty = !chat?.messages?.length && !isLoading;
|
|
820
|
-
|
|
821
825
|
const linearScriptActive = Boolean(
|
|
822
826
|
currentChatId && scriptByChatId[currentChatId],
|
|
823
827
|
);
|
|
@@ -993,6 +997,7 @@ export function useChatPanelChromeModel({
|
|
|
993
997
|
onPromptSubmit: handlePromptSubmit,
|
|
994
998
|
onChatDeleted: endLocalDemoFlow,
|
|
995
999
|
promptPrefill: promptLinkPrefill,
|
|
1000
|
+
emptyState,
|
|
996
1001
|
};
|
|
997
1002
|
|
|
998
1003
|
const toggleOpen = () => onOpenChange(!isOpen);
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
useCallback,
|
|
5
5
|
useContext,
|
|
6
6
|
useEffect,
|
|
7
|
+
useMemo,
|
|
7
8
|
useState,
|
|
8
9
|
} from 'react';
|
|
9
10
|
|
|
@@ -383,6 +384,9 @@ export function ChatProvider({
|
|
|
383
384
|
);
|
|
384
385
|
}
|
|
385
386
|
|
|
387
|
+
export const isChatEmpty = (chat: Chat | null): boolean =>
|
|
388
|
+
chat?.messages.length === 0;
|
|
389
|
+
|
|
386
390
|
export function useChats() {
|
|
387
391
|
const context = useContext(ChatContext);
|
|
388
392
|
if (context === undefined) {
|
|
@@ -396,9 +400,13 @@ export function useChat(
|
|
|
396
400
|
chatId: string | undefined,
|
|
397
401
|
): Chat | null {
|
|
398
402
|
const { getChatsForScopeId } = useChats();
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
403
|
+
return useMemo(() => {
|
|
404
|
+
if (!scopeId || !chatId) return null;
|
|
405
|
+
return (
|
|
406
|
+
getChatsForScopeId(scopeId)?.find(chat => chat.session_id === chatId) ??
|
|
407
|
+
null
|
|
408
|
+
);
|
|
409
|
+
}, [scopeId, chatId, getChatsForScopeId]);
|
|
402
410
|
}
|
|
403
411
|
|
|
404
412
|
export function useChatsForScopeId(scopeId: string) {
|
|
@@ -414,9 +422,11 @@ export function useChatsForScopeId(scopeId: string) {
|
|
|
414
422
|
} = useChats();
|
|
415
423
|
const chats = getChatsForScopeId(scopeId);
|
|
416
424
|
const currentChatId = getCurrentChatId(scopeId);
|
|
425
|
+
const currentChat = useChat(scopeId, currentChatId ?? undefined);
|
|
417
426
|
|
|
418
427
|
return {
|
|
419
428
|
chats,
|
|
429
|
+
currentChat,
|
|
420
430
|
currentChatId,
|
|
421
431
|
setCurrentChatId: (targetId: string) => setCurrentChatId(scopeId, targetId),
|
|
422
432
|
newChat: () => newChat(scopeId),
|
|
@@ -13,6 +13,14 @@ export function DocsHeaderActions() {
|
|
|
13
13
|
</>
|
|
14
14
|
}
|
|
15
15
|
scopeId={DOCS_CHAT_SCOPE_ID}
|
|
16
|
+
emptyState={{
|
|
17
|
+
title: 'Start a conversation',
|
|
18
|
+
description:
|
|
19
|
+
'Send a message below. This demo appends a canned reply after a short delay.',
|
|
20
|
+
additionalContent: (
|
|
21
|
+
<p>Optional empty-state slot via additionalContent.</p>
|
|
22
|
+
),
|
|
23
|
+
}}
|
|
16
24
|
/>
|
|
17
25
|
);
|
|
18
26
|
}
|
|
@@ -22,7 +22,7 @@ export default function CardPage() {
|
|
|
22
22
|
actions={<DocsHeaderActions />}
|
|
23
23
|
/>
|
|
24
24
|
<PageContentSection>
|
|
25
|
-
<Card style={{ maxWidth: 360 }}>
|
|
25
|
+
<Card paddingSize="s" style={{ maxWidth: 360 }}>
|
|
26
26
|
<CardHeader>
|
|
27
27
|
<CardTitle>Card title</CardTitle>
|
|
28
28
|
<CardDescription>Supporting description text.</CardDescription>
|
|
@@ -101,6 +101,9 @@ export default function ChatPage() {
|
|
|
101
101
|
title: 'Start a conversation',
|
|
102
102
|
description:
|
|
103
103
|
'Send a message below. This demo appends a canned reply after a short delay.',
|
|
104
|
+
additionalContent: (
|
|
105
|
+
<p>Optional empty-state slot via additionalContent.</p>
|
|
106
|
+
),
|
|
104
107
|
}}
|
|
105
108
|
/>
|
|
106
109
|
</PageContentSection>
|