@datalayer/core 0.0.15 → 0.0.17
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/lib/components/chat/ChatComponent.d.ts +4 -0
- package/lib/components/chat/ChatComponent.js +139 -0
- package/lib/components/chat/MessagePart.d.ts +11 -0
- package/lib/components/chat/MessagePart.js +23 -0
- package/lib/components/chat/display/DynamicToolPart.d.ts +6 -0
- package/lib/components/chat/display/DynamicToolPart.js +5 -0
- package/lib/components/chat/display/ReasoningPart.d.ts +6 -0
- package/lib/components/chat/display/ReasoningPart.js +54 -0
- package/lib/components/chat/display/TextPart.d.ts +9 -0
- package/lib/components/chat/display/TextPart.js +93 -0
- package/lib/components/chat/display/ToolPart.d.ts +6 -0
- package/lib/components/chat/display/ToolPart.js +144 -0
- package/lib/components/chat/display/index.d.ts +4 -0
- package/lib/components/chat/display/index.js +9 -0
- package/lib/components/chat/handler.d.ts +8 -0
- package/lib/components/chat/handler.js +39 -0
- package/lib/components/chat/index.d.ts +4 -0
- package/lib/components/chat/index.js +9 -0
- package/lib/components/index.d.ts +1 -1
- package/lib/components/index.js +1 -1
- package/lib/components/runtimes/RuntimeSimplePicker.d.ts +4 -0
- package/lib/components/runtimes/RuntimeSimplePicker.js +3 -3
- package/lib/examples/ChatExample.d.ts +8 -0
- package/lib/examples/ChatExample.js +51 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/hooks/useAIJupyterChat.d.ts +36 -0
- package/lib/hooks/useAIJupyterChat.js +49 -0
- package/lib/hooks/useCache.js +105 -45
- package/lib/hooks/useMobile.d.ts +1 -0
- package/lib/hooks/useMobile.js +22 -0
- package/lib/hooks/useUpload.js +29 -21
- package/lib/index.d.ts +1 -0
- package/lib/index.js +8 -4
- package/lib/stateful/index.d.ts +0 -1
- package/lib/stateful/index.js +0 -1
- package/lib/stateful/runtimes/actions.d.ts +1 -1
- package/lib/stateful/runtimes/actions.js +1 -1
- package/lib/theme/DatalayerTheme.d.ts +2 -2
- package/lib/theme/DatalayerTheme.js +4 -4
- package/lib/theme/DatalayerThemeProvider.js +2 -2
- package/lib/types.d.ts +5 -0
- package/lib/types.js +6 -0
- package/package.json +16 -2
- package/style/base.css +4 -0
- package/lib/sdk/index.d.ts +0 -27
- package/lib/sdk/index.js +0 -33
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2024-2025 Datalayer, Inc.
|
|
4
|
+
*
|
|
5
|
+
* BSD 3-Clause License
|
|
6
|
+
*/
|
|
7
|
+
import { useState, useEffect, useMemo, useRef } from 'react';
|
|
8
|
+
import { useQuery } from '@tanstack/react-query';
|
|
9
|
+
import { Box, Button, IconButton, Textarea, FormControl, Spinner, ActionMenu, ActionList, Text, } from '@primer/react';
|
|
10
|
+
import { ToolsIcon, ArrowUpIcon, ArrowDownIcon, AiModelIcon, } from '@primer/octicons-react';
|
|
11
|
+
import { useAIJupyterChat } from '../../hooks/useAIJupyterChat';
|
|
12
|
+
import { requestAPI } from './handler';
|
|
13
|
+
import { MessagePart } from './MessagePart';
|
|
14
|
+
async function getModels() {
|
|
15
|
+
return await requestAPI('configure');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Main Chat component using Primer React
|
|
19
|
+
*/
|
|
20
|
+
export const ChatComponent = () => {
|
|
21
|
+
const [model, setModel] = useState('');
|
|
22
|
+
const [enabledTools, setEnabledTools] = useState([]);
|
|
23
|
+
const [inputValue, setInputValue] = useState('');
|
|
24
|
+
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
25
|
+
const { messages, sendMessage, status, regenerate } = useAIJupyterChat();
|
|
26
|
+
const textareaRef = useRef(null);
|
|
27
|
+
const messagesEndRef = useRef(null);
|
|
28
|
+
const scrollContainerRef = useRef(null);
|
|
29
|
+
const configQuery = useQuery({
|
|
30
|
+
queryFn: getModels,
|
|
31
|
+
queryKey: ['models'],
|
|
32
|
+
});
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (configQuery.data) {
|
|
35
|
+
setModel(configQuery.data.models[0].id);
|
|
36
|
+
// Enable all builtin tools by default
|
|
37
|
+
const allToolIds = configQuery.data.builtinTools?.map(tool => tool.id) || [];
|
|
38
|
+
setEnabledTools(allToolIds);
|
|
39
|
+
}
|
|
40
|
+
}, [configQuery.data]);
|
|
41
|
+
// Auto-scroll to bottom when new messages arrive
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
44
|
+
}, [messages]);
|
|
45
|
+
// Handle scroll to show/hide scroll button
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const container = scrollContainerRef.current;
|
|
48
|
+
if (!container)
|
|
49
|
+
return;
|
|
50
|
+
const handleScroll = () => {
|
|
51
|
+
const isScrolledUp = container.scrollHeight - container.scrollTop - container.clientHeight >
|
|
52
|
+
100;
|
|
53
|
+
setShowScrollButton(isScrolledUp);
|
|
54
|
+
};
|
|
55
|
+
container.addEventListener('scroll', handleScroll);
|
|
56
|
+
return () => container.removeEventListener('scroll', handleScroll);
|
|
57
|
+
}, []);
|
|
58
|
+
const handleSubmit = (e) => {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
if (inputValue.trim() && status !== 'streaming') {
|
|
61
|
+
sendMessage({ text: inputValue }, {
|
|
62
|
+
body: { model, builtinTools: enabledTools },
|
|
63
|
+
}).catch((error) => {
|
|
64
|
+
console.error('Error sending message:', error);
|
|
65
|
+
});
|
|
66
|
+
setInputValue('');
|
|
67
|
+
textareaRef.current?.focus();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const handleKeyDown = (e) => {
|
|
71
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
handleSubmit(e);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const regen = (id) => {
|
|
77
|
+
regenerate({ messageId: id }).catch((error) => {
|
|
78
|
+
console.error('Error regenerating message:', error);
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
const scrollToBottom = () => {
|
|
82
|
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
83
|
+
};
|
|
84
|
+
const availableTools = useMemo(() => {
|
|
85
|
+
if (!configQuery.data)
|
|
86
|
+
return [];
|
|
87
|
+
const selectedModel = configQuery.data.models.find(entry => entry.id === model);
|
|
88
|
+
const enabledToolIds = selectedModel?.builtinTools ?? [];
|
|
89
|
+
// If model doesn't specify tools, show all builtin tools
|
|
90
|
+
const tools = enabledToolIds.length > 0
|
|
91
|
+
? (configQuery.data.builtinTools?.filter(tool => enabledToolIds.includes(tool.id)) ?? [])
|
|
92
|
+
: (configQuery.data.builtinTools ?? []);
|
|
93
|
+
return tools;
|
|
94
|
+
}, [configQuery.data, model]);
|
|
95
|
+
return (_jsxs(Box, { className: "dla-chat-root", sx: {
|
|
96
|
+
position: 'absolute',
|
|
97
|
+
top: 0,
|
|
98
|
+
left: 0,
|
|
99
|
+
right: 0,
|
|
100
|
+
bottom: 0,
|
|
101
|
+
display: 'flex',
|
|
102
|
+
flexDirection: 'column',
|
|
103
|
+
}, children: [_jsxs(Box, { ref: scrollContainerRef, className: "dla-chat-messages", sx: {
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
top: 0,
|
|
106
|
+
left: 0,
|
|
107
|
+
right: 0,
|
|
108
|
+
bottom: 0,
|
|
109
|
+
overflowY: 'scroll',
|
|
110
|
+
overflowX: 'hidden',
|
|
111
|
+
padding: 3,
|
|
112
|
+
paddingBottom: '180px',
|
|
113
|
+
}, children: [messages.map(message => (_jsx(Box, { sx: { marginBottom: 3 }, children: message.parts.map((part, index) => (_jsx(MessagePart, { part: part, message: message, status: status, index: index, regen: regen, lastMessage: message.id === messages.at(-1)?.id }, `${message.id}-${index}`))) }, message.id))), status === 'submitted' && (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', padding: 3 }, children: _jsx(Spinner, { size: "medium" }) })), _jsx("div", { ref: messagesEndRef })] }), showScrollButton && (_jsx(Box, { sx: {
|
|
114
|
+
position: 'absolute',
|
|
115
|
+
bottom: 160,
|
|
116
|
+
right: 16,
|
|
117
|
+
zIndex: 1,
|
|
118
|
+
}, children: _jsx(IconButton, { icon: ArrowDownIcon, "aria-label": "Scroll to bottom", onClick: scrollToBottom, variant: "default", size: "medium" }) })), _jsx(Box, { sx: {
|
|
119
|
+
position: 'absolute',
|
|
120
|
+
bottom: 0,
|
|
121
|
+
left: 0,
|
|
122
|
+
right: 0,
|
|
123
|
+
borderTop: '1px solid',
|
|
124
|
+
borderColor: 'border.default',
|
|
125
|
+
padding: 3,
|
|
126
|
+
backgroundColor: 'canvas.default',
|
|
127
|
+
zIndex: 0,
|
|
128
|
+
}, children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(FormControl, { sx: { width: '100%' }, children: _jsx(Textarea, { ref: textareaRef, value: inputValue, onChange: e => setInputValue(e.target.value), onKeyDown: handleKeyDown, placeholder: "Ask me anything...", rows: 3, resize: "vertical", sx: { marginBottom: 2, width: '100%' } }) }), _jsxs(Box, { sx: {
|
|
129
|
+
display: 'flex',
|
|
130
|
+
justifyContent: 'space-between',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [availableTools.length > 0 && (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(IconButton, { icon: ToolsIcon, "aria-label": "Tools", variant: "invisible", size: "small" }) }), _jsx(ActionMenu.Overlay, { children: _jsx(ActionList, { children: _jsx(ActionList.Group, { title: "Available Tools", children: availableTools.map(tool => (_jsxs(ActionList.Item, { disabled: true, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(Box, { sx: {
|
|
133
|
+
width: 8,
|
|
134
|
+
height: 8,
|
|
135
|
+
borderRadius: '50%',
|
|
136
|
+
backgroundColor: 'success.emphasis',
|
|
137
|
+
} }) }), tool.name] }, tool.id))) }) }) })] })), configQuery.data && model && (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Button, { type: "button", variant: "invisible", size: "small", leadingVisual: AiModelIcon, children: _jsx(Text, { sx: { fontSize: 0 }, children: configQuery.data.models.find(m => m.id === model)
|
|
138
|
+
?.name || 'Select Model' }) }) }), _jsx(ActionMenu.Overlay, { children: _jsx(ActionList, { selectionVariant: "single", children: configQuery.data.models.map(modelItem => (_jsx(ActionList.Item, { selected: model === modelItem.id, onSelect: () => setModel(modelItem.id), children: modelItem.name }, modelItem.id))) }) })] }))] }), _jsx(Button, { type: "submit", variant: "primary", size: "small", disabled: !inputValue.trim() || status === 'streaming', leadingVisual: ArrowUpIcon, children: status === 'streaming' ? 'Sending...' : 'Send' })] })] }) })] }));
|
|
139
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { UIDataTypes, UIMessagePart, UITools, UIMessage } from 'ai';
|
|
2
|
+
interface IMessagePartProps {
|
|
3
|
+
part: UIMessagePart<UIDataTypes, UITools>;
|
|
4
|
+
message: UIMessage;
|
|
5
|
+
status: string;
|
|
6
|
+
regen: (id: string) => void;
|
|
7
|
+
index: number;
|
|
8
|
+
lastMessage: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function MessagePart({ part, message, status, regen, index, lastMessage, }: IMessagePartProps): import("react/jsx-runtime").JSX.Element | null;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { TextPart } from './display/TextPart';
|
|
3
|
+
import { ReasoningPart } from './display/ReasoningPart';
|
|
4
|
+
import { ToolPart } from './display/ToolPart';
|
|
5
|
+
import { DynamicToolPart } from './display/DynamicToolPart';
|
|
6
|
+
export function MessagePart({ part, message, status, regen, index, lastMessage, }) {
|
|
7
|
+
if (part.type === 'text') {
|
|
8
|
+
return (_jsx(TextPart, { text: part.text, message: message, isLastPart: index === message.parts.length - 1, onRegenerate: regen }));
|
|
9
|
+
}
|
|
10
|
+
else if (part.type === 'reasoning') {
|
|
11
|
+
const isStreaming = status === 'streaming' &&
|
|
12
|
+
index === message.parts.length - 1 &&
|
|
13
|
+
lastMessage;
|
|
14
|
+
return _jsx(ReasoningPart, { text: part.text, isStreaming: isStreaming });
|
|
15
|
+
}
|
|
16
|
+
else if (part.type === 'dynamic-tool') {
|
|
17
|
+
return _jsx(DynamicToolPart, { part: part });
|
|
18
|
+
}
|
|
19
|
+
else if ('toolCallId' in part) {
|
|
20
|
+
return _jsx(ToolPart, { part: part });
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text, Flash } from '@primer/react';
|
|
3
|
+
export function DynamicToolPart({ part }) {
|
|
4
|
+
return (_jsx(Flash, { variant: "warning", sx: { marginBottom: 2 }, children: _jsxs(Text, { children: ["Dynamic Tool: ", JSON.stringify(part)] }) }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2024-2025 Datalayer, Inc.
|
|
4
|
+
*
|
|
5
|
+
* BSD 3-Clause License
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { Text, Button } from '@primer/react';
|
|
9
|
+
import { Box } from '@datalayer/primer-addons';
|
|
10
|
+
import { ChevronDownIcon } from '@primer/octicons-react';
|
|
11
|
+
import { Streamdown } from 'streamdown';
|
|
12
|
+
export function ReasoningPart({ text, isStreaming }) {
|
|
13
|
+
const [isExpanded, setIsExpanded] = React.useState(false);
|
|
14
|
+
// Auto-close after streaming ends (with delay)
|
|
15
|
+
React.useEffect(() => {
|
|
16
|
+
if (!isStreaming && isExpanded) {
|
|
17
|
+
const timer = setTimeout(() => {
|
|
18
|
+
setIsExpanded(false);
|
|
19
|
+
}, 1000);
|
|
20
|
+
return () => clearTimeout(timer);
|
|
21
|
+
}
|
|
22
|
+
}, [isStreaming, isExpanded]);
|
|
23
|
+
return (_jsxs(Box, { sx: { marginBottom: 3 }, children: [_jsxs(Button, { variant: "invisible", size: "small", onClick: () => setIsExpanded(!isExpanded), sx: {
|
|
24
|
+
width: '100%',
|
|
25
|
+
display: 'flex',
|
|
26
|
+
gap: 2,
|
|
27
|
+
justifyContent: 'flex-start',
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
paddingX: 0,
|
|
30
|
+
paddingY: 1,
|
|
31
|
+
color: 'fg.muted',
|
|
32
|
+
border: 'none',
|
|
33
|
+
'&:hover': {
|
|
34
|
+
color: 'fg.default',
|
|
35
|
+
},
|
|
36
|
+
}, children: [_jsx(Text, { sx: { fontSize: 1 }, children: "\uD83E\uDDE0" }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'normal' }, children: isStreaming ? 'Thinking...' : 'Reasoning' }), _jsx(Box, { as: "span", sx: {
|
|
37
|
+
display: 'inline-flex',
|
|
38
|
+
marginLeft: 'auto',
|
|
39
|
+
transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)',
|
|
40
|
+
transition: 'transform 0.2s',
|
|
41
|
+
}, children: _jsx(ChevronDownIcon, {}) })] }), isExpanded && (_jsx(Box, { sx: {
|
|
42
|
+
marginTop: 2,
|
|
43
|
+
padding: 3,
|
|
44
|
+
backgroundColor: 'canvas.inset',
|
|
45
|
+
borderRadius: 2,
|
|
46
|
+
border: '1px solid',
|
|
47
|
+
borderColor: 'border.default',
|
|
48
|
+
fontSize: 1,
|
|
49
|
+
lineHeight: 1.6,
|
|
50
|
+
color: 'fg.muted',
|
|
51
|
+
'& > *:first-child': { marginTop: 0 },
|
|
52
|
+
'& > *:last-child': { marginBottom: 0 },
|
|
53
|
+
}, children: _jsx(Streamdown, { children: text }) }))] }));
|
|
54
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UIMessage } from 'ai';
|
|
2
|
+
interface ITextPartProps {
|
|
3
|
+
text: string;
|
|
4
|
+
message: UIMessage;
|
|
5
|
+
isLastPart: boolean;
|
|
6
|
+
onRegenerate: (id: string) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function TextPart({ text, message, isLastPart, onRegenerate, }: ITextPartProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text, IconButton } from '@primer/react';
|
|
3
|
+
import { Box } from '@datalayer/primer-addons';
|
|
4
|
+
import { CopyIcon, SyncIcon } from '@primer/octicons-react';
|
|
5
|
+
import { Streamdown } from 'streamdown';
|
|
6
|
+
export function TextPart({ text, message, isLastPart, onRegenerate, }) {
|
|
7
|
+
const copy = (text) => {
|
|
8
|
+
navigator.clipboard.writeText(text).catch((error) => {
|
|
9
|
+
console.error('Error copying text:', error);
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
return (_jsxs(Box, { sx: {
|
|
13
|
+
padding: 3,
|
|
14
|
+
borderRadius: 2,
|
|
15
|
+
backgroundColor: message.role === 'user' ? 'accent.subtle' : 'canvas.subtle',
|
|
16
|
+
marginBottom: 2,
|
|
17
|
+
}, children: [_jsxs(Box, { sx: {
|
|
18
|
+
display: 'flex',
|
|
19
|
+
justifyContent: 'space-between',
|
|
20
|
+
alignItems: 'flex-start',
|
|
21
|
+
marginBottom: 2,
|
|
22
|
+
}, children: [_jsx(Text, { sx: {
|
|
23
|
+
fontWeight: 'bold',
|
|
24
|
+
fontSize: 1,
|
|
25
|
+
color: 'fg.muted',
|
|
26
|
+
textTransform: 'uppercase',
|
|
27
|
+
}, children: message.role === 'user' ? 'You' : 'Assistant' }), message.role === 'assistant' && isLastPart && (_jsxs(Box, { sx: { display: 'flex', gap: 1 }, children: [_jsx(IconButton, { icon: SyncIcon, "aria-label": "Regenerate", size: "small", variant: "invisible", onClick: () => onRegenerate(message.id) }), _jsx(IconButton, { icon: CopyIcon, "aria-label": "Copy", size: "small", variant: "invisible", onClick: () => copy(text) })] }))] }), _jsx(Box, { sx: {
|
|
28
|
+
fontSize: 1,
|
|
29
|
+
lineHeight: 1.6,
|
|
30
|
+
'& > *:first-child': { marginTop: 0 },
|
|
31
|
+
'& > *:last-child': { marginBottom: 0 },
|
|
32
|
+
'& p': { marginTop: 0, marginBottom: '1em' },
|
|
33
|
+
'& h1, & h2, & h3, & h4, & h5, & h6': {
|
|
34
|
+
marginTop: '1em',
|
|
35
|
+
marginBottom: '0.5em',
|
|
36
|
+
fontWeight: 'bold',
|
|
37
|
+
},
|
|
38
|
+
'& ul, & ol': {
|
|
39
|
+
marginTop: '0.5em',
|
|
40
|
+
marginBottom: '0.5em',
|
|
41
|
+
paddingLeft: '1.5em',
|
|
42
|
+
},
|
|
43
|
+
'& code': {
|
|
44
|
+
backgroundColor: 'neutral.muted',
|
|
45
|
+
padding: '2px 4px',
|
|
46
|
+
borderRadius: 1,
|
|
47
|
+
fontSize: '0.9em',
|
|
48
|
+
fontFamily: 'mono',
|
|
49
|
+
},
|
|
50
|
+
'& pre': {
|
|
51
|
+
backgroundColor: 'canvas.inset',
|
|
52
|
+
padding: 3,
|
|
53
|
+
borderRadius: 2,
|
|
54
|
+
overflow: 'auto',
|
|
55
|
+
marginTop: '1em',
|
|
56
|
+
marginBottom: '1em',
|
|
57
|
+
border: '1px solid',
|
|
58
|
+
borderColor: 'border.default',
|
|
59
|
+
},
|
|
60
|
+
'& pre code': {
|
|
61
|
+
backgroundColor: 'transparent',
|
|
62
|
+
padding: 0,
|
|
63
|
+
fontSize: '0.875em',
|
|
64
|
+
},
|
|
65
|
+
'& blockquote': {
|
|
66
|
+
borderLeft: '3px solid',
|
|
67
|
+
borderColor: 'border.default',
|
|
68
|
+
paddingLeft: 3,
|
|
69
|
+
marginLeft: 0,
|
|
70
|
+
color: 'fg.muted',
|
|
71
|
+
},
|
|
72
|
+
'& a': {
|
|
73
|
+
color: 'accent.fg',
|
|
74
|
+
textDecoration: 'underline',
|
|
75
|
+
},
|
|
76
|
+
'& table': {
|
|
77
|
+
width: '100%',
|
|
78
|
+
borderCollapse: 'collapse',
|
|
79
|
+
marginTop: '1em',
|
|
80
|
+
marginBottom: '1em',
|
|
81
|
+
},
|
|
82
|
+
'& th, & td': {
|
|
83
|
+
border: '1px solid',
|
|
84
|
+
borderColor: 'border.default',
|
|
85
|
+
padding: 2,
|
|
86
|
+
textAlign: 'left',
|
|
87
|
+
},
|
|
88
|
+
'& th': {
|
|
89
|
+
backgroundColor: 'canvas.subtle',
|
|
90
|
+
fontWeight: 'bold',
|
|
91
|
+
},
|
|
92
|
+
}, children: _jsx(Streamdown, { children: text }) })] }));
|
|
93
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2024-2025 Datalayer, Inc.
|
|
4
|
+
*
|
|
5
|
+
* BSD 3-Clause License
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { Text, Button } from '@primer/react';
|
|
9
|
+
import { Box } from '@datalayer/primer-addons';
|
|
10
|
+
import { ChevronDownIcon } from '@primer/octicons-react';
|
|
11
|
+
export function ToolPart({ part }) {
|
|
12
|
+
const [isExpanded, setIsExpanded] = React.useState(true);
|
|
13
|
+
const getStatusInfo = (state) => {
|
|
14
|
+
const statusMap = {
|
|
15
|
+
call: { label: 'Pending', color: 'accent.fg', icon: '○' },
|
|
16
|
+
'input-streaming': { label: 'Pending', color: 'accent.fg', icon: '○' },
|
|
17
|
+
'input-available': {
|
|
18
|
+
label: 'Running',
|
|
19
|
+
color: 'attention.fg',
|
|
20
|
+
icon: '⏱',
|
|
21
|
+
},
|
|
22
|
+
executing: { label: 'Running', color: 'attention.fg', icon: '⏱' },
|
|
23
|
+
'output-available': {
|
|
24
|
+
label: 'Completed',
|
|
25
|
+
color: 'success.fg',
|
|
26
|
+
icon: '✓',
|
|
27
|
+
},
|
|
28
|
+
'output-error': { label: 'Error', color: 'danger.fg', icon: '✕' },
|
|
29
|
+
error: { label: 'Error', color: 'danger.fg', icon: '✕' },
|
|
30
|
+
};
|
|
31
|
+
return (statusMap[state] || {
|
|
32
|
+
label: state,
|
|
33
|
+
color: 'fg.muted',
|
|
34
|
+
icon: '•',
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const statusInfo = getStatusInfo(part.state);
|
|
38
|
+
const toolName = part.type.split('-').slice(1).join('-') || part.type;
|
|
39
|
+
return (_jsxs(Box, { sx: {
|
|
40
|
+
marginBottom: 2,
|
|
41
|
+
border: '1px solid',
|
|
42
|
+
borderColor: 'border.default',
|
|
43
|
+
borderRadius: 2,
|
|
44
|
+
overflow: 'hidden',
|
|
45
|
+
}, children: [_jsxs(Button, { variant: "invisible", onClick: () => setIsExpanded(!isExpanded), sx: {
|
|
46
|
+
width: '100%',
|
|
47
|
+
display: 'flex',
|
|
48
|
+
justifyContent: 'space-between',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
padding: 3,
|
|
51
|
+
backgroundColor: 'canvas.subtle',
|
|
52
|
+
border: 'none',
|
|
53
|
+
borderBottom: isExpanded ? '1px solid' : 'none',
|
|
54
|
+
borderColor: 'border.default',
|
|
55
|
+
textAlign: 'left',
|
|
56
|
+
cursor: 'pointer',
|
|
57
|
+
'&:hover': {
|
|
58
|
+
backgroundColor: 'neutral.muted',
|
|
59
|
+
},
|
|
60
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "\uD83D\uDD27" }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: toolName }), _jsxs(Box, { sx: {
|
|
61
|
+
display: 'inline-flex',
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
gap: 1,
|
|
64
|
+
paddingX: 2,
|
|
65
|
+
paddingY: 1,
|
|
66
|
+
borderRadius: 2,
|
|
67
|
+
backgroundColor: 'neutral.subtle',
|
|
68
|
+
fontSize: 0,
|
|
69
|
+
}, children: [_jsx(Text, { sx: { color: statusInfo.color }, children: statusInfo.icon }), _jsx(Text, { sx: { color: statusInfo.color, fontWeight: 'semibold' }, children: statusInfo.label })] })] }), _jsx(Box, { as: "span", sx: {
|
|
70
|
+
display: 'inline-flex',
|
|
71
|
+
transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)',
|
|
72
|
+
transition: 'transform 0.2s',
|
|
73
|
+
}, children: _jsx(ChevronDownIcon, {}) })] }), isExpanded && (_jsxs(Box, { children: [_jsxs(Box, { sx: {
|
|
74
|
+
padding: 3,
|
|
75
|
+
borderBottom: part.state === 'output-available' ||
|
|
76
|
+
part.state === 'output-error'
|
|
77
|
+
? '1px solid'
|
|
78
|
+
: 'none',
|
|
79
|
+
borderColor: 'border.default',
|
|
80
|
+
}, children: [_jsx(Text, { sx: {
|
|
81
|
+
display: 'block',
|
|
82
|
+
fontSize: 0,
|
|
83
|
+
fontWeight: 'semibold',
|
|
84
|
+
color: 'fg.muted',
|
|
85
|
+
textTransform: 'uppercase',
|
|
86
|
+
letterSpacing: '0.05em',
|
|
87
|
+
marginBottom: 2,
|
|
88
|
+
}, children: "Parameters" }), _jsx(Box, { sx: {
|
|
89
|
+
backgroundColor: 'canvas.inset',
|
|
90
|
+
borderRadius: 2,
|
|
91
|
+
overflow: 'auto',
|
|
92
|
+
border: '1px solid',
|
|
93
|
+
borderColor: 'border.default',
|
|
94
|
+
}, children: _jsx("pre", { style: {
|
|
95
|
+
margin: 0,
|
|
96
|
+
padding: '12px',
|
|
97
|
+
fontSize: '12px',
|
|
98
|
+
fontFamily: 'monospace',
|
|
99
|
+
lineHeight: 1.5,
|
|
100
|
+
overflow: 'auto',
|
|
101
|
+
}, children: JSON.stringify(part.input, null, 2) }) })] }), part.state === 'output-available' && (_jsxs(Box, { sx: { padding: 3 }, children: [_jsx(Text, { sx: {
|
|
102
|
+
display: 'block',
|
|
103
|
+
fontSize: 0,
|
|
104
|
+
fontWeight: 'semibold',
|
|
105
|
+
color: 'fg.muted',
|
|
106
|
+
textTransform: 'uppercase',
|
|
107
|
+
letterSpacing: '0.05em',
|
|
108
|
+
marginBottom: 2,
|
|
109
|
+
}, children: "Result" }), _jsx(Box, { sx: {
|
|
110
|
+
backgroundColor: 'canvas.default',
|
|
111
|
+
borderRadius: 2,
|
|
112
|
+
overflow: 'auto',
|
|
113
|
+
border: '1px solid',
|
|
114
|
+
borderColor: 'border.default',
|
|
115
|
+
}, children: _jsx("pre", { style: {
|
|
116
|
+
margin: 0,
|
|
117
|
+
padding: '12px',
|
|
118
|
+
fontSize: '12px',
|
|
119
|
+
fontFamily: 'monospace',
|
|
120
|
+
lineHeight: 1.5,
|
|
121
|
+
overflow: 'auto',
|
|
122
|
+
}, children: part.output
|
|
123
|
+
? typeof part.output === 'string'
|
|
124
|
+
? part.output
|
|
125
|
+
: JSON.stringify(part.output, null, 2)
|
|
126
|
+
: 'No output' }) })] })), part.state === 'output-error' &&
|
|
127
|
+
'errorText' in part &&
|
|
128
|
+
part.errorText && (_jsxs(Box, { sx: { padding: 3 }, children: [_jsx(Text, { sx: {
|
|
129
|
+
display: 'block',
|
|
130
|
+
fontSize: 0,
|
|
131
|
+
fontWeight: 'semibold',
|
|
132
|
+
color: 'danger.fg',
|
|
133
|
+
textTransform: 'uppercase',
|
|
134
|
+
letterSpacing: '0.05em',
|
|
135
|
+
marginBottom: 2,
|
|
136
|
+
}, children: "Error" }), _jsx(Box, { sx: {
|
|
137
|
+
backgroundColor: 'danger.subtle',
|
|
138
|
+
borderRadius: 2,
|
|
139
|
+
overflow: 'auto',
|
|
140
|
+
border: '1px solid',
|
|
141
|
+
borderColor: 'danger.muted',
|
|
142
|
+
padding: 2,
|
|
143
|
+
}, children: _jsx(Text, { sx: { fontSize: 0, color: 'danger.fg' }, children: part.errorText }) })] }))] }))] }));
|
|
144
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024-2025 Datalayer, Inc.
|
|
3
|
+
*
|
|
4
|
+
* BSD 3-Clause License
|
|
5
|
+
*/
|
|
6
|
+
export { TextPart } from './TextPart';
|
|
7
|
+
export { ReasoningPart } from './ReasoningPart';
|
|
8
|
+
export { ToolPart } from './ToolPart';
|
|
9
|
+
export { DynamicToolPart } from './DynamicToolPart';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call the API extension
|
|
3
|
+
*
|
|
4
|
+
* @param endPoint API REST end point for the extension
|
|
5
|
+
* @param init Initial values for the request
|
|
6
|
+
* @returns The response body interpreted as JSON
|
|
7
|
+
*/
|
|
8
|
+
export declare function requestAPI<T>(endPoint?: string, init?: RequestInit): Promise<T>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024-2025 Datalayer, Inc.
|
|
3
|
+
*
|
|
4
|
+
* BSD 3-Clause License
|
|
5
|
+
*/
|
|
6
|
+
import { URLExt } from '@jupyterlab/coreutils';
|
|
7
|
+
import { ServerConnection } from '@jupyterlab/services';
|
|
8
|
+
/**
|
|
9
|
+
* Call the API extension
|
|
10
|
+
*
|
|
11
|
+
* @param endPoint API REST end point for the extension
|
|
12
|
+
* @param init Initial values for the request
|
|
13
|
+
* @returns The response body interpreted as JSON
|
|
14
|
+
*/
|
|
15
|
+
export async function requestAPI(endPoint = '', init = {}) {
|
|
16
|
+
// Make request to Jupyter API
|
|
17
|
+
const settings = ServerConnection.makeSettings();
|
|
18
|
+
const requestUrl = URLExt.join(settings.baseUrl, 'datalayer', endPoint);
|
|
19
|
+
let response;
|
|
20
|
+
try {
|
|
21
|
+
response = await ServerConnection.makeRequest(requestUrl, init, settings);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new ServerConnection.NetworkError(error);
|
|
25
|
+
}
|
|
26
|
+
let data = await response.text();
|
|
27
|
+
if (data.length > 0) {
|
|
28
|
+
try {
|
|
29
|
+
data = JSON.parse(data);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Not a JSON response body
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new ServerConnection.ResponseError(response, data.message || data);
|
|
37
|
+
}
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './chat';
|
package/lib/components/index.js
CHANGED
|
@@ -26,6 +26,10 @@ interface IRuntimeSimplePickerProps {
|
|
|
26
26
|
* Connection to the active runtime.
|
|
27
27
|
*/
|
|
28
28
|
sessionConnection?: Session.ISessionConnection;
|
|
29
|
+
/**
|
|
30
|
+
* Whether browser runtime is available.
|
|
31
|
+
*/
|
|
32
|
+
browserRuntimeAvailable?: boolean;
|
|
29
33
|
}
|
|
30
34
|
/**
|
|
31
35
|
* Runtime simple picker component.
|