fluxy-bot 0.2.30 → 0.2.31
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/supervisor/chat/fluxy-main.tsx +36 -1
- package/supervisor/chat/src/components/Chat/InputBar.tsx +9 -0
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +3 -3
- package/supervisor/widget.js +6 -1
- package/workspace/client/src/App.tsx +1 -1
- package/workspace/client/src/components/Layout/DashboardHeader.tsx +2 -39
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +2 -3
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import ReactDOM from 'react-dom/client';
|
|
3
|
-
import { MoreVertical, Trash2 } from 'lucide-react';
|
|
3
|
+
import { ChevronRight, MoreVertical, Trash2, Wand2 } from 'lucide-react';
|
|
4
4
|
import { WsClient } from './src/lib/ws-client';
|
|
5
5
|
import { useFluxyChat } from './src/hooks/useFluxyChat';
|
|
6
|
+
import OnboardWizard from './OnboardWizard';
|
|
6
7
|
import MessageList from './src/components/Chat/MessageList';
|
|
7
8
|
import InputBar from './src/components/Chat/InputBar';
|
|
8
9
|
import './src/styles/globals.css';
|
|
@@ -13,6 +14,7 @@ function FluxyApp() {
|
|
|
13
14
|
const [botName, setBotName] = useState('Fluxy');
|
|
14
15
|
const [whisperEnabled, setWhisperEnabled] = useState(false);
|
|
15
16
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
17
|
+
const [showWizard, setShowWizard] = useState(false);
|
|
16
18
|
const [reloadTrigger, setReloadTrigger] = useState(0);
|
|
17
19
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
18
20
|
const wasConnected = useRef(false);
|
|
@@ -90,6 +92,13 @@ function FluxyApp() {
|
|
|
90
92
|
<div className="flex flex-col h-dvh overflow-hidden">
|
|
91
93
|
{/* Header */}
|
|
92
94
|
<div className="flex items-center gap-3 px-4 py-3 border-b border-border shrink-0">
|
|
95
|
+
<button
|
|
96
|
+
onClick={() => window.parent?.postMessage({ type: 'fluxy:close' }, '*')}
|
|
97
|
+
className="flex items-center justify-center h-7 w-7 -ml-1 rounded-full text-muted-foreground hover:text-foreground hover:bg-white/[0.06] transition-colors"
|
|
98
|
+
aria-label="Close chat"
|
|
99
|
+
>
|
|
100
|
+
<ChevronRight className="h-5 w-5" />
|
|
101
|
+
</button>
|
|
93
102
|
<img src="/fluxy.png" alt={botName} className="h-5 w-auto" />
|
|
94
103
|
<span className="text-sm font-semibold">{botName}</span>
|
|
95
104
|
<div className={`h-2 w-2 rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`} />
|
|
@@ -103,6 +112,13 @@ function FluxyApp() {
|
|
|
103
112
|
</button>
|
|
104
113
|
{menuOpen && (
|
|
105
114
|
<div className="absolute right-0 top-full mt-1 min-w-[160px] rounded-md border border-border bg-popover py-1 shadow-lg z-50">
|
|
115
|
+
<button
|
|
116
|
+
onClick={() => { setShowWizard(true); setMenuOpen(false); }}
|
|
117
|
+
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-white/[0.06] transition-colors"
|
|
118
|
+
>
|
|
119
|
+
<Wand2 className="h-4 w-4" />
|
|
120
|
+
Setup Wizard
|
|
121
|
+
</button>
|
|
106
122
|
<button
|
|
107
123
|
onClick={() => { clearContext(); setMenuOpen(false); }}
|
|
108
124
|
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-white/[0.06] transition-colors"
|
|
@@ -120,6 +136,25 @@ function FluxyApp() {
|
|
|
120
136
|
<MessageList messages={messages} streaming={streaming} streamBuffer={streamBuffer} tools={tools} />
|
|
121
137
|
<InputBar onSend={sendMessage} onStop={stopStreaming} streaming={streaming} whisperEnabled={whisperEnabled} />
|
|
122
138
|
</div>
|
|
139
|
+
|
|
140
|
+
{/* Setup Wizard overlay */}
|
|
141
|
+
{showWizard && (
|
|
142
|
+
<OnboardWizard
|
|
143
|
+
onComplete={() => {
|
|
144
|
+
setShowWizard(false);
|
|
145
|
+
// Reload settings (bot name, whisper, etc.)
|
|
146
|
+
fetch('/api/settings')
|
|
147
|
+
.then((r) => r.json())
|
|
148
|
+
.then((s) => {
|
|
149
|
+
if (s.agent_name) setBotName(s.agent_name);
|
|
150
|
+
setWhisperEnabled(s.whisper_enabled === 'true');
|
|
151
|
+
})
|
|
152
|
+
.catch(() => {});
|
|
153
|
+
// Notify dashboard so it can refresh
|
|
154
|
+
window.parent?.postMessage({ type: 'fluxy:onboard-complete' }, '*');
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
123
158
|
</div>
|
|
124
159
|
);
|
|
125
160
|
}
|
|
@@ -334,6 +334,15 @@ export default function InputBar({ onSend, onStop, streaming, whisperEnabled }:
|
|
|
334
334
|
ref={textareaRef}
|
|
335
335
|
value={text}
|
|
336
336
|
onChange={(e) => setText(e.target.value)}
|
|
337
|
+
onKeyDown={(e) => {
|
|
338
|
+
// Desktop: Enter sends, Shift+Enter inserts newline
|
|
339
|
+
// Mobile: Enter always inserts newline (no hardware keyboard)
|
|
340
|
+
const isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
341
|
+
if (e.key === 'Enter' && !e.shiftKey && !isMobile) {
|
|
342
|
+
e.preventDefault();
|
|
343
|
+
if (hasContent) handleSend();
|
|
344
|
+
}
|
|
345
|
+
}}
|
|
337
346
|
placeholder="Type a message..."
|
|
338
347
|
rows={1}
|
|
339
348
|
className="flex-1 resize-none bg-transparent text-gray-900 py-3 text-sm outline-none placeholder:text-gray-400 overflow-y-auto"
|
|
@@ -43,7 +43,7 @@ export default function MessageBubble({ role, content, timestamp, hasAttachments
|
|
|
43
43
|
if (audioData) {
|
|
44
44
|
return (
|
|
45
45
|
<div className="flex flex-col items-end gap-0.5">
|
|
46
|
-
<div className="max-w-[
|
|
46
|
+
<div className="max-w-[92%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-primary text-primary-foreground">
|
|
47
47
|
<AudioBubble audioData={audioData} />
|
|
48
48
|
</div>
|
|
49
49
|
{time && <span className="text-[10px] text-muted-foreground/50 px-1">{time}</span>}
|
|
@@ -53,7 +53,7 @@ export default function MessageBubble({ role, content, timestamp, hasAttachments
|
|
|
53
53
|
|
|
54
54
|
return (
|
|
55
55
|
<div className="flex flex-col items-end gap-0.5">
|
|
56
|
-
<div className="max-w-[
|
|
56
|
+
<div className="max-w-[92%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed whitespace-pre-wrap bg-primary text-primary-foreground break-words overflow-hidden" style={{ overflowWrap: 'anywhere' }}>
|
|
57
57
|
{/* Image thumbnails */}
|
|
58
58
|
{imageUrls.length > 0 && (
|
|
59
59
|
<div className="flex gap-1.5 flex-wrap mb-2">
|
|
@@ -90,7 +90,7 @@ export default function MessageBubble({ role, content, timestamp, hasAttachments
|
|
|
90
90
|
|
|
91
91
|
return (
|
|
92
92
|
<div className="flex flex-col items-start gap-0.5">
|
|
93
|
-
<div className="min-w-0 rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-muted text-foreground prose prose-sm prose-invert max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 break-words overflow-hidden" style={{ maxWidth: '
|
|
93
|
+
<div className="min-w-0 rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-muted text-foreground prose prose-sm prose-invert max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 break-words overflow-hidden" style={{ maxWidth: '92%', overflowWrap: 'anywhere' }}>
|
|
94
94
|
<ReactMarkdown
|
|
95
95
|
remarkPlugins={[remarkGfm]}
|
|
96
96
|
components={{
|
package/supervisor/widget.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
if (document.getElementById('fluxy-widget')) return;
|
|
3
3
|
|
|
4
|
-
var PANEL_WIDTH = '
|
|
4
|
+
var PANEL_WIDTH = '480px';
|
|
5
5
|
|
|
6
6
|
// ── Styles ──
|
|
7
7
|
var style = document.createElement('style');
|
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
if (e.key === 'Escape' && isOpen) toggle();
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
// Close from iframe (e.g. close button inside chat)
|
|
77
|
+
window.addEventListener('message', function (e) {
|
|
78
|
+
if (e.data && e.data.type === 'fluxy:close' && isOpen) toggle();
|
|
79
|
+
});
|
|
80
|
+
|
|
76
81
|
// Restore open state after HMR reload (so chat isn't disrupted)
|
|
77
82
|
try {
|
|
78
83
|
if (sessionStorage.getItem('fluxy_widget_open') === '1') {
|
|
@@ -1,26 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Settings, Bell, Wand2 } from 'lucide-react';
|
|
1
|
+
import { Bell } from 'lucide-react';
|
|
3
2
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
4
3
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
|
5
4
|
import MobileNav from './MobileNav';
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
onOpenOnboard?: () => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default function DashboardHeader({ onOpenOnboard }: Props) {
|
|
12
|
-
const [menuOpen, setMenuOpen] = useState(false);
|
|
13
|
-
const menuRef = useRef<HTMLDivElement>(null);
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
if (!menuOpen) return;
|
|
17
|
-
const handler = (e: MouseEvent) => {
|
|
18
|
-
if (menuRef.current && !menuRef.current.contains(e.target as Node)) setMenuOpen(false);
|
|
19
|
-
};
|
|
20
|
-
document.addEventListener('mousedown', handler);
|
|
21
|
-
return () => document.removeEventListener('mousedown', handler);
|
|
22
|
-
}, [menuOpen]);
|
|
23
|
-
|
|
6
|
+
export default function DashboardHeader() {
|
|
24
7
|
return (
|
|
25
8
|
<header className="flex items-center justify-between px-4 md:px-6 py-3 md:py-4">
|
|
26
9
|
{/* Left: hamburger (mobile) + logo */}
|
|
@@ -53,26 +36,6 @@ export default function DashboardHeader({ onOpenOnboard }: Props) {
|
|
|
53
36
|
|
|
54
37
|
{/* Right: actions */}
|
|
55
38
|
<div className="flex items-center gap-2">
|
|
56
|
-
{/* Settings with dropdown */}
|
|
57
|
-
<div className="relative" ref={menuRef}>
|
|
58
|
-
<button
|
|
59
|
-
onClick={() => setMenuOpen((o) => !o)}
|
|
60
|
-
className="flex items-center justify-center h-10 w-10 rounded-full bg-card border border-border text-muted-foreground hover:text-foreground transition-colors"
|
|
61
|
-
>
|
|
62
|
-
<Settings className="h-4 w-4" />
|
|
63
|
-
</button>
|
|
64
|
-
{menuOpen && (
|
|
65
|
-
<div className="absolute right-0 top-full mt-2 w-52 bg-card border border-border rounded-xl shadow-xl py-1.5 z-50">
|
|
66
|
-
<button
|
|
67
|
-
onClick={() => { onOpenOnboard?.(); setMenuOpen(false); }}
|
|
68
|
-
className="w-full flex items-center gap-3 px-4 py-2.5 text-[13px] text-foreground hover:bg-white/[0.04] transition-colors text-left"
|
|
69
|
-
>
|
|
70
|
-
<Wand2 className="h-4 w-4 text-muted-foreground" />
|
|
71
|
-
Setup Wizard
|
|
72
|
-
</button>
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
75
|
-
</div>
|
|
76
39
|
<button className="relative flex items-center justify-center h-10 w-10 rounded-full bg-card border border-border text-muted-foreground hover:text-foreground transition-colors">
|
|
77
40
|
<Bell className="h-4 w-4" />
|
|
78
41
|
<span className="absolute -top-0.5 -right-0.5 flex items-center justify-center h-4 w-4 rounded-full bg-destructive text-[10px] font-medium text-white">
|
|
@@ -4,13 +4,12 @@ import DashboardHeader from './DashboardHeader';
|
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
children: ReactNode;
|
|
7
|
-
onOpenOnboard?: () => void;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
export default function DashboardLayout({ children
|
|
9
|
+
export default function DashboardLayout({ children }: Props) {
|
|
11
10
|
return (
|
|
12
11
|
<div className="flex h-dvh flex-col overflow-x-hidden">
|
|
13
|
-
<DashboardHeader
|
|
12
|
+
<DashboardHeader />
|
|
14
13
|
<div className="flex flex-1 overflow-hidden">
|
|
15
14
|
{/* Desktop sidebar */}
|
|
16
15
|
<div className="hidden md:flex shrink-0">
|