@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.
- package/dist/components/library/cards/ActionList.d.ts +10 -10
- package/dist/components/library/cards/ActionList.js +2 -3
- package/dist/components/library/cards/ActionList.js.map +1 -1
- package/dist/components/library/cards/ActivityCard.d.ts +18 -5
- package/dist/components/library/cards/ActivityCard.js +3 -4
- package/dist/components/library/cards/ActivityCard.js.map +1 -1
- package/dist/components/library/cards/BaseCard.d.ts +30 -24
- package/dist/components/library/cards/BaseCard.js +2 -3
- package/dist/components/library/cards/BaseCard.js.map +1 -1
- package/dist/components/library/cards/CalloutCard.d.ts +11 -9
- package/dist/components/library/cards/CalloutCard.js +2 -3
- package/dist/components/library/cards/CalloutCard.js.map +1 -1
- package/dist/components/library/cards/ChartCard.d.ts +29 -17
- package/dist/components/library/cards/ChartCard.js +13 -14
- package/dist/components/library/cards/ChartCard.js.map +1 -1
- package/dist/components/library/cards/FeedPanel.d.ts +12 -11
- package/dist/components/library/cards/FeedPanel.js +3 -4
- package/dist/components/library/cards/FeedPanel.js.map +1 -1
- package/dist/components/library/cards/ListCard.d.ts +33 -20
- package/dist/components/library/cards/ListCard.js +35 -35
- package/dist/components/library/cards/ListCard.js.map +1 -1
- package/dist/components/library/cards/MetricCard.d.ts +23 -17
- package/dist/components/library/cards/MetricCard.js +10 -11
- package/dist/components/library/cards/MetricCard.js.map +1 -1
- package/dist/components/library/cards/MetricsStrip.d.ts +11 -11
- package/dist/components/library/cards/MetricsStrip.js +1 -1
- package/dist/components/library/cards/MetricsStrip.js.map +1 -1
- package/dist/components/library/cards/SectionCard.d.ts +17 -12
- package/dist/components/library/cards/SectionCard.js +18 -19
- package/dist/components/library/cards/SectionCard.js.map +1 -1
- package/dist/components/library/cards/SemanticMetricCard.d.ts +15 -20
- package/dist/components/library/cards/SemanticMetricCardWithLoading.d.ts +8 -7
- package/dist/components/library/cards/SemanticTableCard.d.ts +13 -18
- package/dist/components/library/cards/SemanticTableCardWithLoading.d.ts +8 -7
- package/dist/components/library/cards/StatusCard.d.ts +29 -15
- package/dist/components/library/cards/StatusCard.js +16 -17
- package/dist/components/library/cards/StatusCard.js.map +1 -1
- package/dist/components/library/cards/TableCard.d.ts +40 -23
- package/dist/components/library/cards/TableCard.js +59 -59
- package/dist/components/library/cards/TableCard.js.map +1 -1
- package/dist/components/library/cards/WidgetCard.d.ts +19 -11
- package/dist/components/library/cards/WidgetCard.js.map +1 -1
- package/dist/components/library/charts/D3Chart.d.ts +23 -16
- package/dist/components/library/charts/D3Chart.js.map +1 -1
- package/dist/components/library/charts/D3ChartTemplates.d.ts +33 -3
- package/dist/components/library/charts/D3ChartTemplates.js +7 -7
- package/dist/components/library/charts/D3ChartTemplates.js.map +1 -1
- package/dist/components/library/charts/GeoMap.d.ts +81 -18
- package/dist/components/library/charts/GeoMap.js +28 -26
- package/dist/components/library/charts/GeoMap.js.map +1 -1
- package/dist/components/library/chat/ChatBar.d.ts +14 -11
- package/dist/components/library/chat/ChatBar.js +2 -3
- package/dist/components/library/chat/ChatBar.js.map +1 -1
- package/dist/components/library/chat/ChatInput.d.ts +9 -8
- package/dist/components/library/chat/ChatInput.js.map +1 -1
- package/dist/components/library/chat/ChatMessage.d.ts +17 -4
- package/dist/components/library/chat/ChatMessage.js.map +1 -1
- package/dist/components/library/chat/ChatMessageList.d.ts +11 -8
- package/dist/components/library/chat/ChatMessageList.js.map +1 -1
- package/dist/components/library/chat/ChatPanel.d.ts +16 -12
- package/dist/components/library/chat/ChatPanel.js +8 -9
- package/dist/components/library/chat/ChatPanel.js.map +1 -1
- package/dist/components/library/chat/ChatSuggestions.d.ts +5 -4
- package/dist/components/library/chat/ChatSuggestions.js +2 -3
- package/dist/components/library/chat/ChatSuggestions.js.map +1 -1
- package/dist/components/library/chat/ChatToolCall.d.ts +11 -3
- package/dist/components/library/chat/ChatToolCall.js.map +1 -1
- package/dist/components/library/chat/ChatTypingIndicator.d.ts +4 -3
- package/dist/components/library/chat/ChatTypingIndicator.js +2 -3
- package/dist/components/library/chat/ChatTypingIndicator.js.map +1 -1
- package/dist/components/library/chat/ChatWelcome.d.ts +9 -7
- package/dist/components/library/chat/ChatWelcome.js +6 -7
- package/dist/components/library/chat/ChatWelcome.js.map +1 -1
- package/dist/components/library/chat/index.d.ts +10 -0
- package/dist/components/library/chat/useChatState.d.ts +36 -11
- package/dist/components/library/chat/useChatState.js +63 -46
- package/dist/components/library/chat/useChatState.js.map +1 -1
- package/dist/components/library/data/DataModeProvider.d.ts +15 -11
- package/dist/components/library/data/DataModeProvider.js +1 -1
- package/dist/components/library/data/DataModeProvider.js.map +1 -1
- package/dist/components/library/data/DataModeToggle.d.ts +4 -3
- package/dist/components/library/data/DataModeToggle.js +4 -5
- package/dist/components/library/data/DataModeToggle.js.map +1 -1
- package/dist/components/library/data/chartDataProvider.d.ts +41 -3
- package/dist/components/library/data/filterUtils.d.ts +38 -9
- package/dist/components/library/data/filterUtils.js.map +1 -1
- package/dist/components/library/data/useDataSource.d.ts +6 -4
- package/dist/components/library/data/useDataSource.js.map +1 -1
- package/dist/components/library/data/usePageFilters.d.ts +31 -5
- package/dist/components/library/data/usePageFilters.js +6 -2
- package/dist/components/library/data/usePageFilters.js.map +1 -1
- package/dist/components/library/filters/FilterBar.d.ts +18 -8
- package/dist/components/library/filters/FilterBar.js +2 -3
- package/dist/components/library/filters/FilterBar.js.map +1 -1
- package/dist/components/library/filters/SearchFilter.d.ts +7 -6
- package/dist/components/library/filters/SearchFilter.js +2 -3
- package/dist/components/library/filters/SearchFilter.js.map +1 -1
- package/dist/components/library/filters/SelectFilter.d.ts +13 -7
- package/dist/components/library/filters/SelectFilter.js +2 -3
- package/dist/components/library/filters/SelectFilter.js.map +1 -1
- package/dist/components/library/filters/ToggleFilter.d.ts +7 -5
- package/dist/components/library/filters/ToggleFilter.js +2 -3
- package/dist/components/library/filters/ToggleFilter.js.map +1 -1
- package/dist/components/library/forms/FormField.d.ts +10 -8
- package/dist/components/library/forms/FormField.js +3 -4
- package/dist/components/library/forms/FormField.js.map +1 -1
- package/dist/components/library/forms/FormModal.d.ts +23 -14
- package/dist/components/library/forms/FormModal.js.map +1 -1
- package/dist/components/library/forms/FormRenderer.d.ts +29 -9
- package/dist/components/library/forms/FormRenderer.js +6 -7
- package/dist/components/library/forms/FormRenderer.js.map +1 -1
- package/dist/components/library/forms/FormSection.d.ts +10 -8
- package/dist/components/library/forms/FormSection.js +2 -3
- package/dist/components/library/forms/FormSection.js.map +1 -1
- package/dist/components/library/forms/index.d.ts +5 -0
- package/dist/components/library/forms/useFormState.d.ts +23 -15
- package/dist/components/library/forms/useFormState.js +53 -47
- package/dist/components/library/forms/useFormState.js.map +1 -1
- package/dist/components/library/index.d.ts +92 -73
- package/dist/components/library/index.js +25 -25
- package/dist/components/library/index.js.map +1 -1
- package/dist/components/library/layout/PageContainer.d.ts +6 -4
- package/dist/components/library/layout/PageContainer.js +4 -5
- package/dist/components/library/layout/PageContainer.js.map +1 -1
- package/dist/components/library/skeletons/CardSkeleton.d.ts +5 -4
- package/dist/components/library/skeletons/CardSkeleton.js +2 -3
- package/dist/components/library/skeletons/CardSkeleton.js.map +1 -1
- package/dist/components/library/theme/AppThemeProvider.d.ts +13 -50
- package/dist/components/library/theme/AppThemeProvider.js.map +1 -1
- package/dist/components/library/theme/tokens.d.ts +45 -44
- package/dist/components/library/theme/tokens.js.map +1 -1
- package/package.json +4 -1
- package/src/components/library/cards/{ActionList.jsx → ActionList.tsx} +13 -9
- package/src/components/library/cards/{ActivityCard.jsx → ActivityCard.tsx} +33 -4
- package/src/components/library/cards/{BaseCard.jsx → BaseCard.tsx} +33 -6
- package/src/components/library/cards/{CalloutCard.jsx → CalloutCard.tsx} +12 -10
- package/src/components/library/cards/{ChartCard.jsx → ChartCard.tsx} +32 -6
- package/src/components/library/cards/{FeedPanel.jsx → FeedPanel.tsx} +13 -2
- package/src/components/library/cards/{ListCard.jsx → ListCard.tsx} +43 -7
- package/src/components/library/cards/{MetricCard.jsx → MetricCard.tsx} +25 -6
- package/src/components/library/cards/{MetricsStrip.jsx → MetricsStrip.tsx} +22 -12
- package/src/components/library/cards/{SectionCard.jsx → SectionCard.tsx} +27 -8
- package/src/components/library/cards/{SemanticMetricCard.jsx → SemanticMetricCard.tsx} +18 -6
- package/src/components/library/cards/{SemanticMetricCardWithLoading.jsx → SemanticMetricCardWithLoading.tsx} +9 -3
- package/src/components/library/cards/{SemanticTableCard.jsx → SemanticTableCard.tsx} +16 -5
- package/src/components/library/cards/{SemanticTableCardWithLoading.jsx → SemanticTableCardWithLoading.tsx} +9 -5
- package/src/components/library/cards/{StatusCard.jsx → StatusCard.tsx} +61 -12
- package/src/components/library/cards/{TableCard.jsx → TableCard.tsx} +51 -12
- package/src/components/library/cards/{WidgetCard.jsx → WidgetCard.tsx} +28 -5
- package/src/components/library/charts/{D3Chart.jsx → D3Chart.tsx} +27 -7
- package/src/components/library/charts/{D3ChartTemplates.jsx → D3ChartTemplates.tsx} +60 -28
- package/src/components/library/charts/{GeoMap.jsx → GeoMap.tsx} +106 -17
- package/src/components/library/chat/{ChatBar.jsx → ChatBar.tsx} +19 -8
- package/src/components/library/chat/{ChatInput.jsx → ChatInput.tsx} +13 -11
- package/src/components/library/chat/{ChatMessage.jsx → ChatMessage.tsx} +22 -9
- package/src/components/library/chat/{ChatMessageList.jsx → ChatMessageList.tsx} +13 -11
- package/src/components/library/chat/{ChatPanel.jsx → ChatPanel.tsx} +16 -13
- package/src/components/library/chat/{ChatSuggestions.jsx → ChatSuggestions.tsx} +6 -5
- package/src/components/library/chat/{ChatToolCall.jsx → ChatToolCall.tsx} +14 -4
- package/src/components/library/chat/{ChatTypingIndicator.jsx → ChatTypingIndicator.tsx} +5 -2
- package/src/components/library/chat/{ChatWelcome.jsx → ChatWelcome.tsx} +9 -7
- package/src/components/library/chat/index.tsx +26 -0
- package/src/components/library/chat/useChatState.tsx +181 -0
- package/src/components/library/data/{DataModeProvider.jsx → DataModeProvider.tsx} +25 -8
- package/src/components/library/data/{DataModeToggle.jsx → DataModeToggle.tsx} +5 -2
- package/src/components/library/data/{chartDataProvider.jsx → chartDataProvider.tsx} +49 -5
- package/src/components/library/data/{filterUtils.jsx → filterUtils.tsx} +58 -12
- package/src/components/library/data/{useDataSource.jsx → useDataSource.tsx} +9 -2
- package/src/components/library/data/{usePageFilters.jsx → usePageFilters.tsx} +49 -9
- package/src/components/library/filters/{FilterBar.jsx → FilterBar.tsx} +21 -11
- package/src/components/library/filters/{SearchFilter.jsx → SearchFilter.tsx} +8 -2
- package/src/components/library/filters/{SelectFilter.jsx → SelectFilter.tsx} +15 -8
- package/src/components/library/filters/{ToggleFilter.jsx → ToggleFilter.tsx} +7 -6
- package/src/components/library/forms/{FormField.jsx → FormField.tsx} +91 -45
- package/src/components/library/forms/{FormModal.jsx → FormModal.tsx} +21 -20
- package/src/components/library/forms/{FormRenderer.jsx → FormRenderer.tsx} +32 -10
- package/src/components/library/forms/{FormSection.jsx → FormSection.tsx} +13 -7
- package/src/components/library/forms/index.tsx +11 -0
- package/src/components/library/forms/{useFormState.jsx → useFormState.tsx} +43 -23
- package/src/components/library/{index.jsx → index.ts} +14 -14
- package/src/components/library/layout/{PageContainer.jsx → PageContainer.tsx} +6 -3
- package/src/components/library/skeletons/{CardSkeleton.jsx → CardSkeleton.tsx} +5 -4
- package/src/components/library/theme/{AppThemeProvider.jsx → AppThemeProvider.tsx} +20 -7
- package/src/components/library/theme/{tokens.jsx → tokens.tsx} +37 -3
- package/src/components/library/chat/index.jsx +0 -10
- package/src/components/library/chat/useChatState.jsx +0 -130
- package/src/components/library/forms/index.jsx +0 -5
- /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
|
|
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
|
-
|
|
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))
|
|
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
|
-
|
|
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
|
}
|