fluxy-bot 0.2.30 → 0.2.32

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxy-bot",
3
- "version": "0.2.30",
3
+ "version": "0.2.32",
4
4
  "description": "Self-hosted AI bot — run your own AI assistant from anywhere",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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-[85%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-primary text-primary-foreground">
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-[85%] 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' }}>
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: '85%', overflowWrap: 'anywhere' }}>
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={{
@@ -1,15 +1,15 @@
1
1
  (function () {
2
2
  if (document.getElementById('fluxy-widget')) return;
3
3
 
4
- var PANEL_WIDTH = '400px';
4
+ var PANEL_WIDTH = '480px';
5
5
 
6
6
  // ── Styles ──
7
7
  var style = document.createElement('style');
8
8
  style.textContent = [
9
- '#fluxy-widget-bubble{position:fixed;bottom:24px;right:24px;z-index:99998;cursor:pointer;width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;transition:transform .15s ease;-webkit-tap-highlight-color:transparent}',
9
+ '#fluxy-widget-bubble{position:fixed;bottom:24px;right:24px;z-index:99998;cursor:pointer;width:60px;height:60px;border-radius:50%;display:flex;align-items:center;justify-content:center;transition:transform .15s ease;-webkit-tap-highlight-color:transparent}',
10
10
  '#fluxy-widget-bubble:hover{transform:scale(1.1)}',
11
11
  '#fluxy-widget-bubble:active{transform:scale(0.95)}',
12
- '#fluxy-widget-bubble video,#fluxy-widget-bubble img{height:44px;width:auto;pointer-events:none;-webkit-user-drag:none}',
12
+ '#fluxy-widget-bubble video,#fluxy-widget-bubble img{height:60px;width:auto;pointer-events:none;-webkit-user-drag:none}',
13
13
  '#fluxy-widget-backdrop{position:fixed;inset:0;z-index:99998;background:rgba(0,0,0,0.4);opacity:0;transition:opacity .2s ease;pointer-events:none}',
14
14
  '#fluxy-widget-backdrop.open{opacity:1;pointer-events:auto}',
15
15
  '#fluxy-widget-panel{position:fixed;top:0;right:0;bottom:0;z-index:99999;width:' + PANEL_WIDTH + ';max-width:100vw;transform:translateX(100%);transition:transform .25s cubic-bezier(.4,0,.2,1);box-shadow:-4px 0 24px rgba(0,0,0,0.3);border-left:1px solid #3a3a3a;overflow:hidden}',
@@ -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') {
@@ -77,7 +77,7 @@ export default function App() {
77
77
  return (
78
78
  <>
79
79
  <ErrorBoundary fallback={<DashboardError />}>
80
- <DashboardLayout onOpenOnboard={() => setShowOnboard(true)}>
80
+ <DashboardLayout>
81
81
  <DashboardPage />
82
82
  </DashboardLayout>
83
83
  </ErrorBoundary>
@@ -1,26 +1,9 @@
1
- import { useState, useRef, useEffect } from 'react';
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
- interface Props {
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, onOpenOnboard }: Props) {
9
+ export default function DashboardLayout({ children }: Props) {
11
10
  return (
12
11
  <div className="flex h-dvh flex-col overflow-x-hidden">
13
- <DashboardHeader onOpenOnboard={onOpenOnboard} />
12
+ <DashboardHeader />
14
13
  <div className="flex flex-1 overflow-hidden">
15
14
  {/* Desktop sidebar */}
16
15
  <div className="hidden md:flex shrink-0">