Skip to content

Commit d1937ff

Browse files
authored
feat: SQLRooms CLI (#263)
* feat: SQLRooms CLI Signed-off-by: Ilya Boyandin <[email protected]> * remove sqlrooms.db and .wal Signed-off-by: Ilya Boyandin <[email protected]> * gitignore Signed-off-by: Ilya Boyandin <[email protected]> * saving data Signed-off-by: Ilya Boyandin <[email protected]> * :memory: works as db-path Signed-off-by: Ilya Boyandin <[email protected]> * readme Signed-off-by: Ilya Boyandin <[email protected]> * sqlrooms-server, sqlrooms-cli Signed-off-by: Ilya Boyandin <[email protected]> * _pick_free_port Signed-off-by: Ilya Boyandin <[email protected]> * sqlrooms-cli-ui Signed-off-by: Ilya Boyandin <[email protected]> * New CLI flags in sqlrooms for sync Signed-off-by: Ilya Boyandin <[email protected]> * npm scripts Signed-off-by: Ilya Boyandin <[email protected]> * knip fixes Signed-off-by: Ilya Boyandin <[email protected]> * prepare for publishing Signed-off-by: Ilya Boyandin <[email protected]> * adding tests, ruff checks Signed-off-by: Ilya Boyandin <[email protected]> * fix: sqlrooms/web/static wasn't included Signed-off-by: Ilya Boyandin <[email protected]> * fetch runtimeConfig Signed-off-by: Ilya Boyandin <[email protected]> * storage Signed-off-by: Ilya Boyandin <[email protected]> * add prepush typecheck Signed-off-by: Ilya Boyandin <[email protected]> * removed unused var Signed-off-by: Ilya Boyandin <[email protected]> --------- Signed-off-by: Ilya Boyandin <[email protected]>
1 parent ab1d6c1 commit d1937ff

101 files changed

Lines changed: 5315 additions & 203 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/pr-checks.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ jobs:
7272
- name: Build packages
7373
run: pnpm build
7474

75+
- name: Build apps
76+
run: pnpm build:apps
77+
7578
- name: Build examples
7679
run: pnpm build:examples:ci
7780

.husky/pre-push

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pnpm typecheck
12
pnpm test
23

34
# Git LFS pre-push hook

CODE_OF_CONDUCT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ specific emails that can be used to report and escalate issues.
1717
### Project Spaces
1818

1919
For reporting issues in spaces related to SQLRooms please use the email
20-
`ilya@boyandin.me`. SQLRooms handles CoC issues
20+
`ilya@sqlrooms.org`. SQLRooms handles CoC issues
2121
related to the spaces that it maintains. Projects maintainers commit to:
2222

2323
- maintain the confidentiality with regard to the reporter of an incident

LICENSE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright 2025 Ilya Boyandin
3+
Copyright 2025 SQLRooms Contributors
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
66

apps/sqlrooms-cli-ui/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# SQLRooms CLI UI
2+
3+
This package is the **Vite/React UI** that powers the Python `sqlrooms` CLI (`python/sqlrooms-cli`).
4+
5+
In development it runs as a separate dev server (default `http://localhost:4174`) and **proxies API calls** to the Python server (default `http://localhost:4173`).
6+
7+
In production (published `sqlrooms` wheel), the UI is served as **static assets** bundled into the Python package at:
8+
9+
- `python/sqlrooms-cli/sqlrooms/web/static/`
10+
11+
## Dev mode (UI + Python server together)
12+
13+
From `python/sqlrooms-cli`:
14+
15+
```bash
16+
pnpm dev
17+
```
18+
19+
This starts:
20+
21+
- the Python server on `http://127.0.0.1:4173`
22+
- the Vite UI on `http://localhost:4174` (proxying `/api` and `/config.json` to 4173)
23+
24+
If you hit `address already in use`, pass different ports to the Python server:
25+
26+
```bash
27+
pnpm dev:server -- --port 4176 --ws-port 4002
28+
```
29+
30+
Then also update the Vite proxy target in `vite.config.ts` (or run Vite with a different proxy setup).
31+
32+
## Dev mode (separate terminals)
33+
34+
Terminal A (Python server):
35+
36+
```bash
37+
cd python/sqlrooms-cli
38+
pnpm dev:server
39+
```
40+
41+
Terminal B (UI):
42+
43+
```bash
44+
pnpm --filter sqlrooms-cli-app dev -- --host --port 4174
45+
```
46+
47+
## Build the bundled UI (for the Python wheel)
48+
49+
From the repo root:
50+
51+
```bash
52+
pnpm --filter sqlrooms-cli-app build
53+
```
54+
55+
This builds the UI into `apps/sqlrooms-cli-ui/dist`.
56+
57+
To copy it into the Python package bundle directory (so the published `sqlrooms` wheel can serve it), run:
58+
59+
```bash
60+
cd python/sqlrooms-cli
61+
pnpm build:ui
62+
```

apps/sqlrooms-cli-ui/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>SQLRooms CLI App</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.tsx"></script>
11+
</body>
12+
</html>
13+

apps/sqlrooms-cli-ui/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "sqlrooms-cli-app",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc -b && vite build",
9+
"preview": "vite preview",
10+
"lint": "eslint .",
11+
"typecheck": "tsc --noEmit"
12+
},
13+
"dependencies": {
14+
"@sqlrooms/ai": "workspace:*",
15+
"@sqlrooms/dropzone": "workspace:*",
16+
"@sqlrooms/duckdb": "workspace:*",
17+
"@sqlrooms/room-shell": "workspace:*",
18+
"@sqlrooms/sql-editor": "workspace:*",
19+
"@sqlrooms/ui": "workspace:*",
20+
"@sqlrooms/utils": "workspace:*",
21+
"@sqlrooms/vega": "workspace:*",
22+
"lucide-react": "^0.555.0",
23+
"zod": "^4.1.8",
24+
"zustand": "^5.0.8",
25+
"react": "^19.2.0",
26+
"react-dom": "^19.2.0"
27+
},
28+
"devDependencies": {
29+
"@types/react": "^19.2.7",
30+
"@types/react-dom": "^19.2.3",
31+
"@vitejs/plugin-react": "^5.1.2",
32+
"autoprefixer": "^10.4.21",
33+
"eslint": "^9.36.0",
34+
"postcss": "^8.5.6",
35+
"tailwindcss": "^3.4.17",
36+
"typescript": "^5.9.2",
37+
"vite": "^7.1.9"
38+
},
39+
"overrides": {
40+
"@duckdb/duckdb-wasm": "1.31.0"
41+
}
42+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
};
7+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {RoomPanel} from '@sqlrooms/room-shell';
2+
import {TableStructurePanel} from '@sqlrooms/sql-editor';
3+
import {FileDropzone} from '@sqlrooms/dropzone';
4+
import {convertToValidColumnOrTableName} from '@sqlrooms/utils';
5+
import {useToast} from '@sqlrooms/ui';
6+
import {useRoomStore, RoomPanelTypes} from '../store';
7+
8+
export const DataSourcesPanel = () => {
9+
const connector = useRoomStore((state) => state.db.connector);
10+
const refreshTableSchemas = useRoomStore(
11+
(state) => state.db.refreshTableSchemas,
12+
);
13+
const {toast} = useToast();
14+
15+
return (
16+
<RoomPanel type={RoomPanelTypes.enum['data-sources']}>
17+
<FileDropzone
18+
className="h-[200px] p-5"
19+
acceptedFormats={{
20+
'text/csv': ['.csv'],
21+
'text/tsv': ['.tsv'],
22+
'text/parquet': ['.parquet'],
23+
'text/json': ['.json'],
24+
}}
25+
onDrop={async (files) => {
26+
for (const file of files) {
27+
try {
28+
const tableName = convertToValidColumnOrTableName(file.name);
29+
await connector.loadFile(file, tableName);
30+
toast({
31+
variant: 'default',
32+
title: 'Table created',
33+
description: `File ${file.name} loaded as ${tableName}`,
34+
});
35+
} catch (error) {
36+
toast({
37+
variant: 'destructive',
38+
title: 'Error',
39+
description: `Error loading file ${file.name}: ${error}`,
40+
});
41+
}
42+
}
43+
await refreshTableSchemas();
44+
}}
45+
>
46+
<div className="text-xs text-muted-foreground">
47+
Files you add will stay local to your browser.
48+
</div>
49+
</FileDropzone>
50+
<TableStructurePanel />
51+
</RoomPanel>
52+
);
53+
};
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import {
2+
AiSettingsPanel,
3+
AnalysisResultsContainer,
4+
ModelSelector,
5+
QueryControls,
6+
SessionControls,
7+
PromptSuggestions,
8+
} from '@sqlrooms/ai';
9+
import {
10+
Button,
11+
Dialog,
12+
DialogContent,
13+
DialogHeader,
14+
DialogTitle,
15+
DialogTrigger,
16+
SkeletonPane,
17+
Tabs,
18+
TabsContent,
19+
TabsList,
20+
TabsTrigger,
21+
useDisclosure,
22+
} from '@sqlrooms/ui';
23+
import {Settings} from 'lucide-react';
24+
import {useRoomStore} from '../store';
25+
26+
export const MainView: React.FC = () => {
27+
const currentSessionId = useRoomStore(
28+
(s) => s.ai.config.currentSessionId || null,
29+
);
30+
const isDataAvailable = useRoomStore((state) => state.room.initialized);
31+
32+
const settingsPanelOpen = useDisclosure();
33+
34+
return (
35+
<div className="flex h-full w-full flex-col gap-0 overflow-hidden p-4">
36+
<div className="mb-4 flex items-center justify-between gap-2">
37+
<SessionControls className="w-full" />
38+
{currentSessionId && (
39+
<Dialog
40+
open={settingsPanelOpen.isOpen}
41+
onOpenChange={(open) => {
42+
if (open) {
43+
settingsPanelOpen.onOpen();
44+
} else {
45+
settingsPanelOpen.onClose();
46+
}
47+
}}
48+
>
49+
<DialogTrigger asChild>
50+
<Button
51+
variant="outline"
52+
className="flex items-center justify-center transition-colors hover:bg-accent"
53+
title="Configuration"
54+
size="sm"
55+
>
56+
<Settings className="h-4 w-4" />
57+
</Button>
58+
</DialogTrigger>
59+
<DialogContent className="flex h-[80vh] w-[90vw] max-w-3xl flex-col overflow-hidden">
60+
<DialogHeader>
61+
<DialogTitle>AI Assistant Settings</DialogTitle>
62+
</DialogHeader>
63+
<Tabs
64+
defaultValue="providers"
65+
className="flex min-h-0 flex-1 flex-col"
66+
>
67+
<TabsList className="grid w-full flex-shrink-0 grid-cols-3">
68+
<TabsTrigger value="providers">Providers</TabsTrigger>
69+
<TabsTrigger value="models">Models</TabsTrigger>
70+
<TabsTrigger value="parameters">Parameters</TabsTrigger>
71+
</TabsList>
72+
<TabsContent
73+
value="providers"
74+
className="flex-1 overflow-y-auto"
75+
>
76+
<AiSettingsPanel.ProvidersSettings />
77+
</TabsContent>
78+
<TabsContent value="models" className="flex-1 overflow-y-auto">
79+
<AiSettingsPanel.ModelsSettings />
80+
</TabsContent>
81+
<TabsContent
82+
value="parameters"
83+
className="flex-1 overflow-y-auto"
84+
>
85+
<AiSettingsPanel.ModelParametersSettings />
86+
</TabsContent>
87+
</Tabs>
88+
</DialogContent>
89+
</Dialog>
90+
)}
91+
</div>
92+
<div className="print-container flex-grow overflow-auto">
93+
{!currentSessionId ? (
94+
<div className="flex h-full w-full flex-col items-center justify-center">
95+
<p className="mt-4 text-muted-foreground">No session selected</p>
96+
</div>
97+
) : isDataAvailable ? (
98+
<AnalysisResultsContainer key={currentSessionId} />
99+
) : (
100+
<div className="flex h-full w-full flex-col items-center justify-center">
101+
<SkeletonPane className="p-4" />
102+
<p className="mt-4 text-muted-foreground">Loading database...</p>
103+
</div>
104+
)}
105+
</div>{' '}
106+
<PromptSuggestions.Container>
107+
<PromptSuggestions.Item text="What questions can I ask to get insights from my data?" />
108+
<PromptSuggestions.Item text="Show me a summary of the data" />
109+
<PromptSuggestions.Item text="What are the key trends?" />
110+
<PromptSuggestions.Item text="Help me understand the data structure" />
111+
</PromptSuggestions.Container>
112+
<QueryControls placeholder="What would you like to learn about the data?">
113+
<div className="flex items-center justify-end gap-2">
114+
<PromptSuggestions.VisibilityToggle />
115+
<ModelSelector />
116+
</div>
117+
</QueryControls>
118+
</div>
119+
);
120+
};

0 commit comments

Comments
 (0)