@schandlergarcia/sf-web-components 1.7.0 → 1.9.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 (188) hide show
  1. package/dist/components/library/cards/ActionList.d.ts +10 -10
  2. package/dist/components/library/cards/ActionList.js +2 -3
  3. package/dist/components/library/cards/ActionList.js.map +1 -1
  4. package/dist/components/library/cards/ActivityCard.d.ts +18 -5
  5. package/dist/components/library/cards/ActivityCard.js +3 -4
  6. package/dist/components/library/cards/ActivityCard.js.map +1 -1
  7. package/dist/components/library/cards/BaseCard.d.ts +30 -24
  8. package/dist/components/library/cards/BaseCard.js +2 -3
  9. package/dist/components/library/cards/BaseCard.js.map +1 -1
  10. package/dist/components/library/cards/CalloutCard.d.ts +11 -9
  11. package/dist/components/library/cards/CalloutCard.js +2 -3
  12. package/dist/components/library/cards/CalloutCard.js.map +1 -1
  13. package/dist/components/library/cards/ChartCard.d.ts +29 -17
  14. package/dist/components/library/cards/ChartCard.js +13 -14
  15. package/dist/components/library/cards/ChartCard.js.map +1 -1
  16. package/dist/components/library/cards/FeedPanel.d.ts +12 -11
  17. package/dist/components/library/cards/FeedPanel.js +3 -4
  18. package/dist/components/library/cards/FeedPanel.js.map +1 -1
  19. package/dist/components/library/cards/ListCard.d.ts +33 -20
  20. package/dist/components/library/cards/ListCard.js +35 -35
  21. package/dist/components/library/cards/ListCard.js.map +1 -1
  22. package/dist/components/library/cards/MetricCard.d.ts +23 -17
  23. package/dist/components/library/cards/MetricCard.js +10 -11
  24. package/dist/components/library/cards/MetricCard.js.map +1 -1
  25. package/dist/components/library/cards/MetricsStrip.d.ts +11 -11
  26. package/dist/components/library/cards/MetricsStrip.js +1 -1
  27. package/dist/components/library/cards/MetricsStrip.js.map +1 -1
  28. package/dist/components/library/cards/SectionCard.d.ts +17 -12
  29. package/dist/components/library/cards/SectionCard.js +18 -19
  30. package/dist/components/library/cards/SectionCard.js.map +1 -1
  31. package/dist/components/library/cards/SemanticMetricCard.d.ts +15 -20
  32. package/dist/components/library/cards/SemanticMetricCardWithLoading.d.ts +8 -7
  33. package/dist/components/library/cards/SemanticTableCard.d.ts +13 -18
  34. package/dist/components/library/cards/SemanticTableCardWithLoading.d.ts +8 -7
  35. package/dist/components/library/cards/StatusCard.d.ts +29 -15
  36. package/dist/components/library/cards/StatusCard.js +16 -17
  37. package/dist/components/library/cards/StatusCard.js.map +1 -1
  38. package/dist/components/library/cards/TableCard.d.ts +40 -23
  39. package/dist/components/library/cards/TableCard.js +59 -59
  40. package/dist/components/library/cards/TableCard.js.map +1 -1
  41. package/dist/components/library/cards/WidgetCard.d.ts +19 -11
  42. package/dist/components/library/cards/WidgetCard.js.map +1 -1
  43. package/dist/components/library/charts/D3Chart.d.ts +23 -16
  44. package/dist/components/library/charts/D3Chart.js.map +1 -1
  45. package/dist/components/library/charts/D3ChartTemplates.d.ts +33 -3
  46. package/dist/components/library/charts/D3ChartTemplates.js +7 -7
  47. package/dist/components/library/charts/D3ChartTemplates.js.map +1 -1
  48. package/dist/components/library/charts/GeoMap.d.ts +81 -18
  49. package/dist/components/library/charts/GeoMap.js +28 -26
  50. package/dist/components/library/charts/GeoMap.js.map +1 -1
  51. package/dist/components/library/chat/ChatBar.d.ts +14 -11
  52. package/dist/components/library/chat/ChatBar.js +2 -3
  53. package/dist/components/library/chat/ChatBar.js.map +1 -1
  54. package/dist/components/library/chat/ChatInput.d.ts +9 -8
  55. package/dist/components/library/chat/ChatInput.js.map +1 -1
  56. package/dist/components/library/chat/ChatMessage.d.ts +17 -4
  57. package/dist/components/library/chat/ChatMessage.js.map +1 -1
  58. package/dist/components/library/chat/ChatMessageList.d.ts +11 -8
  59. package/dist/components/library/chat/ChatMessageList.js.map +1 -1
  60. package/dist/components/library/chat/ChatPanel.d.ts +16 -12
  61. package/dist/components/library/chat/ChatPanel.js +8 -9
  62. package/dist/components/library/chat/ChatPanel.js.map +1 -1
  63. package/dist/components/library/chat/ChatSuggestions.d.ts +5 -4
  64. package/dist/components/library/chat/ChatSuggestions.js +2 -3
  65. package/dist/components/library/chat/ChatSuggestions.js.map +1 -1
  66. package/dist/components/library/chat/ChatToolCall.d.ts +11 -3
  67. package/dist/components/library/chat/ChatToolCall.js.map +1 -1
  68. package/dist/components/library/chat/ChatTypingIndicator.d.ts +4 -3
  69. package/dist/components/library/chat/ChatTypingIndicator.js +2 -3
  70. package/dist/components/library/chat/ChatTypingIndicator.js.map +1 -1
  71. package/dist/components/library/chat/ChatWelcome.d.ts +9 -7
  72. package/dist/components/library/chat/ChatWelcome.js +6 -7
  73. package/dist/components/library/chat/ChatWelcome.js.map +1 -1
  74. package/dist/components/library/chat/index.d.ts +10 -0
  75. package/dist/components/library/chat/useChatState.d.ts +36 -11
  76. package/dist/components/library/chat/useChatState.js +63 -46
  77. package/dist/components/library/chat/useChatState.js.map +1 -1
  78. package/dist/components/library/data/DataModeProvider.d.ts +15 -11
  79. package/dist/components/library/data/DataModeProvider.js +1 -1
  80. package/dist/components/library/data/DataModeProvider.js.map +1 -1
  81. package/dist/components/library/data/DataModeToggle.d.ts +4 -3
  82. package/dist/components/library/data/DataModeToggle.js +4 -5
  83. package/dist/components/library/data/DataModeToggle.js.map +1 -1
  84. package/dist/components/library/data/chartDataProvider.d.ts +41 -3
  85. package/dist/components/library/data/filterUtils.d.ts +38 -9
  86. package/dist/components/library/data/filterUtils.js.map +1 -1
  87. package/dist/components/library/data/useDataSource.d.ts +6 -4
  88. package/dist/components/library/data/useDataSource.js.map +1 -1
  89. package/dist/components/library/data/usePageFilters.d.ts +31 -5
  90. package/dist/components/library/data/usePageFilters.js +6 -2
  91. package/dist/components/library/data/usePageFilters.js.map +1 -1
  92. package/dist/components/library/filters/FilterBar.d.ts +18 -8
  93. package/dist/components/library/filters/FilterBar.js +2 -3
  94. package/dist/components/library/filters/FilterBar.js.map +1 -1
  95. package/dist/components/library/filters/SearchFilter.d.ts +7 -6
  96. package/dist/components/library/filters/SearchFilter.js +2 -3
  97. package/dist/components/library/filters/SearchFilter.js.map +1 -1
  98. package/dist/components/library/filters/SelectFilter.d.ts +13 -7
  99. package/dist/components/library/filters/SelectFilter.js +2 -3
  100. package/dist/components/library/filters/SelectFilter.js.map +1 -1
  101. package/dist/components/library/filters/ToggleFilter.d.ts +7 -5
  102. package/dist/components/library/filters/ToggleFilter.js +2 -3
  103. package/dist/components/library/filters/ToggleFilter.js.map +1 -1
  104. package/dist/components/library/forms/FormField.d.ts +10 -8
  105. package/dist/components/library/forms/FormField.js +3 -4
  106. package/dist/components/library/forms/FormField.js.map +1 -1
  107. package/dist/components/library/forms/FormModal.d.ts +23 -14
  108. package/dist/components/library/forms/FormModal.js.map +1 -1
  109. package/dist/components/library/forms/FormRenderer.d.ts +29 -9
  110. package/dist/components/library/forms/FormRenderer.js +6 -7
  111. package/dist/components/library/forms/FormRenderer.js.map +1 -1
  112. package/dist/components/library/forms/FormSection.d.ts +10 -8
  113. package/dist/components/library/forms/FormSection.js +2 -3
  114. package/dist/components/library/forms/FormSection.js.map +1 -1
  115. package/dist/components/library/forms/index.d.ts +5 -0
  116. package/dist/components/library/forms/useFormState.d.ts +23 -15
  117. package/dist/components/library/forms/useFormState.js +53 -47
  118. package/dist/components/library/forms/useFormState.js.map +1 -1
  119. package/dist/components/library/index.d.ts +92 -73
  120. package/dist/components/library/index.js +25 -25
  121. package/dist/components/library/index.js.map +1 -1
  122. package/dist/components/library/layout/PageContainer.d.ts +6 -4
  123. package/dist/components/library/layout/PageContainer.js +4 -5
  124. package/dist/components/library/layout/PageContainer.js.map +1 -1
  125. package/dist/components/library/skeletons/CardSkeleton.d.ts +5 -4
  126. package/dist/components/library/skeletons/CardSkeleton.js +2 -3
  127. package/dist/components/library/skeletons/CardSkeleton.js.map +1 -1
  128. package/dist/components/library/theme/AppThemeProvider.d.ts +13 -50
  129. package/dist/components/library/theme/AppThemeProvider.js.map +1 -1
  130. package/dist/components/library/theme/tokens.d.ts +45 -44
  131. package/dist/components/library/theme/tokens.js.map +1 -1
  132. package/package.json +4 -1
  133. package/src/components/library/cards/{ActionList.jsx → ActionList.tsx} +13 -9
  134. package/src/components/library/cards/{ActivityCard.jsx → ActivityCard.tsx} +33 -4
  135. package/src/components/library/cards/{BaseCard.jsx → BaseCard.tsx} +33 -6
  136. package/src/components/library/cards/{CalloutCard.jsx → CalloutCard.tsx} +12 -10
  137. package/src/components/library/cards/{ChartCard.jsx → ChartCard.tsx} +32 -6
  138. package/src/components/library/cards/{FeedPanel.jsx → FeedPanel.tsx} +13 -2
  139. package/src/components/library/cards/{ListCard.jsx → ListCard.tsx} +43 -7
  140. package/src/components/library/cards/{MetricCard.jsx → MetricCard.tsx} +25 -6
  141. package/src/components/library/cards/{MetricsStrip.jsx → MetricsStrip.tsx} +22 -12
  142. package/src/components/library/cards/{SectionCard.jsx → SectionCard.tsx} +27 -8
  143. package/src/components/library/cards/{SemanticMetricCard.jsx → SemanticMetricCard.tsx} +18 -6
  144. package/src/components/library/cards/{SemanticMetricCardWithLoading.jsx → SemanticMetricCardWithLoading.tsx} +9 -3
  145. package/src/components/library/cards/{SemanticTableCard.jsx → SemanticTableCard.tsx} +16 -5
  146. package/src/components/library/cards/{SemanticTableCardWithLoading.jsx → SemanticTableCardWithLoading.tsx} +9 -5
  147. package/src/components/library/cards/{StatusCard.jsx → StatusCard.tsx} +61 -12
  148. package/src/components/library/cards/{TableCard.jsx → TableCard.tsx} +51 -12
  149. package/src/components/library/cards/{WidgetCard.jsx → WidgetCard.tsx} +28 -5
  150. package/src/components/library/charts/{D3Chart.jsx → D3Chart.tsx} +27 -7
  151. package/src/components/library/charts/{D3ChartTemplates.jsx → D3ChartTemplates.tsx} +60 -28
  152. package/src/components/library/charts/{GeoMap.jsx → GeoMap.tsx} +106 -17
  153. package/src/components/library/chat/{ChatBar.jsx → ChatBar.tsx} +19 -8
  154. package/src/components/library/chat/{ChatInput.jsx → ChatInput.tsx} +13 -11
  155. package/src/components/library/chat/{ChatMessage.jsx → ChatMessage.tsx} +22 -9
  156. package/src/components/library/chat/{ChatMessageList.jsx → ChatMessageList.tsx} +13 -11
  157. package/src/components/library/chat/{ChatPanel.jsx → ChatPanel.tsx} +16 -13
  158. package/src/components/library/chat/{ChatSuggestions.jsx → ChatSuggestions.tsx} +6 -5
  159. package/src/components/library/chat/{ChatToolCall.jsx → ChatToolCall.tsx} +14 -4
  160. package/src/components/library/chat/{ChatTypingIndicator.jsx → ChatTypingIndicator.tsx} +5 -2
  161. package/src/components/library/chat/{ChatWelcome.jsx → ChatWelcome.tsx} +9 -7
  162. package/src/components/library/chat/index.tsx +26 -0
  163. package/src/components/library/chat/useChatState.tsx +181 -0
  164. package/src/components/library/data/{DataModeProvider.jsx → DataModeProvider.tsx} +25 -8
  165. package/src/components/library/data/{DataModeToggle.jsx → DataModeToggle.tsx} +5 -2
  166. package/src/components/library/data/{chartDataProvider.jsx → chartDataProvider.tsx} +49 -5
  167. package/src/components/library/data/{filterUtils.jsx → filterUtils.tsx} +58 -12
  168. package/src/components/library/data/{useDataSource.jsx → useDataSource.tsx} +9 -2
  169. package/src/components/library/data/{usePageFilters.jsx → usePageFilters.tsx} +49 -9
  170. package/src/components/library/filters/{FilterBar.jsx → FilterBar.tsx} +21 -11
  171. package/src/components/library/filters/{SearchFilter.jsx → SearchFilter.tsx} +8 -2
  172. package/src/components/library/filters/{SelectFilter.jsx → SelectFilter.tsx} +15 -8
  173. package/src/components/library/filters/{ToggleFilter.jsx → ToggleFilter.tsx} +7 -6
  174. package/src/components/library/forms/{FormField.jsx → FormField.tsx} +91 -45
  175. package/src/components/library/forms/{FormModal.jsx → FormModal.tsx} +21 -20
  176. package/src/components/library/forms/{FormRenderer.jsx → FormRenderer.tsx} +32 -10
  177. package/src/components/library/forms/{FormSection.jsx → FormSection.tsx} +13 -7
  178. package/src/components/library/forms/index.tsx +11 -0
  179. package/src/components/library/forms/{useFormState.jsx → useFormState.tsx} +43 -23
  180. package/src/components/library/{index.jsx → index.ts} +14 -14
  181. package/src/components/library/layout/{PageContainer.jsx → PageContainer.tsx} +6 -3
  182. package/src/components/library/skeletons/{CardSkeleton.jsx → CardSkeleton.tsx} +5 -4
  183. package/src/components/library/theme/{AppThemeProvider.jsx → AppThemeProvider.tsx} +20 -7
  184. package/src/components/library/theme/{tokens.jsx → tokens.tsx} +37 -3
  185. package/src/components/library/chat/index.jsx +0 -10
  186. package/src/components/library/chat/useChatState.jsx +0 -130
  187. package/src/components/library/forms/index.jsx +0 -5
  188. /package/src/components/library/filters/{index.jsx → index.ts} +0 -0
@@ -1,13 +1,14 @@
1
- import React from "react";
2
1
  import { SparklesIcon } from "@heroicons/react/24/outline";
3
2
 
3
+ export interface ChatSuggestionsProps {
4
+ suggestions?: string[];
5
+ onSelect?: (suggestion: string) => void;
6
+ }
7
+
4
8
  /**
5
9
  * Quick-action prompt buttons. Place above the input or after an assistant message.
6
- *
7
- * @param {string[]} suggestions — prompt strings
8
- * @param {Function} onSelect — (suggestion) => void
9
10
  */
10
- export default function ChatSuggestions({ suggestions = [], onSelect }) {
11
+ export default function ChatSuggestions({ suggestions = [], onSelect }: ChatSuggestionsProps) {
11
12
  if (!suggestions.length) return null;
12
13
 
13
14
  return (
@@ -1,4 +1,4 @@
1
- import React, { useState } from "react";
1
+ import { useState } from "react";
2
2
  import Spinner from "../ui/Spinner";
3
3
  import {
4
4
  WrenchScrewdriverIcon,
@@ -7,6 +7,18 @@ import {
7
7
  ChevronDownIcon,
8
8
  } from "@heroicons/react/24/outline";
9
9
 
10
+ export interface ToolCall {
11
+ id?: string;
12
+ name: string;
13
+ args?: unknown;
14
+ status: "running" | "complete" | "error";
15
+ result?: unknown;
16
+ }
17
+
18
+ export interface ChatToolCallProps {
19
+ toolCall: ToolCall;
20
+ }
21
+
10
22
  const STATUS_CONFIG = {
11
23
  running: {
12
24
  icon: <Spinner size="xs" tone="brand" label="Running" />,
@@ -30,10 +42,8 @@ const STATUS_CONFIG = {
30
42
 
31
43
  /**
32
44
  * Displays an agent tool call / function execution step.
33
- *
34
- * @param {Object} toolCall — { id?, name, args?, status: "running"|"complete"|"error", result? }
35
45
  */
36
- export default function ChatToolCall({ toolCall }) {
46
+ export default function ChatToolCall({ toolCall }: ChatToolCallProps) {
37
47
  const [expanded, setExpanded] = useState(false);
38
48
  const config = STATUS_CONFIG[toolCall.status] ?? STATUS_CONFIG.running;
39
49
  const hasDetails = toolCall.args || toolCall.result;
@@ -1,10 +1,13 @@
1
- import React from "react";
2
1
  import { CpuChipIcon } from "@heroicons/react/24/solid";
3
2
 
3
+ export interface ChatTypingIndicatorProps {
4
+ label?: string;
5
+ }
6
+
4
7
  /**
5
8
  * Animated typing indicator shown while the agent is processing.
6
9
  */
7
- export default function ChatTypingIndicator({ label = "Thinking" }) {
10
+ export default function ChatTypingIndicator({ label = "Thinking" }: ChatTypingIndicatorProps) {
8
11
  return (
9
12
  <div className="flex items-start gap-3">
10
13
  <div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-brand-100 dark:bg-brand-900/40">
@@ -2,14 +2,16 @@ import React from "react";
2
2
  import { CpuChipIcon } from "@heroicons/react/24/solid";
3
3
  import ChatSuggestions from "./ChatSuggestions";
4
4
 
5
+ export interface ChatWelcomeProps {
6
+ title?: string;
7
+ subtitle?: string;
8
+ suggestions?: string[];
9
+ onSuggestion?: (text: string) => void;
10
+ icon?: React.ReactNode;
11
+ }
12
+
5
13
  /**
6
14
  * Empty-state welcome screen shown before the first message.
7
- *
8
- * @param {string} title — welcome heading
9
- * @param {string} subtitle — description text
10
- * @param {string[]} suggestions — starter prompt suggestions
11
- * @param {Function} onSuggestion — (text) => void
12
- * @param {React.ReactNode} icon — custom icon override
13
15
  */
14
16
  export default function ChatWelcome({
15
17
  title = "How can I help?",
@@ -17,7 +19,7 @@ export default function ChatWelcome({
17
19
  suggestions = [],
18
20
  onSuggestion,
19
21
  icon,
20
- }) {
22
+ }: ChatWelcomeProps) {
21
23
  return (
22
24
  <div className="flex flex-1 flex-col items-center justify-center gap-4 px-6 py-12 text-center">
23
25
  {icon ?? (
@@ -0,0 +1,26 @@
1
+ export { default as ChatPanel } from "./ChatPanel";
2
+ export { default as ChatBar } from "./ChatBar";
3
+ export { default as ChatMessageList } from "./ChatMessageList";
4
+ export { default as ChatMessage } from "./ChatMessage";
5
+ export { default as ChatInput } from "./ChatInput";
6
+ export { default as ChatTypingIndicator } from "./ChatTypingIndicator";
7
+ export { default as ChatSuggestions } from "./ChatSuggestions";
8
+ export { default as ChatToolCall } from "./ChatToolCall";
9
+ export { default as ChatWelcome } from "./ChatWelcome";
10
+ export { default as useChatState } from "./useChatState";
11
+
12
+ export type { ChatPanelProps } from "./ChatPanel";
13
+ export type { ChatBarProps } from "./ChatBar";
14
+ export type { ChatMessageListProps } from "./ChatMessageList";
15
+ export type { ChatMessageProps, ChatMessageData } from "./ChatMessage";
16
+ export type { ChatInputProps } from "./ChatInput";
17
+ export type { ChatTypingIndicatorProps } from "./ChatTypingIndicator";
18
+ export type { ChatSuggestionsProps } from "./ChatSuggestions";
19
+ export type { ChatToolCallProps, ToolCall } from "./ChatToolCall";
20
+ export type { ChatWelcomeProps } from "./ChatWelcome";
21
+ export type {
22
+ UseChatStateOptions,
23
+ UseChatStateReturn,
24
+ ChatMessage as ChatMessageType,
25
+ ChatStateHelpers,
26
+ } from "./useChatState";
@@ -0,0 +1,181 @@
1
+ import { useState, useCallback, useRef } from "react";
2
+ import { ToolCall } from "./ChatToolCall";
3
+
4
+ let _nextId = 1;
5
+ function uid(): string {
6
+ return `msg-${Date.now()}-${_nextId++}`;
7
+ }
8
+
9
+ export interface ChatMessage {
10
+ id: string;
11
+ role: "user" | "assistant" | "system";
12
+ content?: string;
13
+ timestamp: string;
14
+ components?: unknown[];
15
+ toolCalls?: ToolCall[];
16
+ isError?: boolean;
17
+ isStreaming?: boolean;
18
+ }
19
+
20
+ export interface ChatStateHelpers {
21
+ addMessage: (msg: Partial<ChatMessage>) => string;
22
+ updateMessage: (id: string, updates: Partial<ChatMessage>) => void;
23
+ appendChunk: (id: string, chunk: string) => void;
24
+ setStreaming: (streaming: boolean) => void;
25
+ }
26
+
27
+ export interface UseChatStateOptions {
28
+ initialMessages?: Partial<ChatMessage>[];
29
+ onSend?: (
30
+ userMessage: ChatMessage,
31
+ allMessages: ChatMessage[],
32
+ helpers: ChatStateHelpers
33
+ ) => Promise<Partial<ChatMessage> | void>;
34
+ }
35
+
36
+ export interface UseChatStateReturn {
37
+ messages: ChatMessage[];
38
+ isLoading: boolean;
39
+ isStreaming: boolean;
40
+ error: string | null;
41
+ sendMessage: (content: string, extra?: Partial<ChatMessage>) => Promise<void>;
42
+ addMessage: (msg: Partial<ChatMessage>) => string;
43
+ updateMessage: (id: string, updates: Partial<ChatMessage>) => void;
44
+ appendChunk: (id: string, chunk: string) => void;
45
+ removeMessage: (id: string) => void;
46
+ clearMessages: () => void;
47
+ retryLast: () => void;
48
+ setError: (error: string | null) => void;
49
+ }
50
+
51
+ /**
52
+ * Core state management hook for AI chat.
53
+ *
54
+ * @example
55
+ * const chat = useChatState({
56
+ * onSend: async (msg, history) => {
57
+ * const res = await fetch("/api/chat", { method: "POST", body: JSON.stringify({ messages: history }) });
58
+ * const data = await res.json();
59
+ * return { role: "assistant", content: data.reply, components: data.components };
60
+ * },
61
+ * });
62
+ */
63
+ export default function useChatState({ initialMessages = [], onSend }: UseChatStateOptions = {}): UseChatStateReturn {
64
+ const [messages, setMessages] = useState<ChatMessage[]>(() =>
65
+ initialMessages.map((m) => ({
66
+ id: uid(),
67
+ timestamp: new Date().toISOString(),
68
+ role: m.role || "user",
69
+ ...m
70
+ } as ChatMessage))
71
+ );
72
+ const [isLoading, setIsLoading] = useState(false);
73
+ const [isStreaming, setIsStreaming] = useState(false);
74
+ const [error, setError] = useState<string | null>(null);
75
+ const onSendRef = useRef(onSend);
76
+ onSendRef.current = onSend;
77
+
78
+ const addMessage = useCallback((msg: Partial<ChatMessage>): string => {
79
+ const full: ChatMessage = {
80
+ id: uid(),
81
+ timestamp: new Date().toISOString(),
82
+ role: msg.role || "assistant",
83
+ ...msg
84
+ } as ChatMessage;
85
+ setMessages((prev) => [...prev, full]);
86
+ return full.id;
87
+ }, []);
88
+
89
+ const updateMessage = useCallback((id: string, updates: Partial<ChatMessage>) => {
90
+ setMessages((prev) =>
91
+ prev.map((m) => (m.id === id ? { ...m, ...updates } : m))
92
+ );
93
+ }, []);
94
+
95
+ const appendChunk = useCallback((id: string, chunk: string) => {
96
+ setMessages((prev) =>
97
+ prev.map((m) =>
98
+ m.id === id ? { ...m, content: (m.content ?? "") + chunk } : m
99
+ )
100
+ );
101
+ }, []);
102
+
103
+ const removeMessage = useCallback((id: string) => {
104
+ setMessages((prev) => prev.filter((m) => m.id !== id));
105
+ }, []);
106
+
107
+ const clearMessages = useCallback(() => {
108
+ setMessages([]);
109
+ setError(null);
110
+ }, []);
111
+
112
+ const sendMessage = useCallback(
113
+ async (content: string, extra: Partial<ChatMessage> = {}) => {
114
+ if (!content?.trim()) return;
115
+ setError(null);
116
+
117
+ const userMsg: ChatMessage = {
118
+ role: "user",
119
+ content: content.trim(),
120
+ ...extra,
121
+ id: uid(),
122
+ timestamp: new Date().toISOString()
123
+ } as ChatMessage;
124
+
125
+ setMessages((prev) => [...prev, userMsg]);
126
+ setIsLoading(true);
127
+
128
+ try {
129
+ const allMsgs = [...messages, userMsg];
130
+ const result = await onSendRef.current?.(userMsg, allMsgs, {
131
+ addMessage,
132
+ updateMessage,
133
+ appendChunk,
134
+ setStreaming: setIsStreaming,
135
+ });
136
+
137
+ if (result && typeof result === "object" && result.role) {
138
+ addMessage(result);
139
+ }
140
+ } catch (err) {
141
+ const errorMessage = err instanceof Error ? err.message : "Failed to send message";
142
+ setError(errorMessage);
143
+ addMessage({
144
+ role: "system",
145
+ content: err instanceof Error ? err.message : "Something went wrong. Please try again.",
146
+ isError: true,
147
+ });
148
+ } finally {
149
+ setIsLoading(false);
150
+ setIsStreaming(false);
151
+ }
152
+ },
153
+ [messages, addMessage, updateMessage, appendChunk]
154
+ );
155
+
156
+ const retryLast = useCallback(() => {
157
+ const lastUser = [...messages].reverse().find((m) => m.role === "user");
158
+ if (!lastUser) return;
159
+
160
+ const idx = messages.lastIndexOf(lastUser);
161
+ setMessages(messages.slice(0, idx));
162
+ setError(null);
163
+
164
+ sendMessage(lastUser.content || "");
165
+ }, [messages, sendMessage]);
166
+
167
+ return {
168
+ messages,
169
+ isLoading,
170
+ isStreaming,
171
+ error,
172
+ sendMessage,
173
+ addMessage,
174
+ updateMessage,
175
+ appendChunk,
176
+ removeMessage,
177
+ clearMessages,
178
+ retryLast,
179
+ setError,
180
+ };
181
+ }
@@ -1,6 +1,16 @@
1
1
  import React from "react";
2
2
 
3
- const DataModeContext = React.createContext({
3
+ export type DataMode = "sample" | "live";
4
+
5
+ export interface DataModeContextValue {
6
+ mode: DataMode;
7
+ isSample: boolean;
8
+ isLive: boolean;
9
+ toggle: () => void;
10
+ setMode: (mode: DataMode) => void;
11
+ }
12
+
13
+ const DataModeContext = React.createContext<DataModeContextValue>({
4
14
  mode: "sample",
5
15
  isSample: true,
6
16
  isLive: false,
@@ -9,30 +19,37 @@ const DataModeContext = React.createContext({
9
19
  });
10
20
 
11
21
  const STORAGE_KEY = "app-data-mode";
12
- const VALID_MODES = ["sample", "live"];
22
+ const VALID_MODES: DataMode[] = ["sample", "live"];
13
23
 
14
24
  /**
15
25
  * Read the current data mode from any component.
16
26
  *
17
27
  * @returns {{ mode: "sample"|"live", isSample: boolean, isLive: boolean, toggle: () => void, setMode: (mode) => void }}
18
28
  */
19
- export function useDataMode() {
29
+ export function useDataMode(): DataModeContextValue {
20
30
  return React.useContext(DataModeContext);
21
31
  }
22
32
 
33
+ export interface DataModeProviderProps {
34
+ initialMode?: DataMode;
35
+ children: React.ReactNode;
36
+ }
37
+
23
38
  /**
24
39
  * Provides global data-mode state (sample vs live) to the component tree.
25
40
  * Persists to localStorage so the choice survives page reloads.
26
41
  *
27
42
  * Wrap once in _app.js alongside AppThemeProvider.
28
43
  */
29
- export default function DataModeProvider({ initialMode = "sample", children }) {
30
- const [mode, setModeState] = React.useState(initialMode);
44
+ export default function DataModeProvider({ initialMode = "sample", children }: DataModeProviderProps) {
45
+ const [mode, setModeState] = React.useState<DataMode>(initialMode);
31
46
 
32
47
  React.useEffect(() => {
33
48
  try {
34
49
  const stored = window.localStorage.getItem(STORAGE_KEY);
35
- if (VALID_MODES.includes(stored)) setModeState(stored);
50
+ if (stored && VALID_MODES.includes(stored as DataMode)) {
51
+ setModeState(stored as DataMode);
52
+ }
36
53
  } catch {
37
54
  // SSR or storage unavailable
38
55
  }
@@ -46,11 +63,11 @@ export default function DataModeProvider({ initialMode = "sample", children }) {
46
63
  }
47
64
  }, [mode]);
48
65
 
49
- const setMode = React.useCallback((m) => {
66
+ const setMode = React.useCallback((m: DataMode) => {
50
67
  if (VALID_MODES.includes(m)) setModeState(m);
51
68
  }, []);
52
69
 
53
- const value = React.useMemo(
70
+ const value = React.useMemo<DataModeContextValue>(
54
71
  () => ({
55
72
  mode,
56
73
  isSample: mode === "sample",
@@ -1,12 +1,15 @@
1
- import React from "react";
2
1
  import { useDataMode } from "./DataModeProvider";
3
2
  import { BeakerIcon, SignalIcon } from "@heroicons/react/24/outline";
4
3
 
4
+ export interface DataModeToggleProps {
5
+ className?: string;
6
+ }
7
+
5
8
  /**
6
9
  * Pill toggle for switching between sample and live data modes.
7
10
  * Place in the AppShell header next to the theme toggle.
8
11
  */
9
- export default function DataModeToggle({ className = "" }) {
12
+ export default function DataModeToggle({ className = "" }: DataModeToggleProps) {
10
13
  const { mode, toggle } = useDataMode();
11
14
  const isSample = mode === "sample";
12
15
 
@@ -1,7 +1,52 @@
1
1
  // Minimal semantic data provider (seed data + lookup helpers).
2
2
  // This is intentionally local/in-memory for now; later we can swap to API-backed providers.
3
3
 
4
- const SEMANTIC_DATASETS = {
4
+ export type ChangeType = "positive" | "negative" | "neutral";
5
+ export type MetricColor = "primary" | "success" | "warning" | "danger";
6
+ export type ColumnType = "currency" | "percentage" | "number" | "text";
7
+
8
+ export interface SemanticMetric {
9
+ metricId: string;
10
+ title: string;
11
+ subtitle: string;
12
+ value: string;
13
+ change?: string;
14
+ changeType?: ChangeType;
15
+ color?: MetricColor;
16
+ trend?: string;
17
+ }
18
+
19
+ export interface TableColumn {
20
+ key: string;
21
+ label: string;
22
+ type?: ColumnType;
23
+ sortable?: boolean;
24
+ mono?: boolean;
25
+ }
26
+
27
+ export interface TableRow {
28
+ id: number;
29
+ [key: string]: any;
30
+ }
31
+
32
+ export interface SemanticTable {
33
+ title: string;
34
+ subtitle: string;
35
+ columns: TableColumn[];
36
+ rows: TableRow[];
37
+ }
38
+
39
+ export interface SemanticDataset {
40
+ title: string;
41
+ metrics?: SemanticMetric[];
42
+ table?: SemanticTable;
43
+ }
44
+
45
+ export interface SemanticDatasets {
46
+ [key: string]: SemanticDataset;
47
+ }
48
+
49
+ const SEMANTIC_DATASETS: SemanticDatasets = {
5
50
  sales_pipeline_qtr: {
6
51
  title: "Sales Pipeline (Quarter)",
7
52
  metrics: [
@@ -44,18 +89,17 @@ const SEMANTIC_DATASETS = {
44
89
  }
45
90
  };
46
91
 
47
- export function listSemanticIds() {
92
+ export function listSemanticIds(): string[] {
48
93
  return Object.keys(SEMANTIC_DATASETS);
49
94
  }
50
95
 
51
- export function getSemanticDataset(semanticId) {
96
+ export function getSemanticDataset(semanticId: string): SemanticDataset | null {
52
97
  if (!semanticId) return null;
53
98
  return SEMANTIC_DATASETS[semanticId] ?? null;
54
99
  }
55
100
 
56
- export function getSemanticMetric(semanticId, metricId) {
101
+ export function getSemanticMetric(semanticId: string, metricId: string): SemanticMetric | null {
57
102
  const ds = getSemanticDataset(semanticId);
58
103
  if (!ds?.metrics) return null;
59
104
  return ds.metrics.find((m) => m.metricId === metricId) ?? null;
60
105
  }
61
-
@@ -3,6 +3,52 @@
3
3
  * Stateless — combine with usePageFilters hook for state management.
4
4
  */
5
5
 
6
+ export type SortDirection = "asc" | "desc";
7
+
8
+ export interface DateRange {
9
+ start?: Date | string;
10
+ end?: Date | string;
11
+ }
12
+
13
+ export type FilterType = "search" | "select" | "toggle" | "dateRange";
14
+
15
+ export interface BaseFilterDefinition {
16
+ id: string;
17
+ type: FilterType;
18
+ defaultValue?: any;
19
+ }
20
+
21
+ export interface SearchFilterDefinition extends BaseFilterDefinition {
22
+ type: "search";
23
+ keys: string[];
24
+ }
25
+
26
+ export interface SelectFilterDefinition extends BaseFilterDefinition {
27
+ type: "select";
28
+ key: string;
29
+ }
30
+
31
+ export interface ToggleFilterDefinition extends BaseFilterDefinition {
32
+ type: "toggle";
33
+ key: string;
34
+ matchValue?: any;
35
+ }
36
+
37
+ export interface DateRangeFilterDefinition extends BaseFilterDefinition {
38
+ type: "dateRange";
39
+ key: string;
40
+ }
41
+
42
+ export type FilterDefinition =
43
+ | SearchFilterDefinition
44
+ | SelectFilterDefinition
45
+ | ToggleFilterDefinition
46
+ | DateRangeFilterDefinition;
47
+
48
+ export interface FilterValues {
49
+ [key: string]: any;
50
+ }
51
+
6
52
  /**
7
53
  * Text search across multiple keys.
8
54
  * @param {Array} data
@@ -10,12 +56,12 @@
10
56
  * @param {string[]} keys — object keys to search within
11
57
  * @returns {Array} filtered data
12
58
  */
13
- export function filterBySearch(data, query, keys = []) {
59
+ export function filterBySearch<T>(data: T[], query: string, keys: string[] = []): T[] {
14
60
  if (!query || !query.trim()) return data;
15
61
  const q = query.trim().toLowerCase();
16
62
  return data.filter((row) =>
17
63
  keys.some((key) => {
18
- const val = row?.[key];
64
+ const val = (row as any)?.[key];
19
65
  return val != null && String(val).toLowerCase().includes(q);
20
66
  })
21
67
  );
@@ -29,10 +75,10 @@ export function filterBySearch(data, query, keys = []) {
29
75
  * @param {*} value — value to match (exact, case-insensitive for strings)
30
76
  * @returns {Array}
31
77
  */
32
- export function filterByValue(data, key, value) {
78
+ export function filterByValue<T>(data: T[], key: string, value: any): T[] {
33
79
  if (value == null || value === "" || value === "all") return data;
34
80
  return data.filter((row) => {
35
- const v = row?.[key];
81
+ const v = (row as any)?.[key];
36
82
  if (typeof v === "string" && typeof value === "string") {
37
83
  return v.toLowerCase() === value.toLowerCase();
38
84
  }
@@ -49,10 +95,10 @@ export function filterByValue(data, key, value) {
49
95
  * @param {*} matchValue — value that key should equal when active (default: truthy check)
50
96
  * @returns {Array}
51
97
  */
52
- export function filterByToggle(data, key, isActive, matchValue) {
98
+ export function filterByToggle<T>(data: T[], key: string, isActive: boolean, matchValue?: any): T[] {
53
99
  if (!isActive) return data;
54
100
  return data.filter((row) => {
55
- const v = row?.[key];
101
+ const v = (row as any)?.[key];
56
102
  if (matchValue !== undefined) return v === matchValue;
57
103
  return Boolean(v);
58
104
  });
@@ -65,14 +111,14 @@ export function filterByToggle(data, key, isActive, matchValue) {
65
111
  * @param {{ start?: Date|string, end?: Date|string }} range
66
112
  * @returns {Array}
67
113
  */
68
- export function filterByDateRange(data, key, range) {
114
+ export function filterByDateRange<T>(data: T[], key: string, range: DateRange | null): T[] {
69
115
  if (!range) return data;
70
116
  const start = range.start ? new Date(range.start) : null;
71
117
  const end = range.end ? new Date(range.end) : null;
72
118
  if (!start && !end) return data;
73
119
 
74
120
  return data.filter((row) => {
75
- const raw = row?.[key];
121
+ const raw = (row as any)?.[key];
76
122
  if (raw == null) return false;
77
123
  const d = raw instanceof Date ? raw : new Date(raw);
78
124
  if (Number.isNaN(d.getTime())) return false;
@@ -89,12 +135,12 @@ export function filterByDateRange(data, key, range) {
89
135
  * @param {"asc"|"desc"} direction
90
136
  * @returns {Array} new sorted array
91
137
  */
92
- export function sortByKey(data, key, direction = "asc") {
138
+ export function sortByKey<T>(data: T[], key: string, direction: SortDirection = "asc"): T[] {
93
139
  if (!key) return data;
94
140
  const dir = direction === "desc" ? -1 : 1;
95
141
  return [...data].sort((a, b) => {
96
- const av = a?.[key];
97
- const bv = b?.[key];
142
+ const av = (a as any)?.[key];
143
+ const bv = (b as any)?.[key];
98
144
  if (av == null && bv == null) return 0;
99
145
  if (av == null) return -1 * dir;
100
146
  if (bv == null) return 1 * dir;
@@ -112,7 +158,7 @@ export function sortByKey(data, key, direction = "asc") {
112
158
  * @param {Object} values — current filter values keyed by filter id
113
159
  * @returns {Array} filtered data
114
160
  */
115
- export function applyFilters(data, filters = [], values = {}) {
161
+ export function applyFilters<T>(data: T[], filters: FilterDefinition[] = [], values: FilterValues = {}): T[] {
116
162
  let result = data;
117
163
 
118
164
  for (const filter of filters) {
@@ -1,6 +1,13 @@
1
1
  import { useMemo } from "react";
2
2
  import { useDataMode } from "./DataModeProvider";
3
3
 
4
+ export type DataSourceValue<T> = T | (() => T);
5
+
6
+ export interface UseDataSourceOptions<T> {
7
+ sample: DataSourceValue<T>;
8
+ live: DataSourceValue<T>;
9
+ }
10
+
4
11
  /**
5
12
  * Select between sample and live data based on the global data mode.
6
13
  *
@@ -23,11 +30,11 @@ import { useDataMode } from "./DataModeProvider";
23
30
  * live: () => computeFromAPI(apiData),
24
31
  * });
25
32
  */
26
- export default function useDataSource({ sample, live }) {
33
+ export default function useDataSource<T>({ sample, live }: UseDataSourceOptions<T>): T {
27
34
  const { mode } = useDataMode();
28
35
 
29
36
  return useMemo(() => {
30
37
  const source = mode === "sample" ? sample : live;
31
- return typeof source === "function" ? source() : source;
38
+ return typeof source === "function" ? (source as () => T)() : source;
32
39
  }, [mode, sample, live]);
33
40
  }