fluxy-bot 0.8.9 → 0.9.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.
- package/README.md +53 -84
- package/bin/cli.js +1717 -6
- package/components.json +18 -18
- package/package.json +98 -109
- package/postcss.config.js +3 -3
- package/scripts/install.ps1 +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/postinstall.js +47 -58
- package/shared/ai.ts +118 -202
- package/shared/config.ts +37 -39
- package/shared/logger.ts +6 -14
- package/shared/paths.ts +10 -16
- package/shared/relay.ts +89 -98
- package/supervisor/backend.ts +77 -89
- package/supervisor/chat/ARCHITECTURE.md +13 -14
- package/supervisor/chat/OnboardWizard.tsx +2194 -3419
- package/supervisor/chat/fluxy-main.tsx +495 -633
- package/supervisor/chat/fluxy.html +24 -36
- package/supervisor/chat/onboard-main.tsx +8 -8
- package/supervisor/chat/onboard.html +10 -13
- package/supervisor/chat/src/components/Chat/AudioBubble.tsx +94 -108
- package/supervisor/chat/src/components/Chat/ChatView.tsx +14 -36
- package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +71 -89
- package/supervisor/chat/src/components/Chat/InputBar.tsx +498 -728
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +154 -246
- package/supervisor/chat/src/components/Chat/MessageList.tsx +114 -138
- package/supervisor/chat/src/components/Chat/TypingIndicator.tsx +38 -50
- package/supervisor/chat/src/components/LoginScreen.tsx +245 -296
- package/supervisor/chat/src/hooks/useChat.ts +210 -259
- package/supervisor/chat/src/hooks/useFluxyChat.ts +232 -322
- package/supervisor/chat/src/hooks/useSpeechRecognition.ts +126 -148
- package/supervisor/chat/src/lib/auth.ts +20 -23
- package/supervisor/chat/src/lib/ws-client.ts +113 -125
- package/supervisor/chat/src/styles/globals.css +76 -85
- package/supervisor/file-saver.ts +39 -52
- package/supervisor/fluxy-agent.ts +213 -292
- package/supervisor/index.ts +895 -1263
- package/supervisor/scheduler.ts +237 -299
- package/supervisor/tunnel.ts +113 -167
- package/supervisor/vite-dev.ts +51 -61
- package/supervisor/widget.js +135 -147
- package/supervisor/worker.ts +45 -49
- package/tsconfig.json +18 -24
- package/vite.config.ts +48 -55
- package/vite.fluxy.config.ts +34 -34
- package/worker/claude-auth.ts +202 -265
- package/worker/codex-auth.ts +144 -181
- package/worker/db.ts +63 -191
- package/worker/index.ts +601 -842
- package/workspace/MYSELF.md +1 -1
- package/workspace/PULSE.json +6 -6
- package/workspace/backend/index.ts +16 -19
- package/workspace/client/index.html +47 -60
- package/workspace/client/public/manifest.json +21 -21
- package/workspace/client/public/sw.js +29 -31
- package/workspace/client/src/App.tsx +91 -136
- package/workspace/client/src/components/Dashboard/DashboardPage.tsx +68 -72
- package/workspace/client/src/components/ErrorBoundary.tsx +11 -11
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +35 -35
- package/workspace/client/src/components/Layout/Footer.tsx +12 -12
- package/workspace/client/src/components/Layout/MobileNav.tsx +23 -23
- package/workspace/client/src/components/Layout/Sidebar.tsx +81 -81
- package/workspace/client/src/components/ui/avatar.tsx +85 -85
- package/workspace/client/src/components/ui/badge.tsx +40 -41
- package/workspace/client/src/components/ui/button.tsx +56 -57
- package/workspace/client/src/components/ui/card.tsx +75 -75
- package/workspace/client/src/components/ui/dialog.tsx +114 -117
- package/workspace/client/src/components/ui/dropdown-menu.tsx +191 -199
- package/workspace/client/src/components/ui/input.tsx +17 -17
- package/workspace/client/src/components/ui/scroll-area.tsx +47 -47
- package/workspace/client/src/components/ui/select.tsx +145 -151
- package/workspace/client/src/components/ui/separator.tsx +21 -21
- package/workspace/client/src/components/ui/sheet.tsx +102 -102
- package/workspace/client/src/components/ui/skeleton.tsx +10 -10
- package/workspace/client/src/components/ui/switch.tsx +26 -26
- package/workspace/client/src/components/ui/tabs.tsx +69 -69
- package/workspace/client/src/components/ui/textarea.tsx +14 -14
- package/workspace/client/src/components/ui/tooltip.tsx +37 -37
- package/workspace/client/src/lib/utils.ts +1 -1
- package/workspace/client/src/main.tsx +3 -3
- package/workspace/client/src/styles/globals.css +76 -85
- package/workspace/skills/code-reviewer/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/code-reviewer/skills/code-reviewer/SKILL.md +0 -6
- package/workspace/skills/daily-standup/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/daily-standup/skills/daily-standup/SKILL.md +0 -7
- package/workspace/skills/workspace-helper/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/workspace-helper/skills/workspace-helper/SKILL.md +0 -2
- package/bin/cli.backup.js +0 -2138
- package/cli/commands/daemon.ts +0 -42
- package/cli/commands/init.ts +0 -32
- package/cli/commands/start.ts +0 -113
- package/cli/commands/tunnel.temp.ts +0 -206
- package/cli/commands/tunnel.ts +0 -227
- package/cli/commands/update.ts +0 -163
- package/cli/core/base-adapter.ts +0 -156
- package/cli/core/cloudflared.ts +0 -113
- package/cli/core/config.ts +0 -73
- package/cli/core/os-detector.ts +0 -43
- package/cli/core/server.ts +0 -109
- package/cli/core/types.ts +0 -15
- package/cli/index.ts +0 -72
- package/cli/platforms/darwin.ts +0 -146
- package/cli/platforms/index.ts +0 -21
- package/cli/platforms/linux.ts +0 -156
- package/cli/platforms/win32.ts +0 -34
- package/cli/utils/ui.ts +0 -37
|
@@ -5,146 +5,122 @@ import TypingIndicator from './TypingIndicator';
|
|
|
5
5
|
import ImageLightbox from './ImageLightbox';
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
messages: ChatMessage[];
|
|
9
|
+
streaming: boolean;
|
|
10
|
+
streamBuffer: string;
|
|
11
|
+
tools: ToolActivity[];
|
|
12
|
+
hasMore?: boolean;
|
|
13
|
+
onLoadOlder?: () => void;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export default function MessageList({
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
16
|
+
export default function MessageList({ messages, streaming, streamBuffer, tools, hasMore, onLoadOlder }: Props) {
|
|
17
|
+
const bottomRef = useRef<HTMLDivElement>(null);
|
|
18
|
+
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
19
|
+
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
20
|
+
const isInitialLoad = useRef(true);
|
|
21
|
+
const [loadingOlder, setLoadingOlder] = useState(false);
|
|
22
|
+
const [lightbox, setLightbox] = useState<{ images: string[]; index: number } | null>(null);
|
|
23
|
+
|
|
24
|
+
const handleImageClick = useCallback((images: string[], index: number) => {
|
|
25
|
+
setLightbox({ images, index });
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
// Scroll to bottom — instant on initial load, smooth afterwards
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!bottomRef.current) return;
|
|
31
|
+
if (isInitialLoad.current && messages.length > 0) {
|
|
32
|
+
bottomRef.current.scrollIntoView({ behavior: 'instant' as ScrollBehavior });
|
|
33
|
+
isInitialLoad.current = false;
|
|
34
|
+
} else if (!isInitialLoad.current) {
|
|
35
|
+
bottomRef.current.scrollIntoView({ behavior: 'smooth' });
|
|
36
|
+
}
|
|
37
|
+
}, [messages, streamBuffer, tools, streaming]);
|
|
38
|
+
|
|
39
|
+
// IntersectionObserver for infinite scroll up
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!sentinelRef.current || !onLoadOlder) return;
|
|
42
|
+
const sentinel = sentinelRef.current;
|
|
43
|
+
|
|
44
|
+
const observer = new IntersectionObserver(
|
|
45
|
+
async (entries) => {
|
|
46
|
+
if (entries[0].isIntersecting && hasMore && !loadingOlder) {
|
|
47
|
+
const container = scrollContainerRef.current;
|
|
48
|
+
if (!container) return;
|
|
49
|
+
|
|
50
|
+
// Save scroll position before loading
|
|
51
|
+
const prevScrollHeight = container.scrollHeight;
|
|
52
|
+
|
|
53
|
+
setLoadingOlder(true);
|
|
54
|
+
await onLoadOlder();
|
|
55
|
+
setLoadingOlder(false);
|
|
56
|
+
|
|
57
|
+
// Restore scroll position after DOM update
|
|
58
|
+
requestAnimationFrame(() => {
|
|
59
|
+
const newScrollHeight = container.scrollHeight;
|
|
60
|
+
container.scrollTop += newScrollHeight - prevScrollHeight;
|
|
61
|
+
});
|
|
48
62
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// IntersectionObserver for infinite scroll up
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
if (!sentinelRef.current || !onLoadOlder) return;
|
|
54
|
-
const sentinel = sentinelRef.current;
|
|
55
|
-
|
|
56
|
-
const observer = new IntersectionObserver(
|
|
57
|
-
async entries => {
|
|
58
|
-
if (entries[0].isIntersecting && hasMore && !loadingOlder) {
|
|
59
|
-
const container = scrollContainerRef.current;
|
|
60
|
-
if (!container) return;
|
|
61
|
-
|
|
62
|
-
// Save scroll position before loading
|
|
63
|
-
const prevScrollHeight = container.scrollHeight;
|
|
64
|
-
|
|
65
|
-
setLoadingOlder(true);
|
|
66
|
-
await onLoadOlder();
|
|
67
|
-
setLoadingOlder(false);
|
|
68
|
-
|
|
69
|
-
// Restore scroll position after DOM update
|
|
70
|
-
requestAnimationFrame(() => {
|
|
71
|
-
const newScrollHeight = container.scrollHeight;
|
|
72
|
-
container.scrollTop +=
|
|
73
|
-
newScrollHeight - prevScrollHeight;
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
{ root: scrollContainerRef.current, threshold: 0.1 }
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
observer.observe(sentinel);
|
|
81
|
-
return () => observer.disconnect();
|
|
82
|
-
}, [hasMore, loadingOlder, onLoadOlder]);
|
|
83
|
-
|
|
84
|
-
// Get the latest running tool name (only the current one)
|
|
85
|
-
const currentTool = tools.length > 0 ? tools[tools.length - 1] : null;
|
|
86
|
-
const currentToolName =
|
|
87
|
-
currentTool?.status === 'running' ? currentTool.name : undefined;
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<div
|
|
91
|
-
ref={scrollContainerRef}
|
|
92
|
-
className="flex-1 overflow-y-auto overflow-x-hidden p-4 flex flex-col min-h-0"
|
|
93
|
-
>
|
|
94
|
-
{/* Spacer pushes messages to the bottom when few */}
|
|
95
|
-
<div className="flex-1" />
|
|
96
|
-
|
|
97
|
-
{/* Sentinel for infinite scroll + loading spinner */}
|
|
98
|
-
{hasMore && (
|
|
99
|
-
<div ref={sentinelRef} className="flex justify-center py-2">
|
|
100
|
-
{loadingOlder && (
|
|
101
|
-
<div className="w-5 h-5 border-2 border-muted-foreground/20 border-t-muted-foreground/60 rounded-full animate-spin" />
|
|
102
|
-
)}
|
|
103
|
-
</div>
|
|
104
|
-
)}
|
|
105
|
-
|
|
106
|
-
{messages.length === 0 && !streaming && (
|
|
107
|
-
<div className="flex items-center justify-center py-8 text-muted-foreground text-sm">
|
|
108
|
-
Start a conversation
|
|
109
|
-
</div>
|
|
110
|
-
)}
|
|
111
|
-
|
|
112
|
-
<div className="space-y-3">
|
|
113
|
-
{messages.map(msg => (
|
|
114
|
-
<MessageBubble
|
|
115
|
-
key={msg.id}
|
|
116
|
-
role={msg.role}
|
|
117
|
-
content={msg.content}
|
|
118
|
-
timestamp={msg.timestamp}
|
|
119
|
-
hasAttachments={msg.hasAttachments}
|
|
120
|
-
audioData={msg.audioData}
|
|
121
|
-
attachments={msg.attachments}
|
|
122
|
-
onImageClick={handleImageClick}
|
|
123
|
-
/>
|
|
124
|
-
))}
|
|
125
|
-
|
|
126
|
-
{/* Typing / streaming bubble — shown as soon as agent starts */}
|
|
127
|
-
{streaming && (
|
|
128
|
-
<TypingIndicator
|
|
129
|
-
text={streamBuffer}
|
|
130
|
-
toolName={currentToolName}
|
|
131
|
-
/>
|
|
132
|
-
)}
|
|
133
|
-
</div>
|
|
134
|
-
<div ref={bottomRef} />
|
|
135
|
-
|
|
136
|
-
{lightbox && (
|
|
137
|
-
<ImageLightbox
|
|
138
|
-
images={lightbox.images}
|
|
139
|
-
index={lightbox.index}
|
|
140
|
-
onClose={() => setLightbox(null)}
|
|
141
|
-
onNavigate={i =>
|
|
142
|
-
setLightbox(prev =>
|
|
143
|
-
prev ? { ...prev, index: i } : null
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
/>
|
|
147
|
-
)}
|
|
148
|
-
</div>
|
|
63
|
+
},
|
|
64
|
+
{ root: scrollContainerRef.current, threshold: 0.1 },
|
|
149
65
|
);
|
|
66
|
+
|
|
67
|
+
observer.observe(sentinel);
|
|
68
|
+
return () => observer.disconnect();
|
|
69
|
+
}, [hasMore, loadingOlder, onLoadOlder]);
|
|
70
|
+
|
|
71
|
+
// Get the latest running tool name (only the current one)
|
|
72
|
+
const currentTool = tools.length > 0 ? tools[tools.length - 1] : null;
|
|
73
|
+
const currentToolName = currentTool?.status === 'running' ? currentTool.name : undefined;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div ref={scrollContainerRef} className="flex-1 overflow-y-auto overflow-x-hidden p-4 flex flex-col min-h-0">
|
|
77
|
+
{/* Spacer pushes messages to the bottom when few */}
|
|
78
|
+
<div className="flex-1" />
|
|
79
|
+
|
|
80
|
+
{/* Sentinel for infinite scroll + loading spinner */}
|
|
81
|
+
{hasMore && (
|
|
82
|
+
<div ref={sentinelRef} className="flex justify-center py-2">
|
|
83
|
+
{loadingOlder && (
|
|
84
|
+
<div className="w-5 h-5 border-2 border-muted-foreground/20 border-t-muted-foreground/60 rounded-full animate-spin" />
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{messages.length === 0 && !streaming && (
|
|
90
|
+
<div className="flex items-center justify-center py-8 text-muted-foreground text-sm">
|
|
91
|
+
Start a conversation
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
<div className="space-y-3">
|
|
96
|
+
{messages.map((msg) => (
|
|
97
|
+
<MessageBubble
|
|
98
|
+
key={msg.id}
|
|
99
|
+
role={msg.role}
|
|
100
|
+
content={msg.content}
|
|
101
|
+
timestamp={msg.timestamp}
|
|
102
|
+
hasAttachments={msg.hasAttachments}
|
|
103
|
+
audioData={msg.audioData}
|
|
104
|
+
attachments={msg.attachments}
|
|
105
|
+
onImageClick={handleImageClick}
|
|
106
|
+
/>
|
|
107
|
+
))}
|
|
108
|
+
|
|
109
|
+
{/* Typing / streaming bubble — shown as soon as agent starts */}
|
|
110
|
+
{streaming && (
|
|
111
|
+
<TypingIndicator text={streamBuffer} toolName={currentToolName} />
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
<div ref={bottomRef} />
|
|
115
|
+
|
|
116
|
+
{lightbox && (
|
|
117
|
+
<ImageLightbox
|
|
118
|
+
images={lightbox.images}
|
|
119
|
+
index={lightbox.index}
|
|
120
|
+
onClose={() => setLightbox(null)}
|
|
121
|
+
onNavigate={(i) => setLightbox((prev) => prev ? { ...prev, index: i } : null)}
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
150
126
|
}
|
|
@@ -1,59 +1,47 @@
|
|
|
1
1
|
interface Props {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
text: string;
|
|
3
|
+
toolName?: string;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
/** Friendly label for SDK tool names */
|
|
7
7
|
function toolLabel(name: string): string {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
const labels: Record<string, string> = {
|
|
9
|
+
Read: 'Reading file',
|
|
10
|
+
Write: 'Writing file',
|
|
11
|
+
Edit: 'Editing file',
|
|
12
|
+
Bash: 'Running command',
|
|
13
|
+
Grep: 'Searching code',
|
|
14
|
+
Glob: 'Finding files',
|
|
15
|
+
Task: 'Running task',
|
|
16
|
+
WebFetch: 'Fetching URL',
|
|
17
|
+
WebSearch: 'Searching web',
|
|
18
|
+
};
|
|
19
|
+
return labels[name] || `Using ${name}`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export default function TypingIndicator({ text, toolName }: Props) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
/>
|
|
48
|
-
</span>
|
|
49
|
-
)}
|
|
50
|
-
{toolName && (
|
|
51
|
-
<div className="flex items-center gap-1.5 mt-1 text-xs text-muted-foreground">
|
|
52
|
-
<div className="w-2.5 h-2.5 border-[1.5px] border-muted-foreground/30 border-t-primary rounded-full animate-spin" />
|
|
53
|
-
<span>{toolLabel(toolName)}...</span>
|
|
54
|
-
</div>
|
|
55
|
-
)}
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
);
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex justify-start">
|
|
25
|
+
<div className="max-w-[75%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-muted text-foreground break-words overflow-hidden" style={{ overflowWrap: 'anywhere' }}>
|
|
26
|
+
{text ? (
|
|
27
|
+
<>
|
|
28
|
+
<span className="whitespace-pre-wrap">{text}</span>
|
|
29
|
+
<span className="inline-block w-2 h-4 ml-0.5 bg-muted-foreground animate-pulse" />
|
|
30
|
+
</>
|
|
31
|
+
) : (
|
|
32
|
+
<span className="inline-flex items-center gap-1 h-5">
|
|
33
|
+
<span className="w-1.5 h-1.5 rounded-full bg-muted-foreground/60 animate-bounce" style={{ animationDelay: '0ms' }} />
|
|
34
|
+
<span className="w-1.5 h-1.5 rounded-full bg-muted-foreground/60 animate-bounce" style={{ animationDelay: '150ms' }} />
|
|
35
|
+
<span className="w-1.5 h-1.5 rounded-full bg-muted-foreground/60 animate-bounce" style={{ animationDelay: '300ms' }} />
|
|
36
|
+
</span>
|
|
37
|
+
)}
|
|
38
|
+
{toolName && (
|
|
39
|
+
<div className="flex items-center gap-1.5 mt-1 text-xs text-muted-foreground">
|
|
40
|
+
<div className="w-2.5 h-2.5 border-[1.5px] border-muted-foreground/30 border-t-primary rounded-full animate-spin" />
|
|
41
|
+
<span>{toolLabel(toolName)}...</span>
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
59
47
|
}
|