@malloy-publisher/sdk 0.0.17 → 0.0.18

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.
@@ -0,0 +1,152 @@
1
+ import React from "react";
2
+ import { QueryClient, useQuery } from "@tanstack/react-query";
3
+ import { ModelsApi, Configuration } from "../../client";
4
+ import {
5
+ FormControl,
6
+ Chip,
7
+ Typography,
8
+ Button,
9
+ Dialog,
10
+ DialogTitle,
11
+ DialogContent,
12
+ DialogActions,
13
+ List,
14
+ ListItem,
15
+ ListItemButton,
16
+ ListItemText,
17
+ Stack,
18
+ } from "@mui/material";
19
+ import CloseIcon from "@mui/icons-material/Close";
20
+ import AddIcon from "@mui/icons-material/Add";
21
+ import { StyledCard } from "../styles";
22
+ import { usePublisherPackage } from "../Package/PublisherPackageProvider";
23
+
24
+ const modelsApi = new ModelsApi(new Configuration());
25
+ const queryClient = new QueryClient();
26
+
27
+ interface ModelPickerProps {
28
+ initialSelectedModels: string[];
29
+ onModelChange: (models: string[]) => void;
30
+ }
31
+
32
+ /**
33
+ * Picks Models that are available in the package.
34
+ * Only "malloy" model files are shown.
35
+ */
36
+ export function ModelPicker({
37
+ initialSelectedModels,
38
+ onModelChange,
39
+ }: ModelPickerProps) {
40
+ const { server, projectName, packageName, versionId, accessToken } =
41
+ usePublisherPackage();
42
+ const { data, isLoading, isSuccess, isError, error } = useQuery(
43
+ {
44
+ queryKey: ["models", server, projectName, packageName, versionId],
45
+ queryFn: () =>
46
+ modelsApi.listModels(projectName, packageName, versionId, {
47
+ baseURL: server,
48
+ withCredentials: !accessToken,
49
+ headers: {
50
+ Authorization: accessToken && `Bearer ${accessToken}`,
51
+ },
52
+ }),
53
+ retry: false,
54
+ },
55
+ queryClient,
56
+ );
57
+ const [selectedModels, setSelectedModels] = React.useState<string[]>(
58
+ initialSelectedModels || [],
59
+ );
60
+ const [addDialogOpen, setAddDialogOpen] = React.useState(false);
61
+
62
+ React.useEffect(() => {
63
+ setSelectedModels(initialSelectedModels || []);
64
+ }, [initialSelectedModels]);
65
+
66
+ const handleRemove = (model: string) => {
67
+ const newModels = selectedModels.filter((m) => m !== model);
68
+ setSelectedModels(newModels);
69
+ onModelChange(newModels);
70
+ };
71
+
72
+ const handleAdd = (model: string) => {
73
+ const newModels = [...selectedModels, model];
74
+ setSelectedModels(newModels);
75
+ onModelChange(newModels);
76
+ setAddDialogOpen(false);
77
+ };
78
+
79
+ let availableModels: string[] = [];
80
+ if (isSuccess && data?.data) {
81
+ availableModels = data.data
82
+ .filter(
83
+ (model) =>
84
+ model.type === "source" && !selectedModels.includes(model.path),
85
+ )
86
+ .map((model) => model.path);
87
+ }
88
+
89
+ return (
90
+ <StyledCard
91
+ sx={{ maxWidth: 400, marginLeft: "10px", padding: "10px 5px 5px 5px" }}
92
+ >
93
+ <Typography variant="h6">Imported Models</Typography>
94
+ <FormControl fullWidth>
95
+ {isLoading && <Typography>Loading...</Typography>}
96
+ {isError && <Typography>Error: {error.message}</Typography>}
97
+ <Stack
98
+ direction="row"
99
+ spacing={1}
100
+ sx={{
101
+ flexWrap: "wrap",
102
+ minHeight: 30,
103
+ alignItems: "center",
104
+ rowGap: "8px",
105
+ }}
106
+ >
107
+ {selectedModels.map((model) => (
108
+ <Chip
109
+ key={model}
110
+ label={model}
111
+ onDelete={() => handleRemove(model)}
112
+ deleteIcon={<CloseIcon />}
113
+ sx={{ marginBottom: "5px" }}
114
+ />
115
+ ))}
116
+ <Button
117
+ variant="outlined"
118
+ size="small"
119
+ startIcon={<AddIcon />}
120
+ onClick={() => setAddDialogOpen(true)}
121
+ sx={{ height: 32, marginTop: "10px" }}
122
+ disabled={
123
+ isLoading || isError || availableModels.length === 0
124
+ }
125
+ >
126
+ Add Model
127
+ </Button>
128
+ </Stack>
129
+ </FormControl>
130
+ <Dialog open={addDialogOpen} onClose={() => setAddDialogOpen(false)}>
131
+ <DialogTitle>Select a Model</DialogTitle>
132
+ <DialogContent>
133
+ {availableModels.length === 0 && (
134
+ <Typography>No models available</Typography>
135
+ )}
136
+ <List>
137
+ {availableModels.map((model) => (
138
+ <ListItem key={model} disablePadding>
139
+ <ListItemButton onClick={() => handleAdd(model)}>
140
+ <ListItemText primary={model} />
141
+ </ListItemButton>
142
+ </ListItem>
143
+ ))}
144
+ </List>
145
+ </DialogContent>
146
+ <DialogActions>
147
+ <Button onClick={() => setAddDialogOpen(false)}>Cancel</Button>
148
+ </DialogActions>
149
+ </Dialog>
150
+ </StyledCard>
151
+ );
152
+ }
@@ -0,0 +1,328 @@
1
+ import React, { useState } from "react";
2
+ import { Suspense, lazy } from "react";
3
+ import {
4
+ Stack,
5
+ Collapse,
6
+ CardActions,
7
+ CardContent,
8
+ IconButton,
9
+ Tooltip,
10
+ Button,
11
+ Box,
12
+ DialogContent,
13
+ Dialog,
14
+ DialogActions,
15
+ DialogTitle,
16
+ } from "@mui/material";
17
+ import { StyledCard } from "../styles";
18
+ import Markdown from "markdown-to-jsx";
19
+ import { Typography } from "@mui/material";
20
+ import { Divider } from "@mui/material";
21
+ import { highlight } from "../highlighter";
22
+ import { useEffect } from "react";
23
+ import CodeIcon from "@mui/icons-material/Code";
24
+ import LinkOutlinedIcon from "@mui/icons-material/LinkOutlined";
25
+ import MDEditor from "@uiw/react-md-editor";
26
+ import { EditableMalloyCell } from "./EditableMalloyCell";
27
+ import { NotebookCellValue } from "../NotebookManager";
28
+ import { SourceAndPath } from "../Model/SourcesExplorer";
29
+ const RenderedResult = lazy(() => import("../RenderedResult/RenderedResult"));
30
+
31
+ interface NotebookCellProps {
32
+ cell: NotebookCellValue;
33
+ expandCodeCell?: boolean;
34
+ expandEmbedding?: boolean;
35
+ editingMalloy?: boolean;
36
+ editingMarkdown?: boolean;
37
+ sourceAndPaths: SourceAndPath[];
38
+ newCell: React.ReactNode;
39
+ onCellChange: (cell: NotebookCellValue) => void;
40
+ onClose: () => void;
41
+ onEdit: () => void;
42
+ onDelete: () => void;
43
+ }
44
+
45
+ export function MutableCell({
46
+ cell,
47
+ expandCodeCell,
48
+ expandEmbedding,
49
+ editingMalloy,
50
+ editingMarkdown,
51
+ sourceAndPaths,
52
+ newCell,
53
+ onCellChange,
54
+ onClose,
55
+ onEdit,
56
+ onDelete,
57
+ }: NotebookCellProps) {
58
+ const [codeExpanded, setCodeExpanded] =
59
+ React.useState<boolean>(expandCodeCell);
60
+ const [embeddingExpanded, setEmbeddingExpanded] =
61
+ React.useState<boolean>(expandEmbedding);
62
+ const [highlightedMalloyCode, setHighlightedMalloyCode] =
63
+ React.useState<string>();
64
+ const [highlightedEmbedCode, setHighlightedEmbedCode] =
65
+ React.useState<string>();
66
+
67
+ useEffect(() => {
68
+ if (!cell.isMarkdown)
69
+ highlight(cell.value, "malloy").then((code) => {
70
+ setHighlightedMalloyCode(code);
71
+ });
72
+ }, [cell]);
73
+ const [value, setValue] = useState(cell.value);
74
+ React.useEffect(() => {
75
+ document.documentElement.setAttribute("data-color-mode", "light");
76
+ });
77
+ const updateMarkdown = useDebounce((newValue: string) => {
78
+ onCellChange({ ...cell, value: newValue });
79
+ });
80
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
81
+
82
+ const handleDeleteConfirm = () => {
83
+ onDelete();
84
+ setDeleteDialogOpen(false);
85
+ };
86
+ const noSources = sourceAndPaths.length === 0;
87
+
88
+ const deleteButton = (
89
+ <Tooltip title="Delete Cell">
90
+ <IconButton size="small" onClick={() => setDeleteDialogOpen(true)}>
91
+ <svg
92
+ width="20"
93
+ height="20"
94
+ viewBox="0 0 24 24"
95
+ fill="none"
96
+ xmlns="http://www.w3.org/2000/svg"
97
+ >
98
+ <path
99
+ d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m2 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14zM10 11v6M14 11v6"
100
+ stroke="currentColor"
101
+ strokeWidth="2"
102
+ strokeLinecap="round"
103
+ strokeLinejoin="round"
104
+ />
105
+ </svg>
106
+ </IconButton>
107
+ </Tooltip>
108
+ );
109
+
110
+ const deleteDialog = (
111
+ <Dialog
112
+ open={deleteDialogOpen}
113
+ onClose={() => setDeleteDialogOpen(false)}
114
+ >
115
+ <DialogTitle>Confirm Delete</DialogTitle>
116
+ <DialogContent>
117
+ <Typography>Are you sure you want to delete this cell?</Typography>
118
+ </DialogContent>
119
+ <DialogActions>
120
+ <Button onClick={() => setDeleteDialogOpen(false)}>Cancel</Button>
121
+ <Button onClick={handleDeleteConfirm} color="error">
122
+ Delete
123
+ </Button>
124
+ </DialogActions>
125
+ </Dialog>
126
+ );
127
+ const buttons = cell.isMarkdown ? (
128
+ <>
129
+ {deleteButton}
130
+ {(editingMarkdown && (
131
+ <Button variant="outlined" size="small" onClick={onClose}>
132
+ Save
133
+ </Button>
134
+ )) || (
135
+ <Button variant="outlined" size="small" onClick={onEdit}>
136
+ Edit
137
+ </Button>
138
+ )}
139
+ {newCell}
140
+ </>
141
+ ) : (
142
+ <>
143
+ {!editingMalloy && (
144
+ <Tooltip title={codeExpanded ? "Hide Code" : "View Code"}>
145
+ <IconButton
146
+ size="small"
147
+ onClick={() => {
148
+ setCodeExpanded(!codeExpanded);
149
+ }}
150
+ >
151
+ <CodeIcon />
152
+ </IconButton>
153
+ </Tooltip>
154
+ )}
155
+ {!editingMalloy && cell.result && (
156
+ <Tooltip
157
+ title={embeddingExpanded ? "Hide Embedding" : "View Embedding"}
158
+ >
159
+ <IconButton
160
+ size="small"
161
+ onClick={() => {
162
+ setEmbeddingExpanded(!embeddingExpanded);
163
+ }}
164
+ >
165
+ <LinkOutlinedIcon />
166
+ </IconButton>
167
+ </Tooltip>
168
+ )}
169
+ {deleteButton}
170
+
171
+ {!editingMalloy && (
172
+ <Button variant="outlined" size="small" onClick={onEdit}>
173
+ Edit
174
+ </Button>
175
+ )}
176
+ {newCell}
177
+ </>
178
+ );
179
+ const buttonStack = (
180
+ <>
181
+ <Stack sx={{ flexDirection: "row", justifyContent: "right" }}>
182
+ <CardActions
183
+ sx={{
184
+ padding: "4px",
185
+ mb: "auto",
186
+ mt: "auto",
187
+ }}
188
+ >
189
+ {buttons}
190
+ </CardActions>
191
+ </Stack>
192
+ <Box
193
+ sx={{
194
+ borderBottom: "1px solid #e0e0e0",
195
+ mx: "10px",
196
+ width: "auto",
197
+ }}
198
+ />
199
+ </>
200
+ );
201
+ return (
202
+ <StyledCard variant="outlined" sx={{ marginTop: "10px" }}>
203
+ {(cell.isMarkdown && (
204
+ // <StyledCard variant="outlined" sx={{ border: 0 }}>
205
+ <>
206
+ {buttonStack}
207
+ {editingMarkdown && (
208
+ <MDEditor
209
+ value={value}
210
+ preview="edit"
211
+ onChange={(newValue) => {
212
+ setValue(newValue);
213
+ updateMarkdown(newValue);
214
+ }}
215
+ />
216
+ )}
217
+ <Box
218
+ sx={{
219
+ px: 0.5,
220
+ pt: 0,
221
+ mt: 0,
222
+ "& h1, & h2, & h3, & h4, & h5, & h6": { mt: 0.5, mb: 0.5 },
223
+ "& p": { mt: 0.5, mb: 0.5 },
224
+ "& ul, & ol": { mt: 0.5, mb: 0.5 },
225
+ "& li": { mt: 0, mb: 0 },
226
+ "& pre, & code": { mt: 0.5, mb: 0.5 },
227
+ "& blockquote": { mt: 0.5, mb: 0.5 },
228
+ }}
229
+ >
230
+ <Markdown>{value}</Markdown>
231
+ </Box>
232
+ </>
233
+ )) ||
234
+ (!cell.isMarkdown && (
235
+ <>
236
+ {buttonStack}
237
+ <Collapse in={embeddingExpanded} timeout="auto" unmountOnExit>
238
+ <Divider />
239
+ <Stack
240
+ sx={{
241
+ p: "10px",
242
+ borderRadius: 0,
243
+ flexDirection: "row",
244
+ justifyContent: "space-between",
245
+ }}
246
+ >
247
+ <Typography
248
+ component="div"
249
+ sx={{
250
+ fontSize: "12px",
251
+ "& .line": { textWrap: "wrap" },
252
+ }}
253
+ dangerouslySetInnerHTML={{
254
+ __html: highlightedEmbedCode,
255
+ }}
256
+ />
257
+ </Stack>
258
+ </Collapse>
259
+ <Collapse in={codeExpanded} timeout="auto" unmountOnExit>
260
+ <Divider />
261
+ <Stack
262
+ sx={{
263
+ p: "10px",
264
+ borderRadius: 0,
265
+ flexDirection: "row",
266
+ justifyContent: "space-between",
267
+ }}
268
+ >
269
+ <Typography
270
+ component="div"
271
+ className="content"
272
+ sx={{
273
+ fontSize: "12px",
274
+ "& .line": { textWrap: "wrap" },
275
+ }}
276
+ dangerouslySetInnerHTML={{
277
+ __html: highlightedMalloyCode,
278
+ }}
279
+ />
280
+ </Stack>
281
+ </Collapse>
282
+ {editingMalloy &&
283
+ (noSources ? (
284
+ <Typography>
285
+ No Model Imports. Please add a model import above.
286
+ </Typography>
287
+ ) : (
288
+ <EditableMalloyCell
289
+ sourceAndPaths={sourceAndPaths}
290
+ onCellChange={onCellChange}
291
+ onClose={onClose}
292
+ cell={cell}
293
+ />
294
+ ))}
295
+ {!editingMalloy && cell.result && (
296
+ <>
297
+ <CardContent
298
+ sx={{ maxHeight: "400px", overflow: "auto" }}
299
+ >
300
+ <Suspense fallback="Loading malloy...">
301
+ <RenderedResult result={cell.result} />
302
+ </Suspense>
303
+ </CardContent>
304
+ </>
305
+ )}
306
+ </>
307
+ ))}
308
+ {deleteDialogOpen && deleteDialog}
309
+ </StyledCard>
310
+ );
311
+ }
312
+
313
+ function useDebounce<T>(callback: (value: T) => void, delay: number = 2000) {
314
+ const timeoutRef = React.useRef<NodeJS.Timeout>();
315
+
316
+ return React.useCallback(
317
+ (value: T) => {
318
+ if (timeoutRef.current) {
319
+ clearTimeout(timeoutRef.current);
320
+ }
321
+
322
+ timeoutRef.current = setTimeout(() => {
323
+ callback(value);
324
+ }, delay);
325
+ },
326
+ [callback, delay],
327
+ );
328
+ }