create-theokit 1.23.3 → 1.23.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/package.json +1 -1
- package/templates/default/public/favicon.svg +7 -1
- package/templates/default/public/logo.png +0 -0
- package/templates/surfaces/desktop/frontend/src/App.tsx.tmpl +71 -3
- package/templates/surfaces/desktop/src-tauri/capabilities/default.json +7 -1
- package/templates/surfaces/desktop/src-tauri/src/lib.rs +17 -10
- package/templates/surfaces/desktop/src-tauri/tauri.conf.json.tmpl +2 -1
package/package.json
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024" role="img" aria-label="Theo">
|
|
2
|
+
<rect x="0" y="0" width="1024" height="1024" rx="205" ry="205" fill="#431A7A"/>
|
|
3
|
+
<g fill="#ffffff" fill-rule="evenodd" transform="translate(512,512) scale(0.66) translate(-512,-512)">
|
|
4
|
+
<path d="M193.9,195.9 L180.9,198.9 L162.0,209.9 L149.0,224.9 L142.0,240.9 L140.0,250.9 L140.0,369.7 L142.0,379.7 L155.0,403.6 L163.0,411.6 L180.9,421.6 L193.9,424.6 L354.7,425.6 L366.7,430.6 L376.7,442.6 L379.7,452.6 L379.7,777.1 L384.7,793.1 L390.7,803.1 L411.6,821.1 L434.6,828.1 L585.4,828.1 L595.4,826.1 L608.4,821.1 L627.3,805.1 L635.3,792.1 L640.3,773.1 L639.3,475.5 L631.3,458.6 L623.4,450.6 L608.4,442.6 L503.5,439.6 L503.5,304.8 L411.6,203.9 L403.6,198.9 L392.7,195.9 Z"/>
|
|
5
|
+
<path d="M629.3,195.9 L612.4,202.9 L519.5,304.8 L519.5,424.6 L830.1,424.6 L848.0,419.6 L861.0,411.6 L875.0,395.7 L880.0,385.7 L884.0,369.7 L884.0,250.9 L879.0,232.9 L869.0,216.9 L857.0,205.9 L843.1,198.9 L829.1,195.9 Z"/>
|
|
6
|
+
</g>
|
|
7
|
+
</svg>
|
|
Binary file
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from '@theokit/ui'
|
|
14
14
|
import { Button } from '@usetheo/ui'
|
|
15
15
|
import { createTauriChannelSource, type TauriCore } from '@theokit/tauri'
|
|
16
|
-
import { Bot, Plus, Sparkles } from 'lucide-react'
|
|
16
|
+
import { Bot, Minus, Plus, Sparkles, Square, X } from 'lucide-react'
|
|
17
17
|
import { ChannelTransport, useAgent } from 'theokit/client'
|
|
18
18
|
|
|
19
19
|
import { AGENT } from '../../shared/agent'
|
|
@@ -40,6 +40,26 @@ if (tauri === undefined) {
|
|
|
40
40
|
}
|
|
41
41
|
const transport = new ChannelTransport({ source: createTauriChannelSource(tauri.core) })
|
|
42
42
|
|
|
43
|
+
// The window is frameless (`decorations: false` in tauri.conf.json) so the OS title bar can't clash with
|
|
44
|
+
// the app background — the app owns its top bar. These drive the native window from our custom controls.
|
|
45
|
+
// (The drag + these calls need `core:window:*` permissions in `src-tauri/capabilities/default.json`.)
|
|
46
|
+
const appWindow = (
|
|
47
|
+
globalThis as {
|
|
48
|
+
__TAURI__?: {
|
|
49
|
+
window?: {
|
|
50
|
+
getCurrentWindow: () => {
|
|
51
|
+
minimize: () => Promise<void>
|
|
52
|
+
toggleMaximize: () => Promise<void>
|
|
53
|
+
close: () => Promise<void>
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
).__TAURI__?.window
|
|
59
|
+
const minimizeWindow = (): void => void appWindow?.getCurrentWindow().minimize()
|
|
60
|
+
const maximizeWindow = (): void => void appWindow?.getCurrentWindow().toggleMaximize()
|
|
61
|
+
const closeWindow = (): void => void appWindow?.getCurrentWindow().close()
|
|
62
|
+
|
|
43
63
|
/** The agent's opening line (from `shared/agent.ts`) — so the conversation starts warm instead of empty. */
|
|
44
64
|
const GREETING: UIMessage = {
|
|
45
65
|
id: 'greeting',
|
|
@@ -68,11 +88,27 @@ const styles = {
|
|
|
68
88
|
display: 'flex',
|
|
69
89
|
alignItems: 'center',
|
|
70
90
|
justifyContent: 'space-between',
|
|
91
|
+
// A faint gray wash (4% of the text color over the background) makes the frameless title bar
|
|
92
|
+
// read as a distinct strip in BOTH themes, without breaking the seamless feel.
|
|
93
|
+
background: 'hsl(var(--foreground) / 0.04)',
|
|
71
94
|
borderBottom: '1px solid hsl(var(--border) / 0.6)',
|
|
72
95
|
padding: '8px 16px',
|
|
73
96
|
},
|
|
74
97
|
brand: { display: 'inline-flex', alignItems: 'center', gap: 8 },
|
|
75
98
|
brandName: { fontWeight: 600, fontSize: 13, letterSpacing: '-0.01em' },
|
|
99
|
+
headerRight: { display: 'inline-flex', alignItems: 'center', gap: 4 },
|
|
100
|
+
winBtn: {
|
|
101
|
+
display: 'inline-flex',
|
|
102
|
+
alignItems: 'center',
|
|
103
|
+
justifyContent: 'center',
|
|
104
|
+
width: 28,
|
|
105
|
+
height: 28,
|
|
106
|
+
border: 'none',
|
|
107
|
+
borderRadius: 6,
|
|
108
|
+
background: 'transparent',
|
|
109
|
+
color: 'hsl(var(--muted-foreground))',
|
|
110
|
+
cursor: 'pointer',
|
|
111
|
+
},
|
|
76
112
|
scroll: { flex: 1, overflowY: 'auto', minHeight: 0 },
|
|
77
113
|
column: {
|
|
78
114
|
margin: '0 auto',
|
|
@@ -116,12 +152,44 @@ export function App(): ReactElement {
|
|
|
116
152
|
return (
|
|
117
153
|
<TheoUIProvider>
|
|
118
154
|
<div style={styles.page}>
|
|
119
|
-
|
|
155
|
+
{/* Frameless title bar: `data-tauri-drag-region` makes the whole strip drag the window; the
|
|
156
|
+
background is the app background (with a faint wash), so the bar reads as one with the
|
|
157
|
+
content. Interactive children (theme switcher, window buttons) still receive clicks. */}
|
|
158
|
+
<header style={styles.header} data-tauri-drag-region>
|
|
120
159
|
<span style={styles.brand}>
|
|
121
160
|
<Bot size={18} style={{ color: 'hsl(var(--primary))' }} aria-hidden />
|
|
122
161
|
<span style={styles.brandName}>{{name}} — TheoKit agent</span>
|
|
123
162
|
</span>
|
|
124
|
-
<
|
|
163
|
+
<span style={styles.headerRight}>
|
|
164
|
+
<ThemeSwitcher />
|
|
165
|
+
<button
|
|
166
|
+
type="button"
|
|
167
|
+
style={styles.winBtn}
|
|
168
|
+
onClick={minimizeWindow}
|
|
169
|
+
aria-label="Minimize"
|
|
170
|
+
title="Minimize"
|
|
171
|
+
>
|
|
172
|
+
<Minus size={16} />
|
|
173
|
+
</button>
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
style={styles.winBtn}
|
|
177
|
+
onClick={maximizeWindow}
|
|
178
|
+
aria-label="Maximize"
|
|
179
|
+
title="Maximize"
|
|
180
|
+
>
|
|
181
|
+
<Square size={13} />
|
|
182
|
+
</button>
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
style={styles.winBtn}
|
|
186
|
+
onClick={closeWindow}
|
|
187
|
+
aria-label="Close"
|
|
188
|
+
title="Close"
|
|
189
|
+
>
|
|
190
|
+
<X size={16} />
|
|
191
|
+
</button>
|
|
192
|
+
</span>
|
|
125
193
|
</header>
|
|
126
194
|
|
|
127
195
|
<div style={styles.scroll}>
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "../gen/schemas/desktop-schema.json",
|
|
3
3
|
"identifier": "default",
|
|
4
|
-
"description": "Allow the webview to run the agent turn + settle HITL via the sidecar.",
|
|
4
|
+
"description": "Allow the webview to run the agent turn + settle HITL via the sidecar, and drive the frameless window (drag + minimize/maximize/close).",
|
|
5
5
|
"windows": ["main"],
|
|
6
6
|
"permissions": [
|
|
7
7
|
"core:default",
|
|
8
|
+
"core:window:allow-start-dragging",
|
|
9
|
+
"core:window:allow-minimize",
|
|
10
|
+
"core:window:allow-maximize",
|
|
11
|
+
"core:window:allow-unmaximize",
|
|
12
|
+
"core:window:allow-toggle-maximize",
|
|
13
|
+
"core:window:allow-close",
|
|
8
14
|
"shell:allow-spawn",
|
|
9
15
|
{
|
|
10
16
|
"identifier": "shell:allow-execute",
|
|
@@ -29,6 +29,9 @@ async fn run_turn(
|
|
|
29
29
|
.shell()
|
|
30
30
|
.sidecar("theo-sidecar")
|
|
31
31
|
.map_err(|e| e.to_string())?
|
|
32
|
+
// Forward this process's environment (the provider key — OPENROUTER_API_KEY /
|
|
33
|
+
// ANTHROPIC_API_KEY — lives here) so the sidecar can authenticate.
|
|
34
|
+
.envs(std::env::vars())
|
|
32
35
|
.args([message])
|
|
33
36
|
.spawn()
|
|
34
37
|
.map_err(|e| e.to_string())?;
|
|
@@ -41,18 +44,22 @@ async fn run_turn(
|
|
|
41
44
|
*guard = Some(child);
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
// AWAIT the stream inline — do NOT spawn a detached task and return early. The webview's
|
|
48
|
+
// `ChannelTransport` calls `invoke('run_turn').then(onClose)`, and `onClose` CLOSES the stream
|
|
49
|
+
// (further chunks are dropped). If `run_turn` resolves before the sidecar's chunks are delivered
|
|
50
|
+
// over the Channel, every chunk lands after the stream is closed → the agent renders no reply.
|
|
51
|
+
// Resolving only after the sidecar terminates makes `onClose` fire AFTER the last chunk. HITL is
|
|
52
|
+
// unaffected: `approve` writes to the child's stdin via shared state while this loop awaits.
|
|
53
|
+
while let Some(event) = rx.recv().await {
|
|
54
|
+
match event {
|
|
55
|
+
CommandEvent::Stdout(bytes) => {
|
|
56
|
+
let line = String::from_utf8_lossy(&bytes).to_string();
|
|
57
|
+
let _ = on_chunk.send(line);
|
|
53
58
|
}
|
|
59
|
+
CommandEvent::Terminated(_) => break,
|
|
60
|
+
_ => {}
|
|
54
61
|
}
|
|
55
|
-
}
|
|
62
|
+
}
|
|
56
63
|
Ok(())
|
|
57
64
|
}
|
|
58
65
|
|