@sybilion/uilib 1.2.19 → 1.2.21

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.
Files changed (33) hide show
  1. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +1 -1
  2. package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.js +4 -3
  3. package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.styl.js +2 -2
  4. package/dist/esm/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.js +11 -0
  5. package/dist/esm/components/ui/Chat/ChatMessage/icons/CsvIcon.js +8 -0
  6. package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +12 -18
  7. package/dist/esm/components/ui/Chat/chat-preset-utils.js +12 -3
  8. package/dist/esm/contexts/chat-context.js +69 -10
  9. package/dist/esm/index.js +1 -1
  10. package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +14 -0
  11. package/dist/esm/types/src/components/ui/Chat/ChatMessage/ChatMessage.d.ts +1 -1
  12. package/dist/esm/types/src/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.d.ts +4 -0
  13. package/dist/esm/types/src/components/ui/Chat/ChatMessage/icons/CsvIcon.d.ts +3 -0
  14. package/dist/esm/types/src/components/ui/Chat/index.d.ts +1 -1
  15. package/dist/esm/types/src/contexts/chat-context.d.ts +21 -7
  16. package/dist/esm/types/src/docs/pages/ChatUserCsvAttachmentPage.d.ts +1 -0
  17. package/dist/esm/types/src/utils/downloadTextFile.d.ts +2 -0
  18. package/dist/esm/utils/downloadTextFile.js +14 -0
  19. package/package.json +1 -1
  20. package/src/components/ui/Chat/Chat.types.ts +16 -0
  21. package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +1 -0
  22. package/src/components/ui/Chat/ChatMessage/ChatMessage.styl +67 -0
  23. package/src/components/ui/Chat/ChatMessage/ChatMessage.styl.d.ts +6 -0
  24. package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +8 -1
  25. package/src/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.tsx +36 -0
  26. package/src/components/ui/Chat/ChatMessage/icons/CsvIcon.tsx +7 -0
  27. package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +15 -15
  28. package/src/components/ui/Chat/chat-preset-utils.ts +12 -6
  29. package/src/components/ui/Chat/index.ts +3 -1
  30. package/src/contexts/chat-context.tsx +124 -13
  31. package/src/docs/pages/ChatUserCsvAttachmentPage.tsx +171 -0
  32. package/src/docs/registry.ts +6 -0
  33. package/src/utils/downloadTextFile.ts +16 -0
@@ -0,0 +1,171 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ import { Button } from '#uilib/components/ui/Button';
4
+ import {
5
+ ChatChrome,
6
+ type Message,
7
+ MessageRole,
8
+ } from '#uilib/components/ui/Chat';
9
+ import { PageContentSection } from '#uilib/components/ui/Page';
10
+ import { ScrollRef } from '@homecode/ui';
11
+
12
+ import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
13
+ import { DocsHeaderActions } from '../docsHeaderActions';
14
+
15
+ const NO_QUICK_REPLY_KEYS: ReadonlySet<string> = new Set();
16
+
17
+ const SAMPLE_CSV = `Name,Score
18
+ Alice,10
19
+ Bob,20`;
20
+
21
+ const ASSISTANT_ACK =
22
+ 'Received your message. Use the file row below the text bubble to download the CSV sample.';
23
+
24
+ const ASSISTANT_REPLY_TYPED =
25
+ 'Plain reply for typed messages. Use “Load preset-style…” for the CSV attachment demo.';
26
+
27
+ function makeMessage(role: MessageRole, text: string): Message {
28
+ return {
29
+ id: crypto.randomUUID(),
30
+ role,
31
+ text,
32
+ timestamp: Date.now(),
33
+ };
34
+ }
35
+
36
+ function makeUserMessageWithCsv(
37
+ displayText: string,
38
+ displayName: string,
39
+ ): Message {
40
+ return {
41
+ id: crypto.randomUUID(),
42
+ role: MessageRole.USER,
43
+ text: displayText,
44
+ timestamp: Date.now(),
45
+ userCsvAttachment: {
46
+ displayName,
47
+ filename: 'docs-sample.csv',
48
+ content: SAMPLE_CSV,
49
+ },
50
+ };
51
+ }
52
+
53
+ export default function ChatUserCsvAttachmentPage() {
54
+ const [messages, setMessages] = useState<Message[]>([]);
55
+ const [isLoading, setIsLoading] = useState(false);
56
+ const replyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
57
+ const scrollRef = useRef<ScrollRef>(null);
58
+
59
+ useEffect(() => {
60
+ return () => {
61
+ if (replyTimeoutRef.current != null) {
62
+ clearTimeout(replyTimeoutRef.current);
63
+ }
64
+ };
65
+ }, []);
66
+
67
+ const isEmpty = messages.length === 0 && !isLoading;
68
+ const isLastMessageFromUser =
69
+ messages.length > 0 &&
70
+ messages[messages.length - 1]?.role === MessageRole.USER;
71
+
72
+ const addCsvDemo = useCallback(() => {
73
+ if (isLoading) return;
74
+ const displayText =
75
+ 'Open procurement buys analysis "Am I overpaying on my open orders?" Open buys (sample)';
76
+
77
+ setMessages(prev => [
78
+ ...prev,
79
+ makeUserMessageWithCsv(displayText, 'Open buys (sample)'),
80
+ ]);
81
+ setIsLoading(true);
82
+
83
+ if (replyTimeoutRef.current != null) clearTimeout(replyTimeoutRef.current);
84
+
85
+ replyTimeoutRef.current = setTimeout(() => {
86
+ replyTimeoutRef.current = null;
87
+ setMessages(prev => [
88
+ ...prev,
89
+ makeMessage(MessageRole.ASSISTANT, ASSISTANT_ACK),
90
+ ]);
91
+ setIsLoading(false);
92
+ }, 500);
93
+ }, [isLoading]);
94
+
95
+ const onSubmit = useCallback(
96
+ (raw: string) => {
97
+ const text = raw.trim();
98
+ if (!text || isLoading) return;
99
+
100
+ setMessages(prev => [...prev, makeMessage(MessageRole.USER, text)]);
101
+ setIsLoading(true);
102
+
103
+ if (replyTimeoutRef.current != null) {
104
+ clearTimeout(replyTimeoutRef.current);
105
+ }
106
+ replyTimeoutRef.current = setTimeout(() => {
107
+ replyTimeoutRef.current = null;
108
+ setMessages(prev => [
109
+ ...prev,
110
+ makeMessage(MessageRole.ASSISTANT, ASSISTANT_REPLY_TYPED),
111
+ ]);
112
+ setIsLoading(false);
113
+ }, 900);
114
+ },
115
+ [isLoading],
116
+ );
117
+
118
+ return (
119
+ <>
120
+ <AppPageHeader
121
+ breadcrumbs={[{ label: 'Chat' }, { label: 'User CSV attachment' }]}
122
+ title="Chat — user CSV attachment"
123
+ subheader="USER bubble with separate downloadable CSV row (dashboard preset pattern)."
124
+ actions={<DocsHeaderActions />}
125
+ />
126
+ <PageContentSection>
127
+ <p style={{ marginBottom: 16 }}>
128
+ <Button
129
+ type="button"
130
+ variant="outline"
131
+ size="sm"
132
+ onClick={addCsvDemo}
133
+ >
134
+ Load preset-style message + CSV attachment
135
+ </Button>
136
+ </p>
137
+ <ChatChrome
138
+ showResizeHandle={false}
139
+ resizeHandle={undefined}
140
+ onClose={undefined}
141
+ isEmpty={isEmpty}
142
+ renderPresets={() => null}
143
+ messages={messages}
144
+ onQuickReply={() => {}}
145
+ suppressedQuickReplyKeys={NO_QUICK_REPLY_KEYS}
146
+ isLoading={isLoading}
147
+ scriptContinueLabel={undefined}
148
+ onScriptContinue={undefined}
149
+ showBranchActionsRow={false}
150
+ showSyntheticBranchButtons={false}
151
+ unusedBranchKeys={[]}
152
+ isScriptComplete={false}
153
+ onGenerateDashboard={undefined}
154
+ generatingDashboard={false}
155
+ onGenerateDashboardClick={() => {}}
156
+ showInlinePresets={false}
157
+ isLastMessageFromUser={isLastMessageFromUser}
158
+ scrollRef={scrollRef}
159
+ effectiveScopeId="docs-chat-user-csv"
160
+ onPromptSubmit={onSubmit}
161
+ onChatDeleted={() => {}}
162
+ emptyState={{
163
+ title: 'CSV attachment demo',
164
+ description:
165
+ 'Click the button above or send any message. The CSV row uses the same UI as dashboard presets.',
166
+ }}
167
+ />
168
+ </PageContentSection>
169
+ </>
170
+ );
171
+ }
@@ -96,6 +96,12 @@ export const DOC_REGISTRY: DocEntry[] = [
96
96
  section: 'Chat',
97
97
  load: () => import('./pages/ChatPage'),
98
98
  },
99
+ {
100
+ slug: 'chat-user-csv-attachment',
101
+ title: 'Chat user CSV attachment',
102
+ section: 'Chat',
103
+ load: () => import('./pages/ChatUserCsvAttachmentPage'),
104
+ },
99
105
  {
100
106
  slug: 'checkbox',
101
107
  title: 'Checkbox',
@@ -0,0 +1,16 @@
1
+ /** Trigger a browser download for text/binary content (no backend). */
2
+ export function downloadTextFile(
3
+ content: string,
4
+ filename: string,
5
+ mimeType: string,
6
+ ): void {
7
+ const blob = new Blob([content], { type: mimeType });
8
+ const url = URL.createObjectURL(blob);
9
+ const link = document.createElement('a');
10
+ link.href = url;
11
+ link.download = filename;
12
+ document.body.appendChild(link);
13
+ link.click();
14
+ document.body.removeChild(link);
15
+ URL.revokeObjectURL(url);
16
+ }