Skip to content

Commit 7b655bd

Browse files
authored
Tinybase & ElectricSQL experiment (#1518)
1 parent 24b4d29 commit 7b655bd

Some content is hidden

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

49 files changed

+2962
-213
lines changed

.drizzle/store.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"id": "4f19233e-87d1-4523-aa45-ba9506810177",
3+
"name": "dev",
4+
"slots": [
5+
[
6+
"593b0317-c4ea-46a7-a9ba-a41688d84085",
7+
{
8+
"id": "593b0317-c4ea-46a7-a9ba-a41688d84085",
9+
"name": "dev",
10+
"dialect": "postgresql",
11+
"credentials": {
12+
"host": "postgres",
13+
"port": "5432",
14+
"user": "postgres",
15+
"password": "password",
16+
"database": "electric",
17+
"ssl": false
18+
}
19+
}
20+
]
21+
]
22+
}

Cargo.lock

Lines changed: 24 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop2/package.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,43 @@
77
"dev": "vite",
88
"build": "tsc && vite build",
99
"preview": "vite preview",
10-
"tauri": "tauri"
10+
"tauri": "tauri",
11+
"typecheck": "tsc --noEmit",
12+
"test": "vitest run"
1113
},
1214
"dependencies": {
1315
"@electric-sql/client": "^1.0.10",
1416
"@hypr/db": "workspace:*",
1517
"@hypr/plugin-db2": "workspace:*",
18+
"@hypr/plugin-windows": "workspace:*",
19+
"@hypr/tiptap": "workspace:^",
20+
"@hypr/ui": "workspace:^",
21+
"@hypr/utils": "workspace:^",
1622
"@tanstack/react-router": "^1.131.35",
23+
"@tanstack/react-virtual": "^3.13.12",
1724
"@tanstack/zod-adapter": "^1.131.35",
1825
"@tauri-apps/api": "^2",
1926
"@tauri-apps/plugin-opener": "^2",
27+
"date-fns": "^4.1.0",
2028
"react": "^18.3.1",
2129
"react-dom": "^18.3.1",
2230
"tinybase": "^6.6.1",
2331
"zod": "^4.1.11"
2432
},
2533
"devDependencies": {
34+
"@faker-js/faker": "^10.0.0",
2635
"@tanstack/react-router-devtools": "^1.131.35",
2736
"@tanstack/router-plugin": "^1.131.35",
2837
"@tauri-apps/cli": "^2",
2938
"@types/node": "^24.6.0",
3039
"@types/react": "^18.3.1",
3140
"@types/react-dom": "^18.3.1",
3241
"@vitejs/plugin-react": "^4.6.0",
42+
"autoprefixer": "^10.4.21",
43+
"postcss": "^8.5.6",
44+
"tailwindcss": "^3.4.17",
3345
"typescript": "~5.8.3",
34-
"vite": "^7.0.4"
46+
"vite": "^7.0.4",
47+
"vitest": "^3.2.4"
3548
}
3649
}

apps/desktop2/postcss.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "@hypr/ui/postcss.config";

apps/desktop2/src-tauri/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ tauri = { workspace = true, features = ["specta", "macos-private-api"] }
2222

2323
tauri-plugin-analytics = { workspace = true }
2424
tauri-plugin-db2 = { workspace = true }
25+
tauri-plugin-listener = { workspace = true }
26+
tauri-plugin-local-stt = { workspace = true }
2527
tauri-plugin-opener = { workspace = true }
2628
tauri-plugin-sentry = { workspace = true }
2729
tauri-plugin-single-instance = { workspace = true }
@@ -35,6 +37,7 @@ tauri-specta = { workspace = true, features = ["derive", "typescript"] }
3537
sentry = { workspace = true, features = ["tracing"] }
3638
serde = { workspace = true, features = ["derive"] }
3739
serde_json = { workspace = true }
40+
tracing = { workspace = true }
3841

3942
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
4043

apps/desktop2/src-tauri/capabilities/default.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"$schema": "../gen/schemas/desktop-schema.json",
33
"identifier": "default",
44
"description": "Capability for the main window",
5-
"windows": ["main"],
5+
"windows": ["*"],
66
"permissions": [
77
"core:default",
88
"opener:default",

apps/desktop2/src-tauri/src/lib.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ pub async fn main() {
4040
.plugin(tauri_plugin_opener::init())
4141
.plugin(tauri_plugin_analytics::init())
4242
.plugin(tauri_plugin_db2::init())
43+
.plugin(tauri_plugin_listener::init())
44+
.plugin(tauri_plugin_local_stt::init())
4345
.plugin(tauri_plugin_store::Builder::default().build())
4446
.plugin(tauri_plugin_windows::init());
4547

@@ -60,6 +62,30 @@ pub async fn main() {
6062
.setup(move |app| {
6163
let app = app.handle().clone();
6264

65+
let app_clone = app.clone();
66+
67+
let postgres_url = {
68+
#[cfg(debug_assertions)]
69+
{
70+
"postgresql://postgres:password@localhost:54321/electric"
71+
}
72+
#[cfg(not(debug_assertions))]
73+
{
74+
env!("POSTGRES_URL").to_string()
75+
}
76+
};
77+
78+
tokio::spawn(async move {
79+
use tauri_plugin_db2::Database2PluginExt;
80+
81+
if let Err(e) = app_clone.init_local().await {
82+
tracing::error!("failed_to_init_local: {}", e);
83+
}
84+
if let Err(e) = app_clone.init_cloud(postgres_url).await {
85+
tracing::error!("failed_to_init_cloud: {}", e);
86+
}
87+
});
88+
6389
specta_builder.mount_events(&app);
6490
Ok(())
6591
})
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { useCallback } from "react";
2+
3+
import * as memory from "../tinybase/store/memory";
4+
import * as persisted from "../tinybase/store/persisted";
5+
import { id } from "../utils";
6+
7+
export function Chat() {
8+
const currentChatGroupId = memory.useCurrentChatGroupId();
9+
const chatGroupIds = persisted.UI.useSortedRowIds("chat_groups", "created_at", false, 0, 5, persisted.STORE_ID);
10+
11+
const setCurrentChatGroupId = memory.UI.useSetValueCallback(
12+
"current_chat_group_id",
13+
(e: string) => e,
14+
[],
15+
memory.STORE_ID,
16+
);
17+
18+
const handleAddMessage = persisted.UI.useSetRowCallback(
19+
"chat_messages",
20+
id(),
21+
(row: persisted.ChatMessage) => ({
22+
...row,
23+
metadata: JSON.stringify(row.metadata),
24+
parts: JSON.stringify(row.parts),
25+
} satisfies persisted.ChatMessage),
26+
[],
27+
persisted.STORE_ID,
28+
);
29+
30+
const handleSubmitMessage = useCallback((e: React.FormEvent<HTMLFormElement>) => {
31+
e.preventDefault();
32+
33+
const formData = new FormData(e.currentTarget);
34+
const message = formData.get("message");
35+
if (message) {
36+
handleAddMessage({
37+
user_id: "TODO",
38+
chat_group_id: currentChatGroupId,
39+
role: "user",
40+
content: "TODO",
41+
metadata: "TODO",
42+
parts: JSON.stringify([]),
43+
created_at: new Date().toISOString(),
44+
});
45+
}
46+
}, [handleAddMessage, currentChatGroupId]);
47+
48+
const messageIds = persisted.UI.useSliceRowIds(
49+
persisted.INDEXES.chatMessagesByGroup,
50+
currentChatGroupId,
51+
persisted.STORE_ID,
52+
);
53+
54+
if (!currentChatGroupId || !messageIds?.length) {
55+
return (
56+
<div className="border border-gray-300 rounded p-2">
57+
<div className="text-gray-500">Select or create a chat group</div>
58+
59+
<div className="flex flex-col gap-2">
60+
{chatGroupIds?.map((chatGroupId) => (
61+
<ChatGroup
62+
key={chatGroupId}
63+
id={chatGroupId}
64+
handleClick={() => setCurrentChatGroupId(chatGroupId)}
65+
/>
66+
))}
67+
</div>
68+
</div>
69+
);
70+
}
71+
72+
return (
73+
<div className="border border-gray-300 rounded p-2">
74+
<button onClick={() => setCurrentChatGroupId("")}>
75+
reset
76+
</button>
77+
78+
<div className="space-y-2">
79+
{messageIds?.map((messageId) => <ChatMessage key={messageId} messageId={messageId} />)}
80+
</div>
81+
82+
<form onSubmit={handleSubmitMessage}>
83+
<input
84+
name="message"
85+
type="text"
86+
className="border border-gray-300 rounded p-2"
87+
/>
88+
<button className="border border-gray-300 rounded p-2">Send</button>
89+
</form>
90+
</div>
91+
);
92+
}
93+
94+
function ChatGroup({ id, handleClick }: { id: string; handleClick: () => void }) {
95+
const chatGroup = persisted.UI.useRow("chat_groups", id, persisted.STORE_ID);
96+
97+
return (
98+
<div className="p-2 rounded bg-gray-50" onClick={handleClick}>
99+
<div className="text-xs text-gray-500 mb-1">{chatGroup?.title}</div>
100+
</div>
101+
);
102+
}
103+
104+
function ChatMessage({ messageId }: { messageId: string }) {
105+
const message = persisted.UI.useRow("chat_messages", messageId, persisted.STORE_ID);
106+
107+
return (
108+
<div className="p-2 rounded bg-gray-50">
109+
<div className="text-xs text-gray-500 mb-1">{message?.role}</div>
110+
<div>{message?.content}</div>
111+
</div>
112+
);
113+
}

0 commit comments

Comments
 (0)