@marimo-team/islands 0.16.4 → 0.16.5
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/{ConnectedDataExplorerComponent-CCjhPKMy.js → ConnectedDataExplorerComponent-D96i9G-X.js} +3 -3
- package/dist/assets/__vite-browser-external-Dv_SHu1h.js +1 -0
- package/dist/assets/{worker-DnuXpGWN.js → worker-DVOR9oZG.js} +2 -2
- package/dist/{formats-D5C6JAJf.js → formats-ChrNdVdJ.js} +1 -1
- package/dist/{glide-data-editor-CYfKmSNp.js → glide-data-editor-D_kEsT07.js} +68 -68
- package/dist/main.js +84 -213
- package/dist/{mermaid-BlJDcO4M.js → mermaid-MWiyXDcI.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{types-Dcb1hf55.js → types-1X1uZB4y.js} +323 -179
- package/dist/{useAsyncData-DAtPzJzP.js → useAsyncData-C4IqQK0g.js} +1 -1
- package/dist/{useDateFormatter-CiUlIu7v.js → useDateFormatter-BCsBqetx.js} +1 -1
- package/dist/{useTheme-CmsvrO5o.js → useTheme-C2pgJzDH.js} +1 -0
- package/dist/{vega-component-B3LA6qbm.js → vega-component-Cv4J8CHz.js} +3 -3
- package/package.json +1 -1
- package/src/__tests__/chat-history.test.ts +123 -0
- package/src/components/app-config/ai-config.tsx +23 -0
- package/src/components/app-config/mcp-config.tsx +42 -2
- package/src/components/app-config/user-config-form.tsx +0 -24
- package/src/components/chat/acp/__tests__/context-utils.test.ts +1 -1
- package/src/components/chat/acp/agent-panel.tsx +1 -1
- package/src/components/chat/acp/blocks.tsx +46 -53
- package/src/components/chat/acp/common.tsx +1 -1
- package/src/components/chat/acp/context-utils.ts +1 -1
- package/src/components/chat/acp/session-tabs.tsx +1 -1
- package/src/components/chat/chat-history-popover.tsx +125 -0
- package/src/components/chat/chat-history-utils.ts +69 -0
- package/src/components/chat/chat-panel.tsx +9 -57
- package/src/components/editor/__tests__/data-attributes.test.tsx +1 -1
- package/src/components/editor/actions/useNotebookActions.tsx +2 -4
- package/src/components/editor/ai/__tests__/completion-utils.test.ts +23 -31
- package/src/components/editor/cell/CreateCellButton.tsx +14 -2
- package/src/components/editor/cell/code/cell-editor.tsx +1 -0
- package/src/components/editor/database/schemas.ts +2 -10
- package/src/components/editor/{Cell.tsx → notebook-cell.tsx} +5 -1
- package/src/components/editor/output/MarimoErrorOutput.tsx +4 -34
- package/src/components/editor/renderers/{CellArray.tsx → cell-array.tsx} +1 -1
- package/src/components/forms/__tests__/form-utils.test.ts +2 -2
- package/src/components/mcp/hooks.ts +48 -0
- package/src/components/mcp/mcp-status-indicator.tsx +144 -0
- package/src/components/ui/number-field.tsx +4 -1
- package/src/core/ai/context/providers/__tests__/__snapshots__/tables.test.ts.snap +13 -19
- package/src/core/ai/context/providers/__tests__/cell-output.test.ts +0 -1
- package/src/core/ai/context/providers/__tests__/datasource.test.ts +5 -6
- package/src/core/ai/context/providers/__tests__/error.test.ts +24 -15
- package/src/core/ai/context/providers/cell-output.ts +5 -5
- package/src/core/ai/context/providers/common.ts +13 -4
- package/src/core/ai/context/providers/datasource.ts +31 -20
- package/src/core/ai/context/providers/error.ts +3 -4
- package/src/core/ai/context/providers/file.ts +2 -2
- package/src/core/ai/context/providers/tables.ts +36 -8
- package/src/core/ai/context/providers/variable.ts +2 -3
- package/src/core/cells/__tests__/cells.test.ts +6 -6
- package/src/core/cells/cells.ts +12 -13
- package/src/core/cells/scrollCellIntoView.ts +1 -1
- package/src/core/codemirror/__tests__/setup.test.ts +1 -0
- package/src/core/codemirror/cm.ts +3 -2
- package/src/core/config/__tests__/config-schema.test.ts +2 -0
- package/src/core/config/config-schema.ts +2 -0
- package/src/core/config/feature-flag.tsx +0 -2
- package/src/core/edit-app.tsx +1 -1
- package/src/core/network/CachingRequestRegistry.ts +2 -2
- package/src/stories/cell.stories.tsx +1 -1
- package/src/stories/layout/vertical/one-column.stories.tsx +1 -1
- package/src/utils/numbers.ts +24 -1
- package/dist/assets/__vite-browser-external-BeNtI_tJ.js +0 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { Loader2, PlugIcon, RefreshCwIcon } from "lucide-react";
|
|
4
|
+
import { API } from "@/core/network/api";
|
|
5
|
+
import { cn } from "@/utils/cn";
|
|
6
|
+
import { Button } from "../ui/button";
|
|
7
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
|
8
|
+
import { Tooltip } from "../ui/tooltip";
|
|
9
|
+
import { toast } from "../ui/use-toast";
|
|
10
|
+
import { useMCPStatus } from "./hooks";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* MCP Status indicator component
|
|
14
|
+
* Shows a small icon with status color and a popover with detailed information
|
|
15
|
+
*/
|
|
16
|
+
export const MCPStatusIndicator: React.FC = () => {
|
|
17
|
+
const { data: status, refetch, isFetching } = useMCPStatus();
|
|
18
|
+
|
|
19
|
+
const handleRefresh = async () => {
|
|
20
|
+
try {
|
|
21
|
+
await API.post<object, { success: boolean }>("/ai/mcp/refresh", {});
|
|
22
|
+
toast({
|
|
23
|
+
title: "MCP refreshed",
|
|
24
|
+
description: "MCP server configuration has been refreshed",
|
|
25
|
+
});
|
|
26
|
+
refetch();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
toast({
|
|
29
|
+
title: "Refresh failed",
|
|
30
|
+
description:
|
|
31
|
+
error instanceof Error ? error.message : "Failed to refresh MCP",
|
|
32
|
+
variant: "danger",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const servers = status?.servers || {};
|
|
38
|
+
const hasServers = Object.keys(servers).length > 0;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Popover>
|
|
42
|
+
<Tooltip content="MCP Status">
|
|
43
|
+
<PopoverTrigger asChild={true}>
|
|
44
|
+
<Button variant="text" size="icon">
|
|
45
|
+
<PlugIcon
|
|
46
|
+
className={cn(
|
|
47
|
+
"h-4 w-4",
|
|
48
|
+
status?.status === "ok" && "text-green-500",
|
|
49
|
+
status?.status === "partial" && "text-yellow-500",
|
|
50
|
+
status?.status === "error" && hasServers && "text-red-500",
|
|
51
|
+
)}
|
|
52
|
+
/>
|
|
53
|
+
</Button>
|
|
54
|
+
</PopoverTrigger>
|
|
55
|
+
</Tooltip>
|
|
56
|
+
<PopoverContent className="w-[320px]" align="start" side="right">
|
|
57
|
+
<div className="space-y-3">
|
|
58
|
+
<div className="flex items-center justify-between">
|
|
59
|
+
<h4 className="font-medium text-sm">MCP Server Status</h4>
|
|
60
|
+
<Button
|
|
61
|
+
variant="ghost"
|
|
62
|
+
size="xs"
|
|
63
|
+
onClick={handleRefresh}
|
|
64
|
+
disabled={isFetching}
|
|
65
|
+
>
|
|
66
|
+
{isFetching ? (
|
|
67
|
+
<Loader2 className="h-3 w-3 animate-spin" />
|
|
68
|
+
) : (
|
|
69
|
+
<RefreshCwIcon className="h-3 w-3" />
|
|
70
|
+
)}
|
|
71
|
+
</Button>
|
|
72
|
+
</div>
|
|
73
|
+
{status && (
|
|
74
|
+
<div className="text-xs space-y-2">
|
|
75
|
+
{hasServers && (
|
|
76
|
+
<div className="flex justify-between items-center">
|
|
77
|
+
<span className="text-muted-foreground">Overall:</span>
|
|
78
|
+
<McpStatusText status={status.status} />
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
{status.error && (
|
|
82
|
+
<div className="text-xs text-red-500 bg-red-50 dark:bg-red-950/20 p-2 rounded">
|
|
83
|
+
{status.error}
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
{hasServers && (
|
|
87
|
+
<div className="space-y-1">
|
|
88
|
+
<div className="text-muted-foreground font-medium">
|
|
89
|
+
Servers:
|
|
90
|
+
</div>
|
|
91
|
+
{Object.entries(servers).map(([name, serverStatus]) => (
|
|
92
|
+
<div
|
|
93
|
+
key={name}
|
|
94
|
+
className="flex justify-between items-center pl-2"
|
|
95
|
+
>
|
|
96
|
+
<span className="text-muted-foreground truncate max-w-[180px]">
|
|
97
|
+
{name}
|
|
98
|
+
</span>
|
|
99
|
+
<McpStatusText status={serverStatus} />
|
|
100
|
+
</div>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
{!hasServers && (
|
|
105
|
+
<div className="text-muted-foreground text-center py-2">
|
|
106
|
+
No MCP servers configured. <br /> Configure under{" "}
|
|
107
|
+
<b>Settings > AI > MCP</b>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
</PopoverContent>
|
|
114
|
+
</Popover>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const McpStatusText: React.FC<{
|
|
119
|
+
status:
|
|
120
|
+
| "ok"
|
|
121
|
+
| "partial"
|
|
122
|
+
| "error"
|
|
123
|
+
| "failed"
|
|
124
|
+
| "disconnected"
|
|
125
|
+
| "pending"
|
|
126
|
+
| "connected";
|
|
127
|
+
}> = ({ status }) => {
|
|
128
|
+
return (
|
|
129
|
+
<span
|
|
130
|
+
className={cn(
|
|
131
|
+
"text-xs font-medium",
|
|
132
|
+
status === "ok" && "text-green-500",
|
|
133
|
+
status === "partial" && "text-yellow-500",
|
|
134
|
+
status === "error" && "text-red-500",
|
|
135
|
+
status === "failed" && "text-red-500",
|
|
136
|
+
status === "disconnected" && "text-gray-500",
|
|
137
|
+
status === "pending" && "text-yellow-500",
|
|
138
|
+
status === "connected" && "text-green-500",
|
|
139
|
+
)}
|
|
140
|
+
>
|
|
141
|
+
{status}
|
|
142
|
+
</span>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
@@ -7,8 +7,10 @@ import {
|
|
|
7
7
|
Button,
|
|
8
8
|
type ButtonProps,
|
|
9
9
|
Input as RACInput,
|
|
10
|
+
useLocale,
|
|
10
11
|
} from "react-aria-components";
|
|
11
12
|
import { cn } from "@/utils/cn";
|
|
13
|
+
import { maxFractionalDigits } from "@/utils/numbers";
|
|
12
14
|
|
|
13
15
|
export interface NumberFieldProps extends AriaNumberFieldProps {
|
|
14
16
|
placeholder?: string;
|
|
@@ -17,12 +19,13 @@ export interface NumberFieldProps extends AriaNumberFieldProps {
|
|
|
17
19
|
|
|
18
20
|
export const NumberField = React.forwardRef<HTMLInputElement, NumberFieldProps>(
|
|
19
21
|
({ placeholder, variant = "default", ...props }, ref) => {
|
|
22
|
+
const { locale } = useLocale();
|
|
20
23
|
return (
|
|
21
24
|
<AriaNumberField
|
|
22
25
|
{...props}
|
|
23
26
|
formatOptions={{
|
|
24
27
|
minimumFractionDigits: 0,
|
|
25
|
-
maximumFractionDigits:
|
|
28
|
+
maximumFractionDigits: maxFractionalDigits(locale),
|
|
26
29
|
}}
|
|
27
30
|
>
|
|
28
31
|
<div
|
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
3
|
exports[`TableContextProvider > formatContext > should format context for basic table > basic-table-context 1`] = `
|
|
4
|
-
"<data name="products" source="memory">
|
|
5
|
-
Shape: 100 rows, 3 columns
|
|
4
|
+
"<data name="products" source="memory">Shape: 100 rows, 3 columns
|
|
6
5
|
Columns:
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
6
|
+
id (integer) - samples: [sample_id_1, sample_id_2]
|
|
7
|
+
name (string) - samples: [sample_name_1, sample_name_2]
|
|
8
|
+
active (boolean) - samples: [sample_active_1, sample_active_2]</data>"
|
|
10
9
|
`;
|
|
11
10
|
|
|
12
11
|
exports[`TableContextProvider > formatContext > should format context for remote database table > remote-table-context 1`] = `
|
|
13
|
-
"<data name="remote_table" source="postgresql://localhost:5432/mydb">
|
|
14
|
-
Shape: 100 rows, 3 columns
|
|
12
|
+
"<data name="remote_table" source="postgresql://localhost:5432/mydb">Shape: 100 rows, 3 columns
|
|
15
13
|
Columns:
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
14
|
+
uuid (string) - samples: [sample_uuid_1, sample_uuid_2]
|
|
15
|
+
created_at (string) - samples: [sample_created_at_1, sample_created_at_2]
|
|
16
|
+
metadata (string) - samples: [sample_metadata_1, sample_metadata_2]</data>"
|
|
19
17
|
`;
|
|
20
18
|
|
|
21
|
-
exports[`TableContextProvider > formatContext > should format context for table without columns > no-columns-table-context 1`] = `
|
|
22
|
-
"<data name="no_columns" source="memory">
|
|
23
|
-
Shape: 100 rows, 3 columns</data>"
|
|
24
|
-
`;
|
|
19
|
+
exports[`TableContextProvider > formatContext > should format context for table without columns > no-columns-table-context 1`] = `"<data name="no_columns" source="memory">Shape: 100 rows, 3 columns</data>"`;
|
|
25
20
|
|
|
26
21
|
exports[`TableContextProvider > formatContext > should format context for table without shape info > no-shape-table-context 1`] = `
|
|
27
|
-
"<data name="no_shape" source="memory">
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
- active: boolean</data>"
|
|
22
|
+
"<data name="no_shape" source="memory">Columns:
|
|
23
|
+
id (integer) - samples: [sample_id_1, sample_id_2]
|
|
24
|
+
name (string) - samples: [sample_name_1, sample_name_2]
|
|
25
|
+
active (boolean) - samples: [sample_active_1, sample_active_2]</data>"
|
|
32
26
|
`;
|
|
33
27
|
|
|
34
28
|
exports[`TableContextProvider > getItems > should handle dataframe tables with variable names > dataframe-table 1`] = `
|
|
@@ -187,7 +187,6 @@ describe("CellOutputContextProvider", () => {
|
|
|
187
187
|
expect(completion.displayLabel).toBe(item.data.cellName);
|
|
188
188
|
expect(completion.detail).toContain("output");
|
|
189
189
|
expect(completion.type).toBe("cell-output");
|
|
190
|
-
expect(completion.section).toBe("Cell Output");
|
|
191
190
|
expect(typeof completion.info).toBe("function");
|
|
192
191
|
});
|
|
193
192
|
});
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
} from "@/core/datasets/data-source-connections";
|
|
9
9
|
import { DUCKDB_ENGINE } from "@/core/datasets/engines";
|
|
10
10
|
import type { DataSourceConnection, DataTable } from "@/core/kernel/messages";
|
|
11
|
-
import { Boosts } from "../common";
|
|
11
|
+
import { Boosts, Sections } from "../common";
|
|
12
12
|
import { DatasourceContextProvider } from "../datasource";
|
|
13
13
|
|
|
14
14
|
// Mock data for testing
|
|
@@ -268,8 +268,7 @@ describe("DatasourceContextProvider", () => {
|
|
|
268
268
|
);
|
|
269
269
|
|
|
270
270
|
const items = providerWithEmpty.getItems();
|
|
271
|
-
expect(items).toHaveLength(
|
|
272
|
-
expect(items[0].data.connection.databases).toEqual([]);
|
|
271
|
+
expect(items).toHaveLength(0);
|
|
273
272
|
});
|
|
274
273
|
|
|
275
274
|
it("should handle connections with databases but no schemas", () => {
|
|
@@ -311,7 +310,7 @@ describe("DatasourceContextProvider", () => {
|
|
|
311
310
|
detail: "DuckDB",
|
|
312
311
|
boost: Boosts.MEDIUM,
|
|
313
312
|
type: "datasource",
|
|
314
|
-
section:
|
|
313
|
+
section: Sections.DATA_SOURCES,
|
|
315
314
|
});
|
|
316
315
|
|
|
317
316
|
expect(completion.info).toBeDefined();
|
|
@@ -355,7 +354,7 @@ describe("DatasourceContextProvider", () => {
|
|
|
355
354
|
detail: "PostgreSQL",
|
|
356
355
|
boost: Boosts.MEDIUM,
|
|
357
356
|
type: "datasource",
|
|
358
|
-
section:
|
|
357
|
+
section: Sections.DATA_SOURCES,
|
|
359
358
|
});
|
|
360
359
|
});
|
|
361
360
|
|
|
@@ -380,7 +379,7 @@ describe("DatasourceContextProvider", () => {
|
|
|
380
379
|
detail: "DuckDB",
|
|
381
380
|
boost: Boosts.MEDIUM,
|
|
382
381
|
type: "datasource",
|
|
383
|
-
section:
|
|
382
|
+
section: Sections.DATA_SOURCES,
|
|
384
383
|
});
|
|
385
384
|
});
|
|
386
385
|
});
|
|
@@ -6,7 +6,6 @@ import { beforeEach, describe, expect, it } from "vitest";
|
|
|
6
6
|
import { MockNotebook } from "@/__mocks__/notebook";
|
|
7
7
|
import { notebookAtom } from "@/core/cells/cells";
|
|
8
8
|
import { type CellId, CellId as CellIdClass } from "@/core/cells/ids";
|
|
9
|
-
import { Boosts } from "../common";
|
|
10
9
|
import { ErrorContextProvider } from "../error";
|
|
11
10
|
|
|
12
11
|
describe("ErrorContextProvider", () => {
|
|
@@ -118,15 +117,20 @@ describe("ErrorContextProvider", () => {
|
|
|
118
117
|
const items = provider.getItems();
|
|
119
118
|
const completion = provider.formatCompletion(items[0]);
|
|
120
119
|
|
|
121
|
-
expect(completion).
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
expect(completion).toMatchInlineSnapshot(`
|
|
121
|
+
{
|
|
122
|
+
"apply": "@Errors",
|
|
123
|
+
"detail": "2 errors",
|
|
124
|
+
"displayLabel": "Errors",
|
|
125
|
+
"info": [Function],
|
|
126
|
+
"label": "@Errors",
|
|
127
|
+
"section": {
|
|
128
|
+
"name": "Error",
|
|
129
|
+
"rank": 1,
|
|
130
|
+
},
|
|
131
|
+
"type": "error",
|
|
132
|
+
}
|
|
133
|
+
`);
|
|
130
134
|
|
|
131
135
|
// Test the info function
|
|
132
136
|
expect(completion.info).toBeDefined();
|
|
@@ -179,11 +183,16 @@ describe("ErrorContextProvider", () => {
|
|
|
179
183
|
};
|
|
180
184
|
|
|
181
185
|
const completion = provider.formatCompletion(item);
|
|
182
|
-
expect(completion).
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
expect(completion).toMatchInlineSnapshot(`
|
|
187
|
+
{
|
|
188
|
+
"displayLabel": "Error",
|
|
189
|
+
"label": "Error",
|
|
190
|
+
"section": {
|
|
191
|
+
"name": "Error",
|
|
192
|
+
"rank": 1,
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
`);
|
|
187
196
|
});
|
|
188
197
|
});
|
|
189
198
|
|
|
@@ -13,7 +13,7 @@ import { parseHtmlContent } from "@/utils/dom";
|
|
|
13
13
|
import { Logger } from "@/utils/Logger";
|
|
14
14
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
|
15
15
|
import { contextToXml } from "../utils";
|
|
16
|
-
import { Boosts } from "./common";
|
|
16
|
+
import { Boosts, Sections } from "./common";
|
|
17
17
|
|
|
18
18
|
export interface CellOutputContextItem extends AIContextItem {
|
|
19
19
|
type: "cell-output";
|
|
@@ -150,13 +150,14 @@ export class CellOutputContextProvider extends AIContextProvider<CellOutputConte
|
|
|
150
150
|
|
|
151
151
|
formatCompletion(item: CellOutputContextItem): Completion {
|
|
152
152
|
const { data } = item;
|
|
153
|
+
|
|
153
154
|
return {
|
|
154
155
|
label: `@${data.cellName}`,
|
|
155
156
|
displayLabel: data.cellName,
|
|
156
157
|
detail: `${data.outputType} output`,
|
|
157
|
-
boost: Boosts.
|
|
158
|
+
boost: data.outputType === "media" ? Boosts.HIGH : Boosts.MEDIUM,
|
|
158
159
|
type: this.contextType,
|
|
159
|
-
section:
|
|
160
|
+
section: Sections.CELL_OUTPUT,
|
|
160
161
|
apply: `@${data.cellName}`,
|
|
161
162
|
info: () => {
|
|
162
163
|
const infoContainer = document.createElement("div");
|
|
@@ -247,8 +248,7 @@ export class CellOutputContextProvider extends AIContextProvider<CellOutputConte
|
|
|
247
248
|
"italic",
|
|
248
249
|
"mb-2",
|
|
249
250
|
);
|
|
250
|
-
mediaDiv.textContent =
|
|
251
|
-
"Contains media content (image, SVG, or canvas)";
|
|
251
|
+
mediaDiv.textContent = "A screenshot of the output will be attached";
|
|
252
252
|
infoContainer.append(mediaDiv);
|
|
253
253
|
}
|
|
254
254
|
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import type { CompletionSection } from "@codemirror/autocomplete";
|
|
4
|
+
|
|
3
5
|
/** Number from -99 to 99. Higher numbers are prioritized when surfacing completions. */
|
|
4
6
|
export const Boosts = {
|
|
5
|
-
LOCAL_TABLE:
|
|
6
|
-
REMOTE_TABLE:
|
|
7
|
+
LOCAL_TABLE: 7,
|
|
8
|
+
REMOTE_TABLE: 5,
|
|
7
9
|
HIGH: 4,
|
|
8
|
-
VARIABLE: 3,
|
|
9
10
|
MEDIUM: 3,
|
|
10
11
|
CELL_OUTPUT: 2,
|
|
11
12
|
LOW: 2,
|
|
12
|
-
ERROR: 1,
|
|
13
13
|
} as const;
|
|
14
|
+
|
|
15
|
+
export const Sections = {
|
|
16
|
+
ERROR: { name: "Error", rank: 1 },
|
|
17
|
+
TABLE: { name: "Table", rank: 2 },
|
|
18
|
+
DATA_SOURCES: { name: "Data Sources", rank: 3 },
|
|
19
|
+
VARIABLE: { name: "Variable", rank: 4 },
|
|
20
|
+
CELL_OUTPUT: { name: "Cell Output", rank: 5 },
|
|
21
|
+
FILE: { name: "File", rank: 6 },
|
|
22
|
+
} satisfies Record<string, CompletionSection>;
|
|
@@ -17,7 +17,7 @@ import type { DataSourceConnection, DataTable } from "@/core/kernel/messages";
|
|
|
17
17
|
import type { AIContextItem } from "../registry";
|
|
18
18
|
import { AIContextProvider } from "../registry";
|
|
19
19
|
import { contextToXml } from "../utils";
|
|
20
|
-
import { Boosts } from "./common";
|
|
20
|
+
import { Boosts, Sections } from "./common";
|
|
21
21
|
|
|
22
22
|
type NamedDatasource = Omit<
|
|
23
23
|
DataSourceConnection,
|
|
@@ -53,24 +53,35 @@ export class DatasourceContextProvider extends AIContextProvider<DatasourceConte
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
getItems(): DatasourceContextItem[] {
|
|
56
|
-
return [...this.connectionsMap.values()]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
56
|
+
return [...this.connectionsMap.values()]
|
|
57
|
+
.map((connection): DatasourceContextItem | null => {
|
|
58
|
+
let description = "Database schema.";
|
|
59
|
+
const data: DatasourceContextItem["data"] = {
|
|
60
|
+
connection: connection,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (INTERNAL_SQL_ENGINES.has(connection.name)) {
|
|
64
|
+
data.tables = this.dataframes;
|
|
65
|
+
description =
|
|
66
|
+
"Database schema and the dataframes that can be queried";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Hide empty datasources
|
|
70
|
+
const hasNoTables =
|
|
71
|
+
connection.databases.length === 0 && (data.tables?.length ?? 0) === 0;
|
|
72
|
+
if (hasNoTables) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
uri: this.asURI(connection.name),
|
|
78
|
+
name: connection.name,
|
|
79
|
+
description: description,
|
|
80
|
+
type: this.contextType,
|
|
81
|
+
data: data,
|
|
82
|
+
};
|
|
83
|
+
})
|
|
84
|
+
.filter(Boolean);
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
formatContext(item: DatasourceContextItem): string {
|
|
@@ -115,7 +126,7 @@ export class DatasourceContextProvider extends AIContextProvider<DatasourceConte
|
|
|
115
126
|
detail: dbDisplayName(dataConnection.dialect),
|
|
116
127
|
boost: Boosts.MEDIUM,
|
|
117
128
|
type: this.contextType,
|
|
118
|
-
section:
|
|
129
|
+
section: Sections.DATA_SOURCES,
|
|
119
130
|
info: () => {
|
|
120
131
|
const infoContainer = document.createElement("div");
|
|
121
132
|
infoContainer.classList.add("mo-cm-tooltip", "docs-documentation");
|
|
@@ -9,7 +9,7 @@ import { logNever } from "@/utils/assertNever";
|
|
|
9
9
|
import { PluralWord } from "@/utils/pluralize";
|
|
10
10
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
|
11
11
|
import { contextToXml } from "../utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Sections } from "./common";
|
|
13
13
|
|
|
14
14
|
export interface ErrorContextItem extends AIContextItem {
|
|
15
15
|
type: "error";
|
|
@@ -111,10 +111,9 @@ export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
|
111
111
|
label: "@Errors",
|
|
112
112
|
displayLabel: "Errors",
|
|
113
113
|
detail: `${item.data.errors.length} ${errorsTxt.pluralize(item.data.errors.length)}`,
|
|
114
|
-
boost: Boosts.ERROR,
|
|
115
114
|
type: "error",
|
|
116
115
|
apply: "@Errors",
|
|
117
|
-
section:
|
|
116
|
+
section: Sections.ERROR,
|
|
118
117
|
info: () => {
|
|
119
118
|
const infoContainer = document.createElement("div");
|
|
120
119
|
infoContainer.classList.add(
|
|
@@ -150,7 +149,7 @@ export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
|
150
149
|
return {
|
|
151
150
|
label: "Error",
|
|
152
151
|
displayLabel: "Error",
|
|
153
|
-
|
|
152
|
+
section: Sections.ERROR,
|
|
154
153
|
};
|
|
155
154
|
}
|
|
156
155
|
|
|
@@ -15,7 +15,7 @@ import { type Base64String, base64ToDataURL } from "@/utils/json/base64";
|
|
|
15
15
|
import { Logger } from "@/utils/Logger";
|
|
16
16
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
|
17
17
|
import { contextToXml } from "../utils";
|
|
18
|
-
import { Boosts } from "./common";
|
|
18
|
+
import { Boosts, Sections } from "./common";
|
|
19
19
|
export interface FileContextItem extends AIContextItem {
|
|
20
20
|
type: "file";
|
|
21
21
|
data: {
|
|
@@ -190,7 +190,7 @@ export class FileContextProvider extends AIContextProvider<FileContextItem> {
|
|
|
190
190
|
return {
|
|
191
191
|
...this.createBasicCompletion(item),
|
|
192
192
|
type: "file",
|
|
193
|
-
section:
|
|
193
|
+
section: Sections.FILE,
|
|
194
194
|
boost: data.isDirectory ? Boosts.MEDIUM : Boosts.LOW,
|
|
195
195
|
detail: data.path,
|
|
196
196
|
displayLabel: `${icon} ${name}`,
|
|
@@ -9,7 +9,7 @@ import type { DataTable } from "@/core/kernel/messages";
|
|
|
9
9
|
import type { AIContextItem } from "../registry";
|
|
10
10
|
import { AIContextProvider } from "../registry";
|
|
11
11
|
import { contextToXml } from "../utils";
|
|
12
|
-
import { Boosts } from "./common";
|
|
12
|
+
import { Boosts, Sections } from "./common";
|
|
13
13
|
|
|
14
14
|
export interface TableContextItem extends AIContextItem {
|
|
15
15
|
type: "data";
|
|
@@ -38,21 +38,46 @@ export class TableContextProvider extends AIContextProvider<TableContextItem> {
|
|
|
38
38
|
|
|
39
39
|
formatContext(item: TableContextItem): string {
|
|
40
40
|
const { data } = item;
|
|
41
|
-
const { columns, source, num_rows, num_columns, name } =
|
|
41
|
+
const { columns, source, num_rows, num_columns, name, variable_name } =
|
|
42
|
+
data;
|
|
43
|
+
|
|
44
|
+
// Build shape information
|
|
42
45
|
const shape = [
|
|
43
|
-
num_rows
|
|
44
|
-
num_columns
|
|
46
|
+
num_rows != null ? `${num_rows} rows` : undefined,
|
|
47
|
+
num_columns != null ? `${num_columns} columns` : undefined,
|
|
45
48
|
]
|
|
46
49
|
.filter(Boolean)
|
|
47
50
|
.join(", ");
|
|
48
51
|
|
|
49
52
|
let details = "";
|
|
53
|
+
|
|
54
|
+
// Add shape information
|
|
50
55
|
if (shape) {
|
|
51
|
-
details +=
|
|
56
|
+
details += `Shape: ${shape}\n`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Add variable name if available
|
|
60
|
+
if (variable_name) {
|
|
61
|
+
details += `Variable: ${variable_name}\n`;
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
// Add column information with sample values
|
|
54
65
|
if (columns && columns.length > 0) {
|
|
55
|
-
details +=
|
|
66
|
+
details += "Columns:\n";
|
|
67
|
+
for (const col of columns) {
|
|
68
|
+
let columnInfo = ` ${col.name} (${col.type})`;
|
|
69
|
+
|
|
70
|
+
// Add sample values if available
|
|
71
|
+
if (col.sample_values && col.sample_values.length > 0) {
|
|
72
|
+
const samples = col.sample_values
|
|
73
|
+
.slice(0, 3) // Limit to first 3 samples
|
|
74
|
+
.map((val) => (val === null ? "null" : String(val)))
|
|
75
|
+
.join(", ");
|
|
76
|
+
columnInfo += ` - samples: [${samples}]`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
details += `${columnInfo}\n`;
|
|
80
|
+
}
|
|
56
81
|
}
|
|
57
82
|
|
|
58
83
|
return contextToXml({
|
|
@@ -61,7 +86,7 @@ export class TableContextProvider extends AIContextProvider<TableContextItem> {
|
|
|
61
86
|
name: name,
|
|
62
87
|
source: source ?? "unknown",
|
|
63
88
|
},
|
|
64
|
-
details: details,
|
|
89
|
+
details: details.trim(),
|
|
65
90
|
});
|
|
66
91
|
}
|
|
67
92
|
|
|
@@ -78,7 +103,10 @@ export class TableContextProvider extends AIContextProvider<TableContextItem> {
|
|
|
78
103
|
: Boosts.REMOTE_TABLE,
|
|
79
104
|
type: getTableType(table),
|
|
80
105
|
apply: `@${tableName}`,
|
|
81
|
-
section:
|
|
106
|
+
section: {
|
|
107
|
+
name: getTableType(table) === "dataframe" ? "Dataframe" : "Table",
|
|
108
|
+
rank: Sections.TABLE.rank,
|
|
109
|
+
},
|
|
82
110
|
info: () => this.createTableInfoElement(tableName, table),
|
|
83
111
|
};
|
|
84
112
|
}
|
|
@@ -6,7 +6,7 @@ import type { DatasetTablesMap } from "@/core/datasets/data-source-connections";
|
|
|
6
6
|
import type { Variable, Variables } from "@/core/variables/types";
|
|
7
7
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
|
8
8
|
import { contextToXml } from "../utils";
|
|
9
|
-
import {
|
|
9
|
+
import { Sections } from "./common";
|
|
10
10
|
|
|
11
11
|
export interface VariableContextItem extends AIContextItem {
|
|
12
12
|
type: "variable";
|
|
@@ -58,9 +58,8 @@ export class VariableContextProvider extends AIContextProvider<VariableContextIt
|
|
|
58
58
|
label: `@${variable.name}`,
|
|
59
59
|
displayLabel: variable.name,
|
|
60
60
|
detail: variable.dataType ?? "",
|
|
61
|
-
boost: Boosts.VARIABLE,
|
|
62
61
|
type: this.contextType,
|
|
63
|
-
section:
|
|
62
|
+
section: Sections.VARIABLE,
|
|
64
63
|
info: () => {
|
|
65
64
|
return createVariableInfoElement(variable);
|
|
66
65
|
},
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
it,
|
|
13
13
|
vi,
|
|
14
14
|
} from "vitest";
|
|
15
|
-
import type { CellHandle } from "@/components/editor/
|
|
15
|
+
import type { CellHandle } from "@/components/editor/notebook-cell";
|
|
16
16
|
import { CellId } from "@/core/cells/ids";
|
|
17
17
|
import { foldAllBulk, unfoldAllBulk } from "@/core/codemirror/editing/commands";
|
|
18
18
|
import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
|
|
@@ -2094,9 +2094,9 @@ describe("cell reducer", () => {
|
|
|
2094
2094
|
`);
|
|
2095
2095
|
});
|
|
2096
2096
|
|
|
2097
|
-
it("can create and update a setup cell", () => {
|
|
2097
|
+
it("can create and noop-update a setup cell", () => {
|
|
2098
2098
|
// Create the setup cell
|
|
2099
|
-
actions.
|
|
2099
|
+
actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
|
|
2100
2100
|
|
|
2101
2101
|
// Check that setup cell was created
|
|
2102
2102
|
expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
|
|
@@ -2106,17 +2106,17 @@ describe("cell reducer", () => {
|
|
|
2106
2106
|
expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
|
|
2107
2107
|
|
|
2108
2108
|
// Update the setup cell
|
|
2109
|
-
actions.
|
|
2109
|
+
actions.addSetupCellIfDoesntExist({ code: "# Updated setup code" });
|
|
2110
2110
|
|
|
2111
2111
|
// Check that the same setup cell was updated, not duplicated
|
|
2112
|
-
expect(state.cellData[SETUP_CELL_ID].code).toBe("#
|
|
2112
|
+
expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
|
|
2113
2113
|
expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
|
|
2114
2114
|
expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
|
|
2115
2115
|
});
|
|
2116
2116
|
|
|
2117
2117
|
it("can delete and undelete the setup cell", () => {
|
|
2118
2118
|
// Create the setup cell
|
|
2119
|
-
actions.
|
|
2119
|
+
actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
|
|
2120
2120
|
|
|
2121
2121
|
// Check that setup cell was created
|
|
2122
2122
|
expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
|