fluxy-bot 0.1.0

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.
Files changed (79) hide show
  1. package/bin/cli.js +469 -0
  2. package/client/index.html +13 -0
  3. package/client/public/fluxy.png +0 -0
  4. package/client/public/icons/claude.png +0 -0
  5. package/client/public/icons/codex.png +0 -0
  6. package/client/public/icons/openai.svg +15 -0
  7. package/client/src/App.tsx +81 -0
  8. package/client/src/components/Chat/ChatView.tsx +19 -0
  9. package/client/src/components/Chat/InputBar.tsx +242 -0
  10. package/client/src/components/Chat/MessageBubble.tsx +20 -0
  11. package/client/src/components/Chat/MessageList.tsx +39 -0
  12. package/client/src/components/Chat/TypingIndicator.tsx +10 -0
  13. package/client/src/components/Dashboard/ConversationAnalytics.tsx +84 -0
  14. package/client/src/components/Dashboard/DashboardPage.tsx +52 -0
  15. package/client/src/components/Dashboard/PromoCard.tsx +44 -0
  16. package/client/src/components/Dashboard/ReportCard.tsx +35 -0
  17. package/client/src/components/Dashboard/TodayStats.tsx +28 -0
  18. package/client/src/components/ErrorBoundary.tsx +23 -0
  19. package/client/src/components/FluxyFab.tsx +25 -0
  20. package/client/src/components/Layout/ConnectionStatus.tsx +8 -0
  21. package/client/src/components/Layout/DashboardHeader.tsx +90 -0
  22. package/client/src/components/Layout/DashboardLayout.tsx +24 -0
  23. package/client/src/components/Layout/Header.tsx +10 -0
  24. package/client/src/components/Layout/MobileNav.tsx +30 -0
  25. package/client/src/components/Layout/Sidebar.tsx +55 -0
  26. package/client/src/components/Onboard/OnboardWizard.tsx +763 -0
  27. package/client/src/components/ui/avatar.tsx +109 -0
  28. package/client/src/components/ui/badge.tsx +48 -0
  29. package/client/src/components/ui/button.tsx +64 -0
  30. package/client/src/components/ui/card.tsx +92 -0
  31. package/client/src/components/ui/dialog.tsx +156 -0
  32. package/client/src/components/ui/dropdown-menu.tsx +257 -0
  33. package/client/src/components/ui/input.tsx +21 -0
  34. package/client/src/components/ui/scroll-area.tsx +58 -0
  35. package/client/src/components/ui/select.tsx +190 -0
  36. package/client/src/components/ui/separator.tsx +28 -0
  37. package/client/src/components/ui/sheet.tsx +141 -0
  38. package/client/src/components/ui/skeleton.tsx +13 -0
  39. package/client/src/components/ui/switch.tsx +33 -0
  40. package/client/src/components/ui/tabs.tsx +89 -0
  41. package/client/src/components/ui/textarea.tsx +18 -0
  42. package/client/src/components/ui/tooltip.tsx +55 -0
  43. package/client/src/hooks/useChat.ts +69 -0
  44. package/client/src/hooks/useMobile.ts +16 -0
  45. package/client/src/hooks/useWebSocket.ts +24 -0
  46. package/client/src/lib/mock-data.ts +104 -0
  47. package/client/src/lib/utils.ts +6 -0
  48. package/client/src/lib/ws-client.ts +52 -0
  49. package/client/src/main.tsx +10 -0
  50. package/client/src/styles/globals.css +55 -0
  51. package/components.json +20 -0
  52. package/dist/assets/index-BkNWpS06.css +1 -0
  53. package/dist/assets/index-CX3QeqQ8.js +64 -0
  54. package/dist/fluxy.png +0 -0
  55. package/dist/icons/claude.png +0 -0
  56. package/dist/icons/codex.png +0 -0
  57. package/dist/icons/openai.svg +15 -0
  58. package/dist/index.html +14 -0
  59. package/dist/manifest.webmanifest +1 -0
  60. package/dist/registerSW.js +1 -0
  61. package/dist/sw.js +1 -0
  62. package/dist/workbox-8c29f6e4.js +1 -0
  63. package/package.json +82 -0
  64. package/postcss.config.js +5 -0
  65. package/shared/ai.ts +141 -0
  66. package/shared/config.ts +37 -0
  67. package/shared/logger.ts +13 -0
  68. package/shared/paths.ts +14 -0
  69. package/shared/relay.ts +101 -0
  70. package/supervisor/fluxy.html +94 -0
  71. package/supervisor/index.ts +173 -0
  72. package/supervisor/tunnel.ts +62 -0
  73. package/supervisor/worker.ts +55 -0
  74. package/tsconfig.json +20 -0
  75. package/vite.config.ts +38 -0
  76. package/worker/claude-auth.ts +224 -0
  77. package/worker/codex-auth.ts +199 -0
  78. package/worker/db.ts +75 -0
  79. package/worker/index.ts +169 -0
@@ -0,0 +1,13 @@
1
+ import { cn } from "@/lib/utils"
2
+
3
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
+ return (
5
+ <div
6
+ data-slot="skeleton"
7
+ className={cn("bg-accent animate-pulse rounded-md", className)}
8
+ {...props}
9
+ />
10
+ )
11
+ }
12
+
13
+ export { Skeleton }
@@ -0,0 +1,33 @@
1
+ import * as React from "react"
2
+ import { Switch as SwitchPrimitive } from "radix-ui"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function Switch({
7
+ className,
8
+ size = "default",
9
+ ...props
10
+ }: React.ComponentProps<typeof SwitchPrimitive.Root> & {
11
+ size?: "sm" | "default"
12
+ }) {
13
+ return (
14
+ <SwitchPrimitive.Root
15
+ data-slot="switch"
16
+ data-size={size}
17
+ className={cn(
18
+ "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",
19
+ className
20
+ )}
21
+ {...props}
22
+ >
23
+ <SwitchPrimitive.Thumb
24
+ data-slot="switch-thumb"
25
+ className={cn(
26
+ "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
27
+ )}
28
+ />
29
+ </SwitchPrimitive.Root>
30
+ )
31
+ }
32
+
33
+ export { Switch }
@@ -0,0 +1,89 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { Tabs as TabsPrimitive } from "radix-ui"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ function Tabs({
8
+ className,
9
+ orientation = "horizontal",
10
+ ...props
11
+ }: React.ComponentProps<typeof TabsPrimitive.Root>) {
12
+ return (
13
+ <TabsPrimitive.Root
14
+ data-slot="tabs"
15
+ data-orientation={orientation}
16
+ orientation={orientation}
17
+ className={cn(
18
+ "group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ const tabsListVariants = cva(
27
+ "rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
28
+ {
29
+ variants: {
30
+ variant: {
31
+ default: "bg-muted",
32
+ line: "gap-1 bg-transparent",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: "default",
37
+ },
38
+ }
39
+ )
40
+
41
+ function TabsList({
42
+ className,
43
+ variant = "default",
44
+ ...props
45
+ }: React.ComponentProps<typeof TabsPrimitive.List> &
46
+ VariantProps<typeof tabsListVariants>) {
47
+ return (
48
+ <TabsPrimitive.List
49
+ data-slot="tabs-list"
50
+ data-variant={variant}
51
+ className={cn(tabsListVariants({ variant }), className)}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ function TabsTrigger({
58
+ className,
59
+ ...props
60
+ }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
61
+ return (
62
+ <TabsPrimitive.Trigger
63
+ data-slot="tabs-trigger"
64
+ className={cn(
65
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
66
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
67
+ "data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",
68
+ "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ )
74
+ }
75
+
76
+ function TabsContent({
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<typeof TabsPrimitive.Content>) {
80
+ return (
81
+ <TabsPrimitive.Content
82
+ data-slot="tabs-content"
83
+ className={cn("flex-1 outline-none", className)}
84
+ {...props}
85
+ />
86
+ )
87
+ }
88
+
89
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
@@ -0,0 +1,18 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export { Textarea }
@@ -0,0 +1,55 @@
1
+ import * as React from "react"
2
+ import { Tooltip as TooltipPrimitive } from "radix-ui"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function TooltipProvider({
7
+ delayDuration = 0,
8
+ ...props
9
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
10
+ return (
11
+ <TooltipPrimitive.Provider
12
+ data-slot="tooltip-provider"
13
+ delayDuration={delayDuration}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ function Tooltip({
20
+ ...props
21
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
22
+ return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
23
+ }
24
+
25
+ function TooltipTrigger({
26
+ ...props
27
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
28
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
29
+ }
30
+
31
+ function TooltipContent({
32
+ className,
33
+ sideOffset = 0,
34
+ children,
35
+ ...props
36
+ }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
37
+ return (
38
+ <TooltipPrimitive.Portal>
39
+ <TooltipPrimitive.Content
40
+ data-slot="tooltip-content"
41
+ sideOffset={sideOffset}
42
+ className={cn(
43
+ "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
44
+ className
45
+ )}
46
+ {...props}
47
+ >
48
+ {children}
49
+ <TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
50
+ </TooltipPrimitive.Content>
51
+ </TooltipPrimitive.Portal>
52
+ )
53
+ }
54
+
55
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -0,0 +1,69 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import type { WsClient } from '../lib/ws-client';
3
+
4
+ export interface ChatMessage {
5
+ id: string;
6
+ role: 'user' | 'assistant';
7
+ content: string;
8
+ }
9
+
10
+ export function useChat(ws: WsClient | null) {
11
+ const [messages, setMessages] = useState<ChatMessage[]>([]);
12
+ const [conversationId, setConversationId] = useState<string | null>(null);
13
+ const [streaming, setStreaming] = useState(false);
14
+ const [streamBuffer, setStreamBuffer] = useState('');
15
+
16
+ useEffect(() => {
17
+ if (!ws) return;
18
+
19
+ const unsubs = [
20
+ ws.on('bot:typing', () => setStreaming(true)),
21
+ ws.on('bot:token', (data: { token: string }) => {
22
+ setStreamBuffer((buf) => buf + data.token);
23
+ }),
24
+ ws.on('bot:response', (data: { conversationId: string; messageId: string; content: string }) => {
25
+ setConversationId(data.conversationId);
26
+ setMessages((msgs) => [...msgs, { id: data.messageId, role: 'assistant', content: data.content }]);
27
+ setStreamBuffer('');
28
+ setStreaming(false);
29
+ }),
30
+ ws.on('bot:error', (data: { error: string }) => {
31
+ setStreamBuffer('');
32
+ setStreaming(false);
33
+ setMessages((msgs) => [
34
+ ...msgs,
35
+ { id: Date.now().toString(), role: 'assistant', content: `Error: ${data.error}` },
36
+ ]);
37
+ }),
38
+ ];
39
+
40
+ return () => unsubs.forEach((u) => u());
41
+ }, [ws]);
42
+
43
+ const sendMessage = useCallback(
44
+ (content: string) => {
45
+ if (!ws || !content.trim()) return;
46
+
47
+ const userMsg: ChatMessage = { id: Date.now().toString(), role: 'user', content };
48
+ setMessages((msgs) => [...msgs, userMsg]);
49
+ ws.send('user:message', { conversationId, content });
50
+ },
51
+ [ws, conversationId],
52
+ );
53
+
54
+ const stopStreaming = useCallback(() => {
55
+ if (!ws || !conversationId) return;
56
+ ws.send('user:stop', { conversationId });
57
+ setStreaming(false);
58
+ setStreamBuffer('');
59
+ }, [ws, conversationId]);
60
+
61
+ const newChat = useCallback(() => {
62
+ setMessages([]);
63
+ setConversationId(null);
64
+ setStreamBuffer('');
65
+ setStreaming(false);
66
+ }, []);
67
+
68
+ return { messages, streaming, streamBuffer, conversationId, sendMessage, stopStreaming, newChat };
69
+ }
@@ -0,0 +1,16 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ export function useMobile(breakpoint = 768) {
4
+ const [isMobile, setIsMobile] = useState(false);
5
+
6
+ useEffect(() => {
7
+ const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
8
+ const onChange = (e: MediaQueryListEvent | MediaQueryList) =>
9
+ setIsMobile(e.matches);
10
+ onChange(mql);
11
+ mql.addEventListener('change', onChange);
12
+ return () => mql.removeEventListener('change', onChange);
13
+ }, [breakpoint]);
14
+
15
+ return isMobile;
16
+ }
@@ -0,0 +1,24 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { WsClient } from '../lib/ws-client';
3
+
4
+ export function useWebSocket() {
5
+ const clientRef = useRef<WsClient | null>(null);
6
+ const [connected, setConnected] = useState(false);
7
+
8
+ useEffect(() => {
9
+ const client = new WsClient();
10
+ clientRef.current = client;
11
+ client.connect();
12
+
13
+ const checkConnection = setInterval(() => {
14
+ setConnected(client.connected);
15
+ }, 1000);
16
+
17
+ return () => {
18
+ clearInterval(checkConnection);
19
+ client.disconnect();
20
+ };
21
+ }, []);
22
+
23
+ return { ws: clientRef.current, connected };
24
+ }
@@ -0,0 +1,104 @@
1
+ import {
2
+ LayoutDashboard,
3
+ MessageSquare,
4
+ Brain,
5
+ BookOpen,
6
+ ScrollText,
7
+ Settings,
8
+ type LucideIcon,
9
+ } from 'lucide-react';
10
+
11
+ /* ── Navigation ─────────────────────────────── */
12
+
13
+ export interface NavItem {
14
+ label: string;
15
+ icon: LucideIcon;
16
+ href: string;
17
+ active?: boolean;
18
+ }
19
+
20
+ export interface NavSection {
21
+ label: string;
22
+ items: NavItem[];
23
+ }
24
+
25
+ export const navSections: NavSection[] = [
26
+ {
27
+ label: 'HOME',
28
+ items: [
29
+ { label: 'Overview', icon: LayoutDashboard, href: '#', active: true },
30
+ { label: 'Conversations', icon: MessageSquare, href: '#' },
31
+ { label: 'Models', icon: Brain, href: '#' },
32
+ { label: 'Knowledge', icon: BookOpen, href: '#' },
33
+ ],
34
+ },
35
+ {
36
+ label: 'SYSTEM',
37
+ items: [
38
+ { label: 'Logs', icon: ScrollText, href: '#' },
39
+ { label: 'Settings', icon: Settings, href: '#' },
40
+ ],
41
+ },
42
+ ];
43
+
44
+ /* ── Weekly activity grid ───────────────────── */
45
+
46
+ export interface WeeklyModelActivity {
47
+ model: string;
48
+ /** 0–4 intensity per day (Mon–Sun) */
49
+ days: number[];
50
+ }
51
+
52
+ export const weeklyActivity: WeeklyModelActivity[] = [
53
+ { model: 'GPT-4o', days: [3, 2, 4, 3, 1, 2, 0] },
54
+ { model: 'Claude', days: [2, 4, 3, 4, 2, 1, 1] },
55
+ { model: 'Local', days: [1, 1, 2, 1, 3, 4, 2] },
56
+ ];
57
+
58
+ export const dayLabels = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
59
+
60
+ export const activityColors = [
61
+ '#2a2a2a', // 0 – none
62
+ '#1e3a5f', // 1 – low
63
+ '#2c5a8f', // 2 – medium-low
64
+ '#3578bf', // 3 – medium-high
65
+ '#3C8FFF', // 4 – high
66
+ ];
67
+
68
+ export const activityLegend = [
69
+ { label: '< 10', level: 1 },
70
+ { label: '10–50', level: 2 },
71
+ { label: '> 50', level: 3 },
72
+ { label: '100+', level: 4 },
73
+ { label: 'None', level: 0 },
74
+ ];
75
+
76
+ /* ── Stats ──────────────────────────────────── */
77
+
78
+ export interface StatItem {
79
+ label: string;
80
+ value: string;
81
+ change: string;
82
+ trend: 'up' | 'down';
83
+ }
84
+
85
+ export const stats: StatItem[] = [
86
+ { label: 'Conversations', value: '1,284', change: '+12%', trend: 'up' },
87
+ { label: 'Messages Today', value: '342', change: '+8%', trend: 'up' },
88
+ { label: 'Avg Response', value: '1.2s', change: '-15%', trend: 'down' },
89
+ { label: 'Uptime', value: '99.9%', change: '+0.1%', trend: 'up' },
90
+ ];
91
+
92
+ /* ── System status ──────────────────────────── */
93
+
94
+ export interface SystemService {
95
+ name: string;
96
+ status: 'online' | 'offline' | 'degraded';
97
+ }
98
+
99
+ export const systemStatus: SystemService[] = [
100
+ { name: 'Worker', status: 'online' },
101
+ { name: 'Tunnel', status: 'online' },
102
+ { name: 'Database', status: 'online' },
103
+ { name: 'AI Provider', status: 'online' },
104
+ ];
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,52 @@
1
+ type MessageHandler = (msg: any) => void;
2
+
3
+ export class WsClient {
4
+ private ws: WebSocket | null = null;
5
+ private handlers = new Map<string, Set<MessageHandler>>();
6
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
7
+ private url: string;
8
+
9
+ constructor(url?: string) {
10
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
11
+ const host = import.meta.env.DEV ? 'localhost:3000' : location.host;
12
+ this.url = url ?? `${proto}//${host}/ws`;
13
+ }
14
+
15
+ connect(): void {
16
+ this.ws = new WebSocket(this.url);
17
+
18
+ this.ws.onmessage = (e) => {
19
+ const msg = JSON.parse(e.data);
20
+ const handlers = this.handlers.get(msg.type);
21
+ handlers?.forEach((h) => h(msg.data));
22
+ };
23
+
24
+ this.ws.onclose = () => {
25
+ this.reconnectTimer = setTimeout(() => this.connect(), 2000);
26
+ };
27
+
28
+ this.ws.onerror = () => this.ws?.close();
29
+ }
30
+
31
+ disconnect(): void {
32
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
33
+ this.ws?.close();
34
+ this.ws = null;
35
+ }
36
+
37
+ on(type: string, handler: MessageHandler): () => void {
38
+ if (!this.handlers.has(type)) this.handlers.set(type, new Set());
39
+ this.handlers.get(type)!.add(handler);
40
+ return () => this.handlers.get(type)?.delete(handler);
41
+ }
42
+
43
+ send(type: string, data: any): void {
44
+ if (this.ws?.readyState === WebSocket.OPEN) {
45
+ this.ws.send(JSON.stringify({ type, data }));
46
+ }
47
+ }
48
+
49
+ get connected(): boolean {
50
+ return this.ws?.readyState === WebSocket.OPEN;
51
+ }
52
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+ import './styles/globals.css';
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ );
@@ -0,0 +1,55 @@
1
+ @import "tailwindcss";
2
+
3
+ @custom-variant dark (&:is(.dark *));
4
+
5
+ @theme inline {
6
+ --color-background: #212121;
7
+ --color-foreground: #f5f5f5;
8
+ --color-card: #2a2a2a;
9
+ --color-card-foreground: #f5f5f5;
10
+ --color-popover: #2a2a2a;
11
+ --color-popover-foreground: #f5f5f5;
12
+ --color-primary: #3C8FFF;
13
+ --color-primary-foreground: #ffffff;
14
+ --color-secondary: #333333;
15
+ --color-secondary-foreground: #f5f5f5;
16
+ --color-muted: #333333;
17
+ --color-muted-foreground: #999999;
18
+ --color-accent: #333333;
19
+ --color-accent-foreground: #f5f5f5;
20
+ --color-destructive: #FD486B;
21
+ --color-destructive-foreground: #ffffff;
22
+ --color-border: #3a3a3a;
23
+ --color-input: #3a3a3a;
24
+ --color-ring: #3C8FFF;
25
+ --color-chart-1: #3C8FFF;
26
+ --color-chart-2: #FD486B;
27
+ --color-chart-3: #F59E0B;
28
+ --color-chart-4: #8B5CF6;
29
+ --color-chart-5: #10B981;
30
+ --color-sidebar: #1c1c1c;
31
+ --color-sidebar-foreground: #f5f5f5;
32
+ --color-sidebar-primary: #3C8FFF;
33
+ --color-sidebar-primary-foreground: #ffffff;
34
+ --color-sidebar-accent: #282828;
35
+ --color-sidebar-accent-foreground: #f5f5f5;
36
+ --color-sidebar-border: #3a3a3a;
37
+ --color-sidebar-ring: #3C8FFF;
38
+ --radius: 0.75rem;
39
+ }
40
+
41
+ body {
42
+ background-color: var(--color-background);
43
+ color: var(--color-foreground);
44
+ -webkit-font-smoothing: antialiased;
45
+ -moz-osx-font-smoothing: grayscale;
46
+ }
47
+
48
+ ::selection {
49
+ background-color: rgba(60, 143, 255, 0.25);
50
+ }
51
+
52
+ ::-webkit-scrollbar { width: 6px; }
53
+ ::-webkit-scrollbar-track { background: transparent; }
54
+ ::-webkit-scrollbar-thumb { background: #3a3a3a; border-radius: 3px; }
55
+ ::-webkit-scrollbar-thumb:hover { background: #4a4a4a; }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "client/src/styles/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true
11
+ },
12
+ "aliases": {
13
+ "components": "@/components",
14
+ "utils": "@/lib/utils",
15
+ "ui": "@/components/ui",
16
+ "lib": "@/lib",
17
+ "hooks": "@/hooks"
18
+ },
19
+ "iconLibrary": "lucide"
20
+ }