@dyyz1993/create-agent 1.9.0 → 2.0.1
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 +23 -23
- package/src/lib/copy.ts +260 -245
- package/templates/agent/eslint.config.mjs +14 -0
- package/templates/agent/package.json +8 -1
- package/templates/agent/src/__tests__/hybrid-mode.test.ts +126 -0
- package/templates/agent/src/__tests__/server-config-security.test.ts +55 -0
- package/templates/agent/src/bun/index.ts +96 -41
- package/templates/agent/src/bun/three.d.ts +1 -0
- package/templates/agent/src/gateway/__tests__/ws-handler-token.test.ts +118 -0
- package/templates/agent/src/gateway/http-routes.ts +1 -1
- package/templates/agent/src/gateway/ws-handler.ts +84 -56
- package/templates/agent/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
- package/templates/agent/src/mainview/__tests__/setup.ts +32 -29
- package/templates/agent/src/mainview/__tests__/theme-variables.test.ts +36 -0
- package/templates/agent/src/mainview/components/common/ErrorBoundary.tsx +1 -58
- package/templates/agent/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
- package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +1 -0
- package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +232 -210
- package/templates/agent/src/mainview/components/layout/AppLayout.tsx +3 -1
- package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +149 -122
- package/templates/agent/src/mainview/lib/api-client.ts +182 -172
- package/templates/agent/src/server-config.ts +34 -32
- package/templates/agent/src/server.ts +43 -54
- package/templates/agent/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
- package/templates/agent/src/shared/handlers/__tests__/handler-isolation.test.ts +244 -0
- package/templates/agent/src/shared/handlers/bash.ts +65 -65
- package/templates/agent/src/shared/handlers/chat.ts +189 -155
- package/templates/agent/src/shared/handlers/debug.ts +8 -0
- package/templates/agent/src/shared/handlers/feed.ts +31 -32
- package/templates/agent/src/shared/handlers/index.ts +1 -0
- package/templates/agent/src/shared/handlers/rules.ts +31 -31
- package/templates/agent/src/shared/handlers/todo.ts +31 -31
- package/templates/agent/src/shared/http-routes.ts +250 -0
- package/templates/agent/src/shared/lib/__tests__/web-server.test.ts +190 -0
- package/templates/agent/src/shared/lib/logger.ts +117 -95
- package/templates/agent/src/shared/lib/web-server.ts +128 -0
- package/templates/agent/src/shared/modules/debug.ts +8 -0
- package/templates/agent/src/shared/rpc-schema.ts +16 -3
- package/templates/agent/test-upload.txt +0 -0
- package/templates/agent/tsconfig.ipc.json +10 -0
- package/templates/agent/tsconfig.json +10 -2
- package/templates/chat/eslint.config.mjs +14 -0
- package/templates/chat/package.json +8 -1
- package/templates/chat/src/__tests__/hybrid-mode.test.ts +125 -0
- package/templates/chat/src/__tests__/server-config-security.test.ts +55 -0
- package/templates/chat/src/__tests__/server-config.test.ts +46 -44
- package/templates/chat/src/bun/index.ts +94 -44
- package/templates/chat/src/bun/three.d.ts +1 -0
- package/templates/chat/src/gateway/http-routes.ts +1 -1
- package/templates/chat/src/gateway/ws-handler.ts +84 -56
- package/templates/chat/src/mainview/App.tsx +6 -0
- package/templates/chat/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
- package/templates/chat/src/mainview/__tests__/theme-variables.test.ts +36 -0
- package/templates/chat/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
- package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +1 -0
- package/templates/chat/src/mainview/lib/api-client.ts +183 -156
- package/templates/chat/src/mainview/lib/rpc-cache.ts +84 -0
- package/templates/chat/src/mainview/main.tsx +4 -3
- package/templates/chat/src/server-config.ts +33 -31
- package/templates/chat/src/server.ts +43 -60
- package/templates/chat/src/shared/handlers/chat.ts +196 -162
- package/templates/chat/src/shared/handlers/debug.ts +8 -0
- package/templates/chat/src/shared/handlers/index.ts +1 -0
- package/templates/chat/src/shared/http-routes.ts +250 -0
- package/templates/chat/src/shared/lib/__tests__/logger.test.ts +92 -0
- package/templates/chat/src/shared/lib/logger.ts +108 -95
- package/templates/chat/src/shared/lib/web-server.ts +128 -0
- package/templates/chat/src/shared/modules/debug.ts +8 -0
- package/templates/chat/src/shared/rpc-schema.ts +3 -2
- package/templates/chat/test-upload.txt +0 -0
- package/templates/chat/tsconfig.ipc.json +10 -0
- package/templates/general/eslint.config.mjs +14 -0
- package/templates/general/package.json +8 -1
- package/templates/general/src/__tests__/hybrid-mode.test.ts +125 -0
- package/templates/general/src/__tests__/server-config-security.test.ts +55 -0
- package/templates/general/src/__tests__/server-config.test.ts +46 -44
- package/templates/general/src/bun/index.ts +94 -44
- package/templates/general/src/bun/three.d.ts +1 -0
- package/templates/general/src/gateway/http-routes.ts +1 -1
- package/templates/general/src/gateway/ws-handler.ts +84 -56
- package/templates/general/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
- package/templates/general/src/mainview/__tests__/theme-variables.test.ts +36 -0
- package/templates/general/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
- package/templates/general/src/mainview/components/common/ThemeToggle.tsx +1 -0
- package/templates/general/src/mainview/components/feed/FeedPanel.tsx +241 -219
- package/templates/general/src/mainview/components/layout/AppLayout.tsx +6 -0
- package/templates/general/src/mainview/lib/api-client.ts +183 -156
- package/templates/general/src/mainview/lib/rpc-cache.ts +84 -0
- package/templates/general/src/mainview/main.tsx +4 -3
- package/templates/general/src/server-config.ts +33 -31
- package/templates/general/src/server.ts +43 -60
- package/templates/general/src/shared/handlers/__tests__/chat.test.ts +165 -160
- package/templates/general/src/shared/handlers/chat.ts +189 -155
- package/templates/general/src/shared/handlers/debug.ts +8 -0
- package/templates/general/src/shared/handlers/feed.ts +38 -40
- package/templates/general/src/shared/handlers/index.ts +1 -0
- package/templates/general/src/shared/http-routes.ts +250 -0
- package/templates/general/src/shared/lib/__tests__/logger.test.ts +92 -0
- package/templates/general/src/shared/lib/logger.ts +117 -95
- package/templates/general/src/shared/lib/web-server.ts +128 -0
- package/templates/general/src/shared/modules/debug.ts +8 -0
- package/templates/general/src/shared/rpc-schema.ts +12 -2
- package/templates/general/test-upload.txt +0 -0
- package/templates/general/tsconfig.ipc.json +10 -0
- package/templates/shared/__tests__/cors-security.test.ts +165 -0
- package/templates/shared/__tests__/file-path-security.test.ts +260 -0
- package/templates/shared/__tests__/http-routes.test.ts +174 -0
- package/templates/shared/__tests__/logger.test.ts +186 -0
- package/templates/shared/components/ErrorBoundary.tsx +58 -0
- package/templates/shared/env.d.ts +11 -0
- package/templates/shared/http-routes.ts +230 -0
- package/templates/shared/logger.ts +129 -0
- package/templates/shared/test-upload.txt +0 -0
- package/templates/shared/vite-base.config.ts +92 -0
- package/templates/shared/vitest-base.config.ts +42 -0
- package/templates/agent/bun.lock +0 -1279
- package/templates/agent/package-lock.json +0 -3670
- package/templates/chat/bun.lock +0 -1203
- package/templates/chat/package-lock.json +0 -3670
- package/templates/general/bun.lock +0 -1266
- package/templates/general/package-lock.json +0 -3670
|
@@ -5,244 +5,266 @@ import { apiClient } from "../../lib/api-client";
|
|
|
5
5
|
import { useLogStore } from "../../stores/use-log-store";
|
|
6
6
|
import { useConnectionStore } from "../../stores/use-connection-store";
|
|
7
7
|
import { useFeedStore, useEventStreamStore, type SubEventType } from "../../stores/use-feed-store";
|
|
8
|
-
import type { FeedCategory } from "
|
|
8
|
+
import type { FeedCategory } from "@shared/modules/feed";
|
|
9
9
|
|
|
10
10
|
const CATEGORIES: FeedCategory[] = ["tech", "news", "general"];
|
|
11
11
|
const EVENT_TYPES: SubEventType[] = ["feed.update", "chat.message", "timer.tick"];
|
|
12
12
|
const FILTER_PRESETS: Record<SubEventType, string[]> = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
"feed.update": ["", '{"category":"tech"}', '{"category":"news"}', '{"author":"alice"}'],
|
|
14
|
+
"chat.message": ["", '{"role":"user"}', '{"role":"assistant"}'],
|
|
15
|
+
"timer.tick": ["", '{"channel":"default"}'],
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const CATEGORY_COLORS: Record<FeedCategory, string> = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
tech: "bg-blue-600/30 text-blue-300",
|
|
20
|
+
news: "bg-amber-600/30 text-amber-300",
|
|
21
|
+
general: "bg-green-600/30 text-green-300",
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
export function FeedPanel() {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
const { t } = useTranslation();
|
|
26
|
+
const posts = useFeedStore((s) => s.posts);
|
|
27
|
+
const author = useFeedStore((s) => s.author);
|
|
28
|
+
const category = useFeedStore((s) => s.category);
|
|
29
|
+
const content = useFeedStore((s) => s.content);
|
|
30
|
+
const loading = useFeedStore((s) => s.loading);
|
|
31
|
+
const setAuthor = useFeedStore((s) => s.setAuthor);
|
|
32
|
+
const setCategory = useFeedStore((s) => s.setCategory);
|
|
33
|
+
const setContent = useFeedStore((s) => s.setContent);
|
|
34
|
+
const createPost = useFeedStore((s) => s.createPost);
|
|
35
|
+
const loadPosts = useFeedStore((s) => s.loadPosts);
|
|
36
|
+
const addPost = useFeedStore((s) => s.addPost);
|
|
37
|
+
const addLog = useLogStore((s) => s.addLog);
|
|
38
|
+
const ready = useConnectionStore((s) => s.ready);
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
// Event stream state
|
|
41
|
+
const entries = useEventStreamStore((s) => s.entries);
|
|
42
|
+
const activeEventType = useEventStreamStore((s) => s.activeEventType);
|
|
43
|
+
const activeFilter = useEventStreamStore((s) => s.activeFilter);
|
|
44
|
+
const subscribed = useEventStreamStore((s) => s.subscribed);
|
|
45
|
+
const setActiveEventType = useEventStreamStore((s) => s.setActiveEventType);
|
|
46
|
+
const setActiveFilter = useEventStreamStore((s) => s.setActiveFilter);
|
|
47
|
+
const handleSubscribe = useEventStreamStore((s) => s.handleSubscribe);
|
|
48
|
+
const handleUnsubscribe = useEventStreamStore((s) => s.handleUnsubscribe);
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
// On ready: subscribe to feed.update (no filter) + load posts
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!ready) return;
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
let subId: string | null = null;
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
const setup = async () => {
|
|
57
|
+
// Subscribe to all feed updates (no filter) to populate feed list
|
|
58
|
+
subId = await apiClient.subscribe(
|
|
59
|
+
"feed.update",
|
|
60
|
+
(payload) => {
|
|
61
|
+
addPost(payload as (typeof posts)[number]);
|
|
62
|
+
},
|
|
63
|
+
{}
|
|
64
|
+
);
|
|
65
|
+
addLog("Subscribed to feed.update (no filter)");
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
await loadPosts();
|
|
68
|
+
};
|
|
69
|
+
setup();
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
return () => {
|
|
72
|
+
if (subId) apiClient.unsubscribe(subId);
|
|
73
|
+
};
|
|
74
|
+
}, [ready, addLog, loadPosts, addPost]);
|
|
71
75
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
return (
|
|
77
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
78
|
+
{/* Header */}
|
|
79
|
+
<div className="px-4 py-2 bg-[var(--color-bg-secondary)] border-b border-[var(--color-border-primary)] flex items-center justify-between flex-shrink-0">
|
|
80
|
+
<h2 className="text-sm font-semibold flex items-center gap-1.5">
|
|
81
|
+
<Rss className="w-4 h-4 text-[var(--color-text-accent)]" />
|
|
82
|
+
{t("feed.title")}
|
|
83
|
+
{posts.length > 0 && (
|
|
84
|
+
<span className="ml-1 px-2 py-0.5 bg-[var(--color-accent)]/30 text-[var(--color-text-accent)] rounded text-[10px]">
|
|
85
|
+
{posts.length}
|
|
86
|
+
</span>
|
|
87
|
+
)}
|
|
88
|
+
</h2>
|
|
89
|
+
</div>
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
91
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
92
|
+
{/* Create Post */}
|
|
93
|
+
<div className="bg-[var(--color-bg-secondary)] rounded-lg p-3 space-y-2">
|
|
94
|
+
<div className="text-xs font-semibold text-[var(--color-text-tertiary)] uppercase tracking-wider">
|
|
95
|
+
{t("feed.newPost")}
|
|
96
|
+
</div>
|
|
97
|
+
<div className="flex gap-2">
|
|
98
|
+
<input
|
|
99
|
+
type="text"
|
|
100
|
+
value={author}
|
|
101
|
+
onChange={(e) => setAuthor(e.target.value)}
|
|
102
|
+
placeholder={t("feed.authorPlaceholder")}
|
|
103
|
+
className="w-24 px-2 py-1.5 text-xs bg-[var(--color-bg-tertiary)] rounded text-[var(--color-text-primary)] border border-[var(--color-border-secondary)] focus:border-[var(--color-accent)] focus:outline-none"
|
|
104
|
+
/>
|
|
105
|
+
<select
|
|
106
|
+
value={category}
|
|
107
|
+
onChange={(e) => setCategory(e.target.value as FeedCategory)}
|
|
108
|
+
className="px-2 py-1.5 text-xs bg-[var(--color-bg-tertiary)] rounded text-[var(--color-text-primary)] border border-[var(--color-border-secondary)]"
|
|
109
|
+
>
|
|
110
|
+
{CATEGORIES.map((c) => (
|
|
111
|
+
<option key={c} value={c}>
|
|
112
|
+
{c}
|
|
113
|
+
</option>
|
|
114
|
+
))}
|
|
115
|
+
</select>
|
|
116
|
+
</div>
|
|
117
|
+
<div className="flex gap-2">
|
|
118
|
+
<input
|
|
119
|
+
type="text"
|
|
120
|
+
value={content}
|
|
121
|
+
onChange={(e) => setContent(e.target.value)}
|
|
122
|
+
onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && createPost()}
|
|
123
|
+
placeholder={t("feed.postPlaceholder")}
|
|
124
|
+
className="flex-1 px-3 py-1.5 text-xs bg-[var(--color-bg-tertiary)] rounded text-[var(--color-text-primary)] border border-[var(--color-border-secondary)] focus:border-[var(--color-accent)] focus:outline-none"
|
|
125
|
+
/>
|
|
126
|
+
<button
|
|
127
|
+
onClick={createPost}
|
|
128
|
+
disabled={loading || !content.trim()}
|
|
129
|
+
className="px-3 py-1.5 text-xs bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 rounded transition-colors flex items-center gap-1"
|
|
130
|
+
>
|
|
131
|
+
<Send className="w-3 h-3" />
|
|
132
|
+
{t("feed.post")}
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
128
136
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
137
|
+
{/* Subscription Panel */}
|
|
138
|
+
<div className="bg-[var(--color-bg-secondary)] rounded-lg p-3 space-y-2">
|
|
139
|
+
<div className="text-xs font-semibold text-[var(--color-text-tertiary)] uppercase tracking-wider">
|
|
140
|
+
{t("feed.subscribeWithFilter")}
|
|
141
|
+
</div>
|
|
142
|
+
<div className="flex gap-2 items-center">
|
|
143
|
+
<select
|
|
144
|
+
value={activeEventType}
|
|
145
|
+
onChange={(e) => {
|
|
146
|
+
setActiveEventType(e.target.value as SubEventType);
|
|
147
|
+
setActiveFilter("");
|
|
148
|
+
}}
|
|
149
|
+
className="px-2 py-1.5 text-xs bg-[var(--color-bg-tertiary)] rounded text-[var(--color-text-primary)] border border-[var(--color-border-secondary)]"
|
|
150
|
+
>
|
|
151
|
+
{EVENT_TYPES.map((t) => (
|
|
152
|
+
<option key={t} value={t}>
|
|
153
|
+
{t}
|
|
154
|
+
</option>
|
|
155
|
+
))}
|
|
156
|
+
</select>
|
|
157
|
+
<input
|
|
158
|
+
type="text"
|
|
159
|
+
value={activeFilter}
|
|
160
|
+
onChange={(e) => setActiveFilter(e.target.value)}
|
|
161
|
+
placeholder={t("feed.filterPlaceholder")}
|
|
162
|
+
className="flex-1 px-2 py-1.5 text-xs bg-[var(--color-bg-tertiary)] rounded text-[var(--color-text-primary)] border border-[var(--color-border-secondary)] focus:border-[var(--color-accent)] focus:outline-none font-mono"
|
|
163
|
+
/>
|
|
164
|
+
{!subscribed ? (
|
|
165
|
+
<button
|
|
166
|
+
onClick={handleSubscribe}
|
|
167
|
+
className="px-3 py-1.5 text-xs bg-green-600 hover:bg-green-700 rounded transition-colors whitespace-nowrap"
|
|
168
|
+
>
|
|
169
|
+
{t("feed.subscribe")}
|
|
170
|
+
</button>
|
|
171
|
+
) : (
|
|
172
|
+
<button
|
|
173
|
+
onClick={handleUnsubscribe}
|
|
174
|
+
className="px-3 py-1.5 text-xs bg-red-600 hover:bg-red-700 rounded transition-colors whitespace-nowrap"
|
|
175
|
+
>
|
|
176
|
+
{t("feed.unsubscribe")}
|
|
177
|
+
</button>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
{/* Filter presets */}
|
|
181
|
+
<div className="flex gap-1 flex-wrap">
|
|
182
|
+
{FILTER_PRESETS[activeEventType].map((preset) => (
|
|
183
|
+
<button
|
|
184
|
+
key={preset}
|
|
185
|
+
onClick={() => setActiveFilter(preset)}
|
|
186
|
+
className={`px-2 py-0.5 text-[10px] rounded border transition-colors ${
|
|
187
|
+
activeFilter === preset
|
|
188
|
+
? "border-[var(--color-accent)] bg-[var(--color-accent)]/30 text-[var(--color-text-accent)]"
|
|
189
|
+
: "border-[var(--color-border-secondary)] text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)]"
|
|
190
|
+
}`}
|
|
191
|
+
>
|
|
192
|
+
{preset || t("feed.noFilter")}
|
|
193
|
+
</button>
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
185
197
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
198
|
+
{/* Event Stream */}
|
|
199
|
+
{entries.length > 0 && (
|
|
200
|
+
<div className="bg-[var(--color-bg-secondary)] rounded-lg p-3 space-y-2">
|
|
201
|
+
<div className="flex items-center justify-between">
|
|
202
|
+
<div className="text-xs font-semibold text-[var(--color-text-tertiary)] uppercase tracking-wider">
|
|
203
|
+
{t("feed.eventStream")}
|
|
204
|
+
</div>
|
|
205
|
+
{subscribed && (
|
|
206
|
+
<span className="px-1.5 py-0.5 bg-green-600/30 text-green-400 rounded text-[10px] animate-pulse">
|
|
207
|
+
{t("feed.live")}
|
|
208
|
+
</span>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
<div className="max-h-48 overflow-y-auto space-y-1">
|
|
212
|
+
{entries.slice(-20).map((entry) => (
|
|
213
|
+
<div
|
|
214
|
+
key={entry.id}
|
|
215
|
+
className="bg-[var(--color-bg-tertiary)] rounded px-2 py-1.5 text-[11px] font-mono"
|
|
216
|
+
>
|
|
217
|
+
<div className="flex items-center gap-2 text-[var(--color-text-tertiary)]">
|
|
218
|
+
<span className="text-[var(--color-text-accent)]">{entry.eventType}</span>
|
|
219
|
+
<span className="text-[10px]">
|
|
220
|
+
{Object.keys(entry.filter).length > 0
|
|
221
|
+
? `filter: ${JSON.stringify(entry.filter)}`
|
|
222
|
+
: t("feed.noFilter")}
|
|
223
|
+
</span>
|
|
224
|
+
<span className="text-[10px] ml-auto">
|
|
225
|
+
{new Date(entry.timestamp).toLocaleTimeString()}
|
|
226
|
+
</span>
|
|
227
|
+
</div>
|
|
228
|
+
<pre className="text-cyan-300 mt-0.5 whitespace-pre-wrap break-all">
|
|
229
|
+
{JSON.stringify(entry.payload, null, 0)}
|
|
230
|
+
</pre>
|
|
231
|
+
</div>
|
|
232
|
+
))}
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
221
236
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
237
|
+
{/* Feed Posts */}
|
|
238
|
+
{posts.length === 0 ? (
|
|
239
|
+
<div className="flex items-center justify-center py-8 text-[var(--color-text-placeholder)] text-xs">
|
|
240
|
+
{t("feed.noPostsYet")}
|
|
241
|
+
</div>
|
|
242
|
+
) : (
|
|
243
|
+
<div className="space-y-2">
|
|
244
|
+
{posts
|
|
245
|
+
.slice()
|
|
246
|
+
.reverse()
|
|
247
|
+
.map((post) => (
|
|
248
|
+
<div key={post.id} className="bg-[var(--color-bg-secondary)] rounded-lg p-3">
|
|
249
|
+
<div className="flex items-center gap-2 mb-1">
|
|
250
|
+
<span className="text-xs font-medium text-[var(--color-text-primary)]">
|
|
251
|
+
{post.author}
|
|
252
|
+
</span>
|
|
253
|
+
<span
|
|
254
|
+
className={`px-1.5 py-0.5 rounded text-[10px] ${CATEGORY_COLORS[post.category]}`}
|
|
255
|
+
>
|
|
256
|
+
{post.category}
|
|
257
|
+
</span>
|
|
258
|
+
<span className="text-[10px] text-[var(--color-text-placeholder)] ml-auto">
|
|
259
|
+
{new Date(post.timestamp).toLocaleTimeString()}
|
|
260
|
+
</span>
|
|
261
|
+
</div>
|
|
262
|
+
<div className="text-xs text-[var(--color-text-secondary)]">{post.content}</div>
|
|
263
|
+
</div>
|
|
264
|
+
))}
|
|
265
|
+
</div>
|
|
266
|
+
)}
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
248
270
|
}
|
|
@@ -13,6 +13,8 @@ import { FeedPanel } from "../feed/FeedPanel";
|
|
|
13
13
|
import { FilePreviewOverlay } from "../file-preview/FilePreviewOverlay";
|
|
14
14
|
import { DiffViewerPanel } from "../diff/DiffViewerPanel";
|
|
15
15
|
import { DebugPanel } from "../debug/DebugPanel";
|
|
16
|
+
import { ThemeToggle } from "../common/ThemeToggle";
|
|
17
|
+
import { LanguageSwitcher } from "../common/LanguageSwitcher";
|
|
16
18
|
|
|
17
19
|
export type CenterTab = "chat" | "feed";
|
|
18
20
|
|
|
@@ -75,6 +77,10 @@ export function AppLayout({
|
|
|
75
77
|
{mode === "desktop" ? t("app.mode.desktop") : t("app.mode.web")}
|
|
76
78
|
</span>
|
|
77
79
|
<span className="ml-3 text-[var(--color-text-tertiary)]">{t("app.title")}</span>
|
|
80
|
+
<div className="ml-auto flex items-center gap-1">
|
|
81
|
+
<LanguageSwitcher />
|
|
82
|
+
<ThemeToggle />
|
|
83
|
+
</div>
|
|
78
84
|
</div>
|
|
79
85
|
)}
|
|
80
86
|
|