@chaaskit/client 0.1.0

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 (135) hide show
  1. package/dist/favicon.svg +11 -0
  2. package/dist/index.html +17 -0
  3. package/dist/lib/LoadingSkeletons-IcIC2JPq.js +132 -0
  4. package/dist/lib/LoadingSkeletons-IcIC2JPq.js.map +1 -0
  5. package/dist/lib/ServerThemeProvider-DNF0LAyk.js +42 -0
  6. package/dist/lib/ServerThemeProvider-DNF0LAyk.js.map +1 -0
  7. package/dist/lib/extensions.js +10 -0
  8. package/dist/lib/extensions.js.map +1 -0
  9. package/dist/lib/favicon.svg +11 -0
  10. package/dist/lib/index.js +74126 -0
  11. package/dist/lib/index.js.map +1 -0
  12. package/dist/lib/logo.svg +12 -0
  13. package/dist/lib/routes/AcceptInviteRoute.js +19 -0
  14. package/dist/lib/routes/AcceptInviteRoute.js.map +1 -0
  15. package/dist/lib/routes/AdminDashboardRoute.js +19 -0
  16. package/dist/lib/routes/AdminDashboardRoute.js.map +1 -0
  17. package/dist/lib/routes/AdminTeamRoute.js +19 -0
  18. package/dist/lib/routes/AdminTeamRoute.js.map +1 -0
  19. package/dist/lib/routes/AdminTeamsRoute.js +19 -0
  20. package/dist/lib/routes/AdminTeamsRoute.js.map +1 -0
  21. package/dist/lib/routes/AdminUsersRoute.js +19 -0
  22. package/dist/lib/routes/AdminUsersRoute.js.map +1 -0
  23. package/dist/lib/routes/ApiKeysRoute.js +19 -0
  24. package/dist/lib/routes/ApiKeysRoute.js.map +1 -0
  25. package/dist/lib/routes/AutomationsRoute.js +19 -0
  26. package/dist/lib/routes/AutomationsRoute.js.map +1 -0
  27. package/dist/lib/routes/ChatRoute.js +19 -0
  28. package/dist/lib/routes/ChatRoute.js.map +1 -0
  29. package/dist/lib/routes/DocumentsRoute.js +19 -0
  30. package/dist/lib/routes/DocumentsRoute.js.map +1 -0
  31. package/dist/lib/routes/OAuthConsentRoute.js +19 -0
  32. package/dist/lib/routes/OAuthConsentRoute.js.map +1 -0
  33. package/dist/lib/routes/PricingRoute.js +19 -0
  34. package/dist/lib/routes/PricingRoute.js.map +1 -0
  35. package/dist/lib/routes/PrivacyRoute.js +19 -0
  36. package/dist/lib/routes/PrivacyRoute.js.map +1 -0
  37. package/dist/lib/routes/TeamSettingsRoute.js +19 -0
  38. package/dist/lib/routes/TeamSettingsRoute.js.map +1 -0
  39. package/dist/lib/routes/TermsRoute.js +19 -0
  40. package/dist/lib/routes/TermsRoute.js.map +1 -0
  41. package/dist/lib/routes/VerifyEmailRoute.js +19 -0
  42. package/dist/lib/routes/VerifyEmailRoute.js.map +1 -0
  43. package/dist/lib/routes.js +79 -0
  44. package/dist/lib/routes.js.map +1 -0
  45. package/dist/lib/ssr-utils.js +29 -0
  46. package/dist/lib/ssr-utils.js.map +1 -0
  47. package/dist/lib/ssr.js +60 -0
  48. package/dist/lib/ssr.js.map +1 -0
  49. package/dist/lib/styles.css +2410 -0
  50. package/dist/lib/useExtensions-B5nX_8XD.js +155 -0
  51. package/dist/lib/useExtensions-B5nX_8XD.js.map +1 -0
  52. package/dist/logo.svg +12 -0
  53. package/package.json +84 -0
  54. package/src/components/AgentSelector.tsx +90 -0
  55. package/src/components/BranchModal.tsx +129 -0
  56. package/src/components/ClientOnly.tsx +27 -0
  57. package/src/components/ExportMenu.tsx +122 -0
  58. package/src/components/LoadingSkeletons.tsx +110 -0
  59. package/src/components/MCPCredentialsSection.tsx +309 -0
  60. package/src/components/MentionChip.tsx +149 -0
  61. package/src/components/MentionDropdown.tsx +175 -0
  62. package/src/components/MentionInput.tsx +293 -0
  63. package/src/components/MessageItem.tsx +300 -0
  64. package/src/components/MessageList.tsx +159 -0
  65. package/src/components/OAuthAppsSection.tsx +124 -0
  66. package/src/components/ProjectFolder.tsx +141 -0
  67. package/src/components/ProjectModal.tsx +296 -0
  68. package/src/components/SSRMessageList.tsx +153 -0
  69. package/src/components/SearchModal.tsx +173 -0
  70. package/src/components/SettingsModal.tsx +412 -0
  71. package/src/components/ShareModal.tsx +280 -0
  72. package/src/components/Sidebar.tsx +491 -0
  73. package/src/components/TeamSwitcher.tsx +273 -0
  74. package/src/components/ToolCallDisplay.tsx +473 -0
  75. package/src/components/ToolConfirmationModal.tsx +130 -0
  76. package/src/components/UsageChart.tsx +177 -0
  77. package/src/components/content/CodeBlock.tsx +69 -0
  78. package/src/components/content/MarkdownRenderer.tsx +64 -0
  79. package/src/components/content/SSRMarkdownRenderer.tsx +158 -0
  80. package/src/contexts/AuthContext.tsx +119 -0
  81. package/src/contexts/ConfigContext.tsx +214 -0
  82. package/src/contexts/ProjectContext.tsx +167 -0
  83. package/src/contexts/ServerConfigProvider.tsx +41 -0
  84. package/src/contexts/ServerThemeProvider.tsx +47 -0
  85. package/src/contexts/TeamContext.tsx +255 -0
  86. package/src/contexts/ThemeContext.tsx +113 -0
  87. package/src/extensions/index.ts +15 -0
  88. package/src/extensions/registry.ts +187 -0
  89. package/src/extensions/useExtensions.ts +52 -0
  90. package/src/hooks/useAppPath.ts +34 -0
  91. package/src/hooks/useBasePath.ts +13 -0
  92. package/src/hooks/useKeyboardShortcuts.ts +50 -0
  93. package/src/hooks/useMentionSearch.ts +106 -0
  94. package/src/index.tsx +116 -0
  95. package/src/layouts/MainLayout.tsx +98 -0
  96. package/src/pages/AcceptInvitePage.tsx +175 -0
  97. package/src/pages/AdminDashboardPage.tsx +362 -0
  98. package/src/pages/AdminTeamPage.tsx +304 -0
  99. package/src/pages/AdminTeamsPage.tsx +242 -0
  100. package/src/pages/AdminUsersPage.tsx +385 -0
  101. package/src/pages/ApiKeysPage.tsx +449 -0
  102. package/src/pages/ChatPage.tsx +310 -0
  103. package/src/pages/DocumentsPage.tsx +577 -0
  104. package/src/pages/LoginPage.tsx +232 -0
  105. package/src/pages/OAuthConsentPage.tsx +234 -0
  106. package/src/pages/PricingPage.tsx +314 -0
  107. package/src/pages/PrivacyPage.tsx +65 -0
  108. package/src/pages/RegisterPage.tsx +153 -0
  109. package/src/pages/ScheduledPromptsPage.tsx +702 -0
  110. package/src/pages/SharedThreadPage.tsx +116 -0
  111. package/src/pages/TeamSettingsPage.tsx +1085 -0
  112. package/src/pages/TermsPage.tsx +82 -0
  113. package/src/pages/VerifyEmailPage.tsx +202 -0
  114. package/src/routes/AcceptInviteRoute.tsx +24 -0
  115. package/src/routes/AdminDashboardRoute.tsx +24 -0
  116. package/src/routes/AdminTeamRoute.tsx +24 -0
  117. package/src/routes/AdminTeamsRoute.tsx +24 -0
  118. package/src/routes/AdminUsersRoute.tsx +24 -0
  119. package/src/routes/ApiKeysRoute.tsx +24 -0
  120. package/src/routes/AutomationsRoute.tsx +24 -0
  121. package/src/routes/ChatRoute.tsx +28 -0
  122. package/src/routes/DocumentsRoute.tsx +24 -0
  123. package/src/routes/OAuthConsentRoute.tsx +24 -0
  124. package/src/routes/PricingRoute.tsx +24 -0
  125. package/src/routes/PrivacyRoute.tsx +24 -0
  126. package/src/routes/TeamSettingsRoute.tsx +24 -0
  127. package/src/routes/TermsRoute.tsx +24 -0
  128. package/src/routes/VerifyEmailRoute.tsx +24 -0
  129. package/src/routes/index.ts +57 -0
  130. package/src/ssr-utils.tsx +84 -0
  131. package/src/ssr.ts +123 -0
  132. package/src/stores/chatStore.ts +670 -0
  133. package/src/styles/index.css +254 -0
  134. package/src/utils/api.ts +78 -0
  135. package/src/vite-env.d.ts +13 -0
@@ -0,0 +1,175 @@
1
+ import { useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
2
+ import { FileText, Users, Folder, Loader2 } from 'lucide-react';
3
+ import type { MentionableDocument, DocumentScope } from '@chaaskit/shared';
4
+
5
+ interface MentionDropdownProps {
6
+ documents: MentionableDocument[];
7
+ grouped?: {
8
+ my: MentionableDocument[];
9
+ team: MentionableDocument[];
10
+ project: MentionableDocument[];
11
+ };
12
+ isLoading?: boolean;
13
+ selectedIndex: number;
14
+ onSelect: (doc: MentionableDocument) => void;
15
+ position: { top: number; left: number };
16
+ }
17
+
18
+ export interface MentionDropdownHandle {
19
+ scrollToSelected: () => void;
20
+ }
21
+
22
+ function getScopeIcon(scope: DocumentScope) {
23
+ switch (scope) {
24
+ case 'my':
25
+ return <FileText size={14} className="text-text-muted" />;
26
+ case 'team':
27
+ return <Users size={14} className="text-text-muted" />;
28
+ case 'project':
29
+ return <Folder size={14} className="text-text-muted" />;
30
+ }
31
+ }
32
+
33
+ function getScopeLabel(scope: DocumentScope) {
34
+ switch (scope) {
35
+ case 'my':
36
+ return 'My Documents';
37
+ case 'team':
38
+ return 'Team';
39
+ case 'project':
40
+ return 'Project';
41
+ }
42
+ }
43
+
44
+ function formatCharCount(count: number): string {
45
+ if (count < 1000) return `${count} chars`;
46
+ return `${(count / 1000).toFixed(1)}k chars`;
47
+ }
48
+
49
+ const MentionDropdown = forwardRef<MentionDropdownHandle, MentionDropdownProps>(
50
+ function MentionDropdown(
51
+ { documents, grouped, isLoading, selectedIndex, onSelect, position },
52
+ ref
53
+ ) {
54
+ const containerRef = useRef<HTMLDivElement>(null);
55
+ const itemRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
56
+
57
+ useImperativeHandle(ref, () => ({
58
+ scrollToSelected: () => {
59
+ const selectedItem = itemRefs.current.get(selectedIndex);
60
+ if (selectedItem) {
61
+ selectedItem.scrollIntoView({ block: 'nearest' });
62
+ }
63
+ },
64
+ }));
65
+
66
+ useEffect(() => {
67
+ const selectedItem = itemRefs.current.get(selectedIndex);
68
+ if (selectedItem) {
69
+ selectedItem.scrollIntoView({ block: 'nearest' });
70
+ }
71
+ }, [selectedIndex]);
72
+
73
+ // Render grouped or flat list
74
+ const renderDocuments = () => {
75
+ if (grouped && (grouped.my.length > 0 || grouped.team.length > 0 || grouped.project.length > 0)) {
76
+ let flatIndex = 0;
77
+ const sections: JSX.Element[] = [];
78
+
79
+ const renderSection = (scope: DocumentScope, docs: MentionableDocument[]) => {
80
+ if (docs.length === 0) return null;
81
+
82
+ const sectionItems = docs.map((doc) => {
83
+ const index = flatIndex++;
84
+ return (
85
+ <button
86
+ key={doc.id}
87
+ ref={(el) => {
88
+ if (el) itemRefs.current.set(index, el);
89
+ else itemRefs.current.delete(index);
90
+ }}
91
+ onClick={() => onSelect(doc)}
92
+ className={`flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-background-secondary ${
93
+ selectedIndex === index ? 'bg-background-secondary' : ''
94
+ }`}
95
+ >
96
+ {getScopeIcon(doc.scope)}
97
+ <span className="flex-1 truncate text-text-primary">{doc.name}</span>
98
+ <span className="text-xs text-text-muted">{formatCharCount(doc.charCount)}</span>
99
+ </button>
100
+ );
101
+ });
102
+
103
+ return (
104
+ <div key={scope}>
105
+ <div className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-text-muted">
106
+ {getScopeIcon(scope)}
107
+ {getScopeLabel(scope)}
108
+ {scope !== 'my' && docs[0]?.scopeName && (
109
+ <span className="text-text-secondary">: {docs[0].scopeName}</span>
110
+ )}
111
+ </div>
112
+ {sectionItems}
113
+ </div>
114
+ );
115
+ };
116
+
117
+ if (grouped.my.length > 0) {
118
+ sections.push(renderSection('my', grouped.my)!);
119
+ }
120
+ if (grouped.team.length > 0) {
121
+ sections.push(renderSection('team', grouped.team)!);
122
+ }
123
+ if (grouped.project.length > 0) {
124
+ sections.push(renderSection('project', grouped.project)!);
125
+ }
126
+
127
+ return sections;
128
+ }
129
+
130
+ // Flat list fallback
131
+ return documents.map((doc, index) => (
132
+ <button
133
+ key={doc.id}
134
+ ref={(el) => {
135
+ if (el) itemRefs.current.set(index, el);
136
+ else itemRefs.current.delete(index);
137
+ }}
138
+ onClick={() => onSelect(doc)}
139
+ className={`flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-background-secondary ${
140
+ selectedIndex === index ? 'bg-background-secondary' : ''
141
+ }`}
142
+ >
143
+ {getScopeIcon(doc.scope)}
144
+ <span className="flex-1 truncate text-text-primary">{doc.name}</span>
145
+ <span className="text-xs text-text-muted">{formatCharCount(doc.charCount)}</span>
146
+ </button>
147
+ ));
148
+ };
149
+
150
+ return (
151
+ <div
152
+ ref={containerRef}
153
+ className="fixed z-50 max-h-64 min-w-[240px] max-w-[320px] overflow-y-auto rounded-lg border border-border bg-background shadow-lg"
154
+ style={{
155
+ top: position.top,
156
+ left: position.left,
157
+ }}
158
+ >
159
+ {isLoading ? (
160
+ <div className="flex items-center justify-center py-4">
161
+ <Loader2 size={20} className="animate-spin text-text-muted" />
162
+ </div>
163
+ ) : documents.length === 0 ? (
164
+ <div className="px-3 py-4 text-center text-sm text-text-muted">
165
+ No documents found
166
+ </div>
167
+ ) : (
168
+ renderDocuments()
169
+ )}
170
+ </div>
171
+ );
172
+ }
173
+ );
174
+
175
+ export default MentionDropdown;
@@ -0,0 +1,293 @@
1
+ import {
2
+ useState,
3
+ useRef,
4
+ useEffect,
5
+ useCallback,
6
+ forwardRef,
7
+ useImperativeHandle,
8
+ type ChangeEvent,
9
+ type KeyboardEvent,
10
+ } from 'react';
11
+ import { createPortal } from 'react-dom';
12
+ import type { MentionableDocument } from '@chaaskit/shared';
13
+ import { useMentionSearch } from '../hooks/useMentionSearch';
14
+ import MentionDropdown, { type MentionDropdownHandle } from './MentionDropdown';
15
+ import { useConfig } from '../contexts/ConfigContext';
16
+
17
+ interface MentionInputProps {
18
+ value: string;
19
+ onChange: (value: string) => void;
20
+ onKeyDown?: (e: KeyboardEvent<HTMLTextAreaElement>) => void;
21
+ placeholder?: string;
22
+ disabled?: boolean;
23
+ className?: string;
24
+ rows?: number;
25
+ maxHeight?: number;
26
+ autoGrow?: boolean;
27
+ }
28
+
29
+ export interface MentionInputHandle {
30
+ focus: () => void;
31
+ blur: () => void;
32
+ }
33
+
34
+ const MentionInput = forwardRef<MentionInputHandle, MentionInputProps>(function MentionInput(
35
+ {
36
+ value,
37
+ onChange,
38
+ onKeyDown,
39
+ placeholder,
40
+ disabled,
41
+ className = '',
42
+ rows = 1,
43
+ maxHeight = 200,
44
+ autoGrow = true,
45
+ },
46
+ ref
47
+ ) {
48
+ const config = useConfig();
49
+ const documentsEnabled = config.documents?.enabled ?? false;
50
+
51
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
52
+ const dropdownRef = useRef<MentionDropdownHandle>(null);
53
+
54
+ const [showDropdown, setShowDropdown] = useState(false);
55
+ const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });
56
+ const [mentionQuery, setMentionQuery] = useState('');
57
+ const [mentionStartIndex, setMentionStartIndex] = useState(-1);
58
+ const [selectedIndex, setSelectedIndex] = useState(0);
59
+
60
+ const { search, clearResults, results, isSearching } = useMentionSearch();
61
+
62
+ // Expose methods to parent
63
+ useImperativeHandle(ref, () => ({
64
+ focus: () => textareaRef.current?.focus(),
65
+ blur: () => textareaRef.current?.blur(),
66
+ }));
67
+
68
+ // Auto-grow textarea
69
+ const adjustHeight = useCallback(() => {
70
+ if (!autoGrow || !textareaRef.current) return;
71
+ const textarea = textareaRef.current;
72
+ textarea.style.height = 'auto';
73
+ textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`;
74
+ }, [autoGrow, maxHeight]);
75
+
76
+ useEffect(() => {
77
+ adjustHeight();
78
+ }, [value, adjustHeight]);
79
+
80
+ // Calculate dropdown position based on cursor
81
+ const updateDropdownPosition = useCallback(() => {
82
+ if (!textareaRef.current) return;
83
+
84
+ const textarea = textareaRef.current;
85
+ const rect = textarea.getBoundingClientRect();
86
+
87
+ // Create a temporary span to measure text position
88
+ const span = document.createElement('span');
89
+ span.style.font = getComputedStyle(textarea).font;
90
+ span.style.visibility = 'hidden';
91
+ span.style.position = 'absolute';
92
+ span.style.whiteSpace = 'pre-wrap';
93
+ span.style.wordWrap = 'break-word';
94
+ span.style.width = `${textarea.clientWidth}px`;
95
+
96
+ // Get text up to cursor
97
+ const textBeforeCursor = value.substring(0, textarea.selectionStart);
98
+ span.textContent = textBeforeCursor;
99
+ document.body.appendChild(span);
100
+
101
+ // Calculate approximate position
102
+ const lines = textBeforeCursor.split('\n');
103
+ const lineHeight = parseInt(getComputedStyle(textarea).lineHeight) || 20;
104
+ const currentLineIndex = lines.length - 1;
105
+
106
+ document.body.removeChild(span);
107
+
108
+ // Position dropdown below the current line
109
+ const top = rect.top + (currentLineIndex + 1) * lineHeight + 4;
110
+ const left = rect.left;
111
+
112
+ // Ensure dropdown doesn't go off-screen
113
+ const viewportHeight = window.innerHeight;
114
+ const dropdownHeight = 256; // max-h-64 = 16rem = 256px
115
+ const adjustedTop = top + dropdownHeight > viewportHeight ? rect.top - dropdownHeight - 4 : top;
116
+
117
+ setDropdownPosition({
118
+ top: adjustedTop,
119
+ left: Math.max(8, Math.min(left, window.innerWidth - 328)), // 320px width + 8px margin
120
+ });
121
+ }, [value]);
122
+
123
+ // Parse for @ mentions as user types
124
+ const handleChange = useCallback(
125
+ (e: ChangeEvent<HTMLTextAreaElement>) => {
126
+ const newValue = e.target.value;
127
+ const cursorPos = e.target.selectionStart;
128
+
129
+ onChange(newValue);
130
+
131
+ if (!documentsEnabled) return;
132
+
133
+ // Find the @ that might be starting a mention
134
+ const textBeforeCursor = newValue.substring(0, cursorPos);
135
+ const atMatch = textBeforeCursor.match(/@(\S*)$/);
136
+
137
+ if (atMatch) {
138
+ const query = atMatch[1];
139
+ const startIndex = textBeforeCursor.length - atMatch[0].length;
140
+
141
+ setMentionStartIndex(startIndex);
142
+ setMentionQuery(query);
143
+ setSelectedIndex(0);
144
+ setShowDropdown(true);
145
+ updateDropdownPosition();
146
+
147
+ // Search with the query (after the @)
148
+ search(query || '');
149
+ } else {
150
+ // No @ mention in progress
151
+ if (showDropdown) {
152
+ setShowDropdown(false);
153
+ clearResults();
154
+ }
155
+ }
156
+ },
157
+ [onChange, documentsEnabled, showDropdown, search, clearResults, updateDropdownPosition]
158
+ );
159
+
160
+ // Handle keyboard navigation in dropdown
161
+ const handleKeyDown = useCallback(
162
+ (e: KeyboardEvent<HTMLTextAreaElement>) => {
163
+ if (showDropdown && results?.documents.length) {
164
+ const docCount = results.documents.length;
165
+
166
+ if (e.key === 'ArrowDown') {
167
+ e.preventDefault();
168
+ setSelectedIndex((prev) => (prev + 1) % docCount);
169
+ return;
170
+ }
171
+
172
+ if (e.key === 'ArrowUp') {
173
+ e.preventDefault();
174
+ setSelectedIndex((prev) => (prev - 1 + docCount) % docCount);
175
+ return;
176
+ }
177
+
178
+ if (e.key === 'Enter' || e.key === 'Tab') {
179
+ e.preventDefault();
180
+ const selectedDoc = results.documents[selectedIndex];
181
+ if (selectedDoc) {
182
+ insertMention(selectedDoc);
183
+ }
184
+ return;
185
+ }
186
+
187
+ if (e.key === 'Escape') {
188
+ e.preventDefault();
189
+ setShowDropdown(false);
190
+ clearResults();
191
+ return;
192
+ }
193
+ }
194
+
195
+ // Pass through to parent handler
196
+ onKeyDown?.(e);
197
+ },
198
+ [showDropdown, results, selectedIndex, onKeyDown, clearResults]
199
+ );
200
+
201
+ // Insert the selected mention
202
+ const insertMention = useCallback(
203
+ (doc: MentionableDocument) => {
204
+ if (mentionStartIndex < 0) return;
205
+
206
+ const textarea = textareaRef.current;
207
+ if (!textarea) return;
208
+
209
+ const cursorPos = textarea.selectionStart;
210
+ const beforeMention = value.substring(0, mentionStartIndex);
211
+ const afterMention = value.substring(cursorPos);
212
+
213
+ // Insert the full path
214
+ const mentionText = doc.path;
215
+ const newValue = beforeMention + mentionText + ' ' + afterMention;
216
+
217
+ onChange(newValue);
218
+
219
+ // Move cursor after the inserted mention
220
+ const newCursorPos = mentionStartIndex + mentionText.length + 1;
221
+ setTimeout(() => {
222
+ textarea.selectionStart = newCursorPos;
223
+ textarea.selectionEnd = newCursorPos;
224
+ textarea.focus();
225
+ }, 0);
226
+
227
+ setShowDropdown(false);
228
+ clearResults();
229
+ },
230
+ [value, mentionStartIndex, onChange, clearResults]
231
+ );
232
+
233
+ // Handle document selection from dropdown
234
+ const handleSelectDocument = useCallback(
235
+ (doc: MentionableDocument) => {
236
+ insertMention(doc);
237
+ },
238
+ [insertMention]
239
+ );
240
+
241
+ // Close dropdown when clicking outside
242
+ useEffect(() => {
243
+ function handleClickOutside(e: MouseEvent) {
244
+ if (
245
+ showDropdown &&
246
+ textareaRef.current &&
247
+ !textareaRef.current.contains(e.target as Node)
248
+ ) {
249
+ setShowDropdown(false);
250
+ clearResults();
251
+ }
252
+ }
253
+
254
+ document.addEventListener('mousedown', handleClickOutside);
255
+ return () => document.removeEventListener('mousedown', handleClickOutside);
256
+ }, [showDropdown, clearResults]);
257
+
258
+ return (
259
+ <>
260
+ <textarea
261
+ ref={textareaRef}
262
+ value={value}
263
+ onChange={handleChange}
264
+ onKeyDown={handleKeyDown}
265
+ placeholder={placeholder}
266
+ disabled={disabled}
267
+ rows={rows}
268
+ className={className}
269
+ style={{
270
+ height: 'auto',
271
+ minHeight: `${rows * 22 + 22}px`,
272
+ }}
273
+ onInput={adjustHeight}
274
+ />
275
+
276
+ {showDropdown &&
277
+ createPortal(
278
+ <MentionDropdown
279
+ ref={dropdownRef}
280
+ documents={results?.documents ?? []}
281
+ grouped={results?.grouped}
282
+ isLoading={isSearching}
283
+ selectedIndex={selectedIndex}
284
+ onSelect={handleSelectDocument}
285
+ position={dropdownPosition}
286
+ />,
287
+ document.body
288
+ )}
289
+ </>
290
+ );
291
+ });
292
+
293
+ export default MentionInput;