fluxy-bot 0.2.44 → 0.3.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.
@@ -0,0 +1 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-green-500:oklch(72.3% .219 149.579);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--blur-md:12px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-background:#212121;--color-foreground:#f5f5f5}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.inset-y-0{inset-block:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.-top-1\.5{top:calc(var(--spacing) * -1.5)}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing) * 2)}.top-4{top:calc(var(--spacing) * 4)}.top-full{top:100%}.-right-1\.5{right:calc(var(--spacing) * -1.5)}.right-0{right:calc(var(--spacing) * 0)}.right-2{right:calc(var(--spacing) * 2)}.right-3{right:calc(var(--spacing) * 3)}.right-4{right:calc(var(--spacing) * 4)}.left-0{left:calc(var(--spacing) * 0)}.left-1\/2{left:50%}.left-3{left:calc(var(--spacing) * 3)}.z-10{z-index:10}.z-50{z-index:50}.z-\[200\]{z-index:200}.my-1\.5{margin-block:calc(var(--spacing) * 1.5)}.my-2{margin-block:calc(var(--spacing) * 2)}.-mt-3{margin-top:calc(var(--spacing) * -3)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-px{margin-top:1px}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.-ml-1{margin-left:calc(var(--spacing) * -1)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.block{display:block}.flex{display:flex}.hidden{display:none}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-1{height:calc(var(--spacing) * 1)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-11{height:calc(var(--spacing) * 11)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-28{height:calc(var(--spacing) * 28)}.h-\[18px\]{height:18px}.h-\[22px\]{height:22px}.h-\[180px\]{height:180px}.h-dvh{height:100dvh}.h-full{height:100%}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-\[85vh\]{max-height:85vh}.min-h-0{min-height:calc(var(--spacing) * 0)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-11{width:calc(var(--spacing) * 11)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-28{width:calc(var(--spacing) * 28)}.w-\[18px\]{width:18px}.w-auto{width:auto}.w-full{width:100%}.max-w-\[75\%\]{max-width:75%}.max-w-\[90vw\]{max-width:90vw}.max-w-\[92\%\]{max-width:92%}.max-w-\[320px\]{max-width:320px}.max-w-\[480px\]{max-width:480px}.max-w-none{max-width:none}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[160px\]{min-width:160px}.min-w-\[180px\]{min-width:180px}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-\[18px\]{--tw-translate-x:18px;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize-none{resize:none}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-3\.5{gap:calc(var(--spacing) * 3.5)}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.75rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-\[24px\]{border-radius:24px}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-\[1\.5px\]{border-style:var(--tw-border-style);border-width:1.5px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-\[\#AF27E3\]\/20{border-color:#af27e333}.border-\[\#AF27E3\]\/30{border-color:#af27e34d}.border-\[\#AF27E3\]\/40{border-color:#af27e366}.border-amber-500\/20{border-color:#f99c0033}@supports (color:color-mix(in lab,red,red)){.border-amber-500\/20{border-color:color-mix(in oklab,var(--color-amber-500) 20%,transparent)}}.border-border{border-color:#3a3a3a}.border-emerald-500\/15{border-color:#00bb7f26}@supports (color:color-mix(in lab,red,red)){.border-emerald-500\/15{border-color:color-mix(in oklab,var(--color-emerald-500) 15%,transparent)}}.border-emerald-500\/20{border-color:#00bb7f33}@supports (color:color-mix(in lab,red,red)){.border-emerald-500\/20{border-color:color-mix(in oklab,var(--color-emerald-500) 20%,transparent)}}.border-muted-foreground\/30{border-color:#9999994d}.border-red-500\/15{border-color:#fb2c3626}@supports (color:color-mix(in lab,red,red)){.border-red-500\/15{border-color:color-mix(in oklab,var(--color-red-500) 15%,transparent)}}.border-red-500\/20{border-color:#fb2c3633}@supports (color:color-mix(in lab,red,red)){.border-red-500\/20{border-color:color-mix(in oklab,var(--color-red-500) 20%,transparent)}}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.border-white\/10{border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.border-white\/20{border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.border-white\/20{border-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.border-white\/\[0\.04\]{border-color:#ffffff0a}@supports (color:color-mix(in lab,red,red)){.border-white\/\[0\.04\]{border-color:color-mix(in oklab,var(--color-white) 4%,transparent)}}.border-white\/\[0\.06\]{border-color:#ffffff0f}@supports (color:color-mix(in lab,red,red)){.border-white\/\[0\.06\]{border-color:color-mix(in oklab,var(--color-white) 6%,transparent)}}.border-white\/\[0\.08\]{border-color:#ffffff14}@supports (color:color-mix(in lab,red,red)){.border-white\/\[0\.08\]{border-color:color-mix(in oklab,var(--color-white) 8%,transparent)}}.border-t-\[\#04D1FE\]{border-top-color:#04d1fe}.border-t-primary{border-top-color:#3c8fff}.bg-\[\#222\]{background-color:#222}.bg-\[\#181818\]{background-color:#181818}.bg-\[\#AF27E3\]\/10{background-color:#af27e31a}.bg-\[\#AF27E3\]\/15{background-color:#af27e326}.bg-\[\#AF27E3\]\/\[0\.06\]{background-color:#af27e30f}.bg-amber-500\/8{background-color:#f99c0014}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/8{background-color:color-mix(in oklab,var(--color-amber-500) 8%,transparent)}}.bg-amber-500\/15{background-color:#f99c0026}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/15{background-color:color-mix(in oklab,var(--color-amber-500) 15%,transparent)}}.bg-background{background-color:#212121}.bg-black\/80{background-color:#000c}@supports (color:color-mix(in lab,red,red)){.bg-black\/80{background-color:color-mix(in oklab,var(--color-black) 80%,transparent)}}.bg-black\/85{background-color:#000000d9}@supports (color:color-mix(in lab,red,red)){.bg-black\/85{background-color:color-mix(in oklab,var(--color-black) 85%,transparent)}}.bg-black\/90{background-color:#000000e6}@supports (color:color-mix(in lab,red,red)){.bg-black\/90{background-color:color-mix(in oklab,var(--color-black) 90%,transparent)}}.bg-destructive{background-color:#fd486b}.bg-destructive\/10{background-color:#fd486b1a}.bg-emerald-500\/8{background-color:#00bb7f14}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/8{background-color:color-mix(in oklab,var(--color-emerald-500) 8%,transparent)}}.bg-emerald-500\/10{background-color:#00bb7f1a}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/10{background-color:color-mix(in oklab,var(--color-emerald-500) 10%,transparent)}}.bg-emerald-500\/15{background-color:#00bb7f26}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/15{background-color:color-mix(in oklab,var(--color-emerald-500) 15%,transparent)}}.bg-green-500{background-color:var(--color-green-500)}.bg-muted{background-color:#333}.bg-muted-foreground{background-color:#999}.bg-muted-foreground\/60{background-color:#9999}.bg-popover{background-color:#2a2a2a}.bg-primary{background-color:#3c8fff}.bg-red-500{background-color:var(--color-red-500)}.bg-red-500\/8{background-color:#fb2c3614}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/8{background-color:color-mix(in oklab,var(--color-red-500) 8%,transparent)}}.bg-red-500\/10{background-color:#fb2c361a}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/10{background-color:color-mix(in oklab,var(--color-red-500) 10%,transparent)}}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/5{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.bg-white\/5{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.bg-white\/10{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.bg-white\/10{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.bg-white\/20{background-color:#fff3}@supports (color:color-mix(in lab,red,red)){.bg-white\/20{background-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.bg-white\/70{background-color:#ffffffb3}@supports (color:color-mix(in lab,red,red)){.bg-white\/70{background-color:color-mix(in oklab,var(--color-white) 70%,transparent)}}.bg-white\/\[0\.02\]{background-color:#ffffff05}@supports (color:color-mix(in lab,red,red)){.bg-white\/\[0\.02\]{background-color:color-mix(in oklab,var(--color-white) 2%,transparent)}}.bg-white\/\[0\.03\]{background-color:#ffffff08}@supports (color:color-mix(in lab,red,red)){.bg-white\/\[0\.03\]{background-color:color-mix(in oklab,var(--color-white) 3%,transparent)}}.bg-white\/\[0\.04\]{background-color:#ffffff0a}@supports (color:color-mix(in lab,red,red)){.bg-white\/\[0\.04\]{background-color:color-mix(in oklab,var(--color-white) 4%,transparent)}}.bg-white\/\[0\.05\]{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.bg-white\/\[0\.05\]{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.bg-white\/\[0\.06\]{background-color:#ffffff0f}@supports (color:color-mix(in lab,red,red)){.bg-white\/\[0\.06\]{background-color:color-mix(in oklab,var(--color-white) 6%,transparent)}}.bg-white\/\[0\.08\]{background-color:#ffffff14}@supports (color:color-mix(in lab,red,red)){.bg-white\/\[0\.08\]{background-color:color-mix(in oklab,var(--color-white) 8%,transparent)}}.fill-current{fill:currentColor}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-7{padding-inline:calc(var(--spacing) * 7)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-2\.5{padding-top:calc(var(--spacing) * 2.5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pr-0\.5{padding-right:calc(var(--spacing) * .5)}.pr-10{padding-right:calc(var(--spacing) * 10)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pl-1{padding-left:calc(var(--spacing) * 1)}.pl-4{padding-left:calc(var(--spacing) * 4)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.8rem\]{font-size:.8rem}.text-\[8px\]{font-size:8px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#AF27E3\]{color:#af27e3}.text-\[\#AF27E3\]\/60{color:#af27e399}.text-amber-400{color:var(--color-amber-400)}.text-amber-400\/60{color:#fcbb0099}@supports (color:color-mix(in lab,red,red)){.text-amber-400\/60{color:color-mix(in oklab,var(--color-amber-400) 60%,transparent)}}.text-amber-400\/70{color:#fcbb00b3}@supports (color:color-mix(in lab,red,red)){.text-amber-400\/70{color:color-mix(in oklab,var(--color-amber-400) 70%,transparent)}}.text-amber-400\/90{color:#fcbb00e6}@supports (color:color-mix(in lab,red,red)){.text-amber-400\/90{color:color-mix(in oklab,var(--color-amber-400) 90%,transparent)}}.text-destructive{color:#fd486b}.text-destructive-foreground{color:#fff}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-400\/60{color:#00d29499}@supports (color:color-mix(in lab,red,red)){.text-emerald-400\/60{color:color-mix(in oklab,var(--color-emerald-400) 60%,transparent)}}.text-emerald-400\/90{color:#00d294e6}@supports (color:color-mix(in lab,red,red)){.text-emerald-400\/90{color:color-mix(in oklab,var(--color-emerald-400) 90%,transparent)}}.text-foreground{color:#f5f5f5}.text-gray-400{color:var(--color-gray-400)}.text-gray-900{color:var(--color-gray-900)}.text-muted-foreground{color:#999}.text-muted-foreground\/50{color:#99999980}.text-primary-foreground{color:#fff}.text-primary-foreground\/60{color:#fff9}.text-red-400{color:var(--color-red-400)}.text-red-400\/70{color:#ff6568b3}@supports (color:color-mix(in lab,red,red)){.text-red-400\/70{color:color-mix(in oklab,var(--color-red-400) 70%,transparent)}}.text-red-400\/90{color:#ff6568e6}@supports (color:color-mix(in lab,red,red)){.text-red-400\/90{color:color-mix(in oklab,var(--color-red-400) 90%,transparent)}}.text-white{color:var(--color-white)}.text-white\/20{color:#fff3}@supports (color:color-mix(in lab,red,red)){.text-white\/20{color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.text-white\/25{color:#ffffff40}@supports (color:color-mix(in lab,red,red)){.text-white\/25{color:color-mix(in oklab,var(--color-white) 25%,transparent)}}.text-white\/30{color:#ffffff4d}@supports (color:color-mix(in lab,red,red)){.text-white\/30{color:color-mix(in oklab,var(--color-white) 30%,transparent)}}.text-white\/35{color:#ffffff59}@supports (color:color-mix(in lab,red,red)){.text-white\/35{color:color-mix(in oklab,var(--color-white) 35%,transparent)}}.text-white\/40{color:#fff6}@supports (color:color-mix(in lab,red,red)){.text-white\/40{color:color-mix(in oklab,var(--color-white) 40%,transparent)}}.text-white\/50{color:#ffffff80}@supports (color:color-mix(in lab,red,red)){.text-white\/50{color:color-mix(in oklab,var(--color-white) 50%,transparent)}}.text-white\/60{color:#fff9}@supports (color:color-mix(in lab,red,red)){.text-white\/60{color:color-mix(in oklab,var(--color-white) 60%,transparent)}}.text-white\/70{color:#ffffffb3}@supports (color:color-mix(in lab,red,red)){.text-white\/70{color:color-mix(in oklab,var(--color-white) 70%,transparent)}}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-75{opacity:.75}.opacity-100{opacity:1}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.will-change-transform{will-change:transform}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.placeholder\:text-gray-400::placeholder{color:var(--color-gray-400)}.placeholder\:text-white\/20::placeholder{color:#fff3}@supports (color:color-mix(in lab,red,red)){.placeholder\:text-white\/20::placeholder{color:color-mix(in oklab,var(--color-white) 20%,transparent)}}@media(hover:hover){.hover\:border-white\/10:hover{border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.hover\:border-white\/10:hover{border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.hover\:border-white\/15:hover{border-color:#ffffff26}@supports (color:color-mix(in lab,red,red)){.hover\:border-white\/15:hover{border-color:color-mix(in oklab,var(--color-white) 15%,transparent)}}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-white\/20:hover{background-color:#fff3}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/20:hover{background-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.hover\:bg-white\/30:hover{background-color:#ffffff4d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/30:hover{background-color:color-mix(in oklab,var(--color-white) 30%,transparent)}}.hover\:bg-white\/\[0\.1\]:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/\[0\.1\]:hover{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.hover\:bg-white\/\[0\.02\]:hover{background-color:#ffffff05}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/\[0\.02\]:hover{background-color:color-mix(in oklab,var(--color-white) 2%,transparent)}}.hover\:bg-white\/\[0\.04\]:hover{background-color:#ffffff0a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/\[0\.04\]:hover{background-color:color-mix(in oklab,var(--color-white) 4%,transparent)}}.hover\:bg-white\/\[0\.06\]:hover{background-color:#ffffff0f}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/\[0\.06\]:hover{background-color:color-mix(in oklab,var(--color-white) 6%,transparent)}}.hover\:bg-white\/\[0\.08\]:hover{background-color:#ffffff14}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/\[0\.08\]:hover{background-color:color-mix(in oklab,var(--color-white) 8%,transparent)}}.hover\:bg-white\/\[0\.09\]:hover{background-color:#ffffff17}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/\[0\.09\]:hover{background-color:color-mix(in oklab,var(--color-white) 9%,transparent)}}.hover\:text-foreground:hover{color:#f5f5f5}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-white:hover{color:var(--color-white)}.hover\:text-white\/40:hover{color:#fff6}@supports (color:color-mix(in lab,red,red)){.hover\:text-white\/40:hover{color:color-mix(in oklab,var(--color-white) 40%,transparent)}}.hover\:text-white\/50:hover{color:#ffffff80}@supports (color:color-mix(in lab,red,red)){.hover\:text-white\/50:hover{color:color-mix(in oklab,var(--color-white) 50%,transparent)}}.hover\:text-white\/60:hover{color:#fff9}@supports (color:color-mix(in lab,red,red)){.hover\:text-white\/60:hover{color:color-mix(in oklab,var(--color-white) 60%,transparent)}}.hover\:opacity-80:hover{opacity:.8}.hover\:opacity-90:hover{opacity:.9}}.focus\:border-\[\#AF27E3\]\/30:focus{border-color:#af27e34d}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-60:disabled{opacity:.6}.\[\&\>\*\:first-child\]\:mt-0>:first-child{margin-top:calc(var(--spacing) * 0)}.\[\&\>\*\:last-child\]\:mb-0>:last-child{margin-bottom:calc(var(--spacing) * 0)}}body{background-color:var(--color-background);color:var(--color-foreground);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}::selection{background-color:#af27e340}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:#3a3a3a;border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#4a4a4a}.text-gradient{color:#0000;-webkit-text-fill-color:transparent;background-image:linear-gradient(135deg,#04d1fe,#af27e3,#fb4072);-webkit-background-clip:text;background-clip:text}.bg-gradient-brand{background-image:linear-gradient(135deg,#04d1fe,#af27e3,#fb4072)}.glow-border{box-shadow:0 0 0 1px #af27e31a,0 0 20px -5px #af27e326}.animated-border{position:relative;overflow:hidden}.animated-border:before{content:"";background:conic-gradient(#04d1fe,#af27e3,#fb4072,#04d1fe);animation:3s linear infinite border-spin;position:absolute;inset:-150%}.animated-border>*{z-index:1;position:relative}.animated-border-slow:before{animation-duration:5s}.input-glow:focus{border-color:#af27e366;box-shadow:0 0 0 1px #af27e326,0 0 20px -5px #af27e340,0 0 4px -1px #04d1fe1a}@keyframes border-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}
@@ -1 +1 @@
1
- import{a as o,j as e,R as n,O as r}from"./globals--DVlL_N5.js";function a(){const t=()=>{window.parent?.postMessage({type:"fluxy:onboard-complete"},"*")};return e.jsx(r,{onComplete:t})}o.createRoot(document.getElementById("root")).render(e.jsx(n.StrictMode,{children:e.jsx(a,{})}));
1
+ import{a as o,j as e,R as n,O as r}from"./globals-CnbiE5KJ.js";function a(){const t=()=>{window.parent?.postMessage({type:"fluxy:onboard-complete"},"*")};return e.jsx(r,{onComplete:t})}o.createRoot(document.getElementById("root")).render(e.jsx(n.StrictMode,{children:e.jsx(a,{})}));
@@ -4,9 +4,9 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content" />
6
6
  <title>Fluxy Chat</title>
7
- <script type="module" crossorigin src="/fluxy/assets/fluxy-B20BJakh.js"></script>
8
- <link rel="modulepreload" crossorigin href="/fluxy/assets/globals--DVlL_N5.js">
9
- <link rel="stylesheet" crossorigin href="/fluxy/assets/globals-DMXu7CFU.css">
7
+ <script type="module" crossorigin src="/fluxy/assets/fluxy-tSkoe-BG.js"></script>
8
+ <link rel="modulepreload" crossorigin href="/fluxy/assets/globals-CnbiE5KJ.js">
9
+ <link rel="stylesheet" crossorigin href="/fluxy/assets/globals-CZf7hjQW.css">
10
10
  </head>
11
11
  <body class="bg-background text-foreground">
12
12
  <div id="root"></div>
@@ -4,9 +4,9 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content" />
6
6
  <title>Fluxy Setup</title>
7
- <script type="module" crossorigin src="/fluxy/assets/onboard-CdR_xRWS.js"></script>
8
- <link rel="modulepreload" crossorigin href="/fluxy/assets/globals--DVlL_N5.js">
9
- <link rel="stylesheet" crossorigin href="/fluxy/assets/globals-DMXu7CFU.css">
7
+ <script type="module" crossorigin src="/fluxy/assets/onboard-B_QyBFoO.js"></script>
8
+ <link rel="modulepreload" crossorigin href="/fluxy/assets/globals-CnbiE5KJ.js">
9
+ <link rel="stylesheet" crossorigin href="/fluxy/assets/globals-CZf7hjQW.css">
10
10
  </head>
11
11
  <body class="bg-background text-foreground">
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxy-bot",
3
- "version": "0.2.44",
3
+ "version": "0.3.1",
4
4
  "description": "Self-hosted AI bot — run your own AI assistant from anywhere",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -55,14 +55,12 @@
55
55
  "date-fns": "^4.1.0",
56
56
  "express": "^5.2.1",
57
57
  "framer-motion": "^12.34.3",
58
- "jose": "^6.1.3",
59
58
  "lucide-react": "^0.575.0",
60
59
  "postcss": "^8.5.6",
61
60
  "radix-ui": "^1.4.3",
62
61
  "react": "^19.2.4",
63
62
  "react-dom": "^19.2.4",
64
63
  "react-markdown": "^10.1.0",
65
- "react-router-dom": "^7.13.1",
66
64
  "react-syntax-highlighter": "^16.1.0",
67
65
  "recharts": "^3.7.0",
68
66
  "remark-gfm": "^4.0.1",
package/shared/config.ts CHANGED
@@ -17,7 +17,6 @@ export interface BotConfig {
17
17
  url: string;
18
18
  };
19
19
  tunnelUrl?: string;
20
- jwtSecret?: string;
21
20
  }
22
21
 
23
22
  const DEFAULTS: BotConfig = {
@@ -4,92 +4,16 @@ import { ArrowLeft, MoreVertical, Trash2, Wand2 } from 'lucide-react';
4
4
  import { WsClient } from './src/lib/ws-client';
5
5
  import { useFluxyChat } from './src/hooks/useFluxyChat';
6
6
  import OnboardWizard from './OnboardWizard';
7
+ import LoginScreen from './src/components/LoginScreen';
8
+ import { getAuthToken, setAuthToken, clearAuthToken, authFetch } from './src/lib/auth';
7
9
  import MessageList from './src/components/Chat/MessageList';
8
10
  import InputBar from './src/components/Chat/InputBar';
9
11
  import './src/styles/globals.css';
10
12
 
11
- type AuthState = 'checking' | 'authenticated' | 'unauthenticated' | 'first-run';
12
-
13
- function LoginForm({ botName, onSuccess }: { botName: string; onSuccess: () => void }) {
14
- const [username, setUsername] = useState('');
15
- const [password, setPassword] = useState('');
16
- const [error, setError] = useState('');
17
- const [loading, setLoading] = useState(false);
18
-
19
- const handleSubmit = async (e: React.FormEvent) => {
20
- e.preventDefault();
21
- setError('');
22
- setLoading(true);
23
- try {
24
- const res = await fetch('/api/auth/login', {
25
- method: 'POST',
26
- headers: { 'Content-Type': 'application/json' },
27
- body: JSON.stringify({ username, password }),
28
- });
29
- const contentType = res.headers.get('content-type') || '';
30
- if (!contentType.includes('application/json')) {
31
- console.error(`[login] Non-JSON response: ${res.status} ${contentType}`);
32
- setError(`Server error (${res.status})`);
33
- return;
34
- }
35
- const data = await res.json();
36
- if (data.ok) {
37
- onSuccess();
38
- } else {
39
- setError(data.error || 'Login failed');
40
- }
41
- } catch (err) {
42
- console.error('[login] Fetch error:', err);
43
- setError('Connection error — check if server is running');
44
- } finally {
45
- setLoading(false);
46
- }
47
- };
48
-
49
- return (
50
- <div className="flex-1 flex flex-col items-center justify-center px-6 py-8">
51
- <img src="/fluxy.png" alt={botName} className="h-10 w-auto mb-4" />
52
- <h2 className="text-lg font-semibold mb-1">Sign in to chat</h2>
53
- <p className="text-sm text-muted-foreground mb-6">Enter your portal credentials</p>
54
-
55
- <form onSubmit={handleSubmit} className="w-full max-w-[280px] flex flex-col gap-3">
56
- <input
57
- type="text"
58
- placeholder="Username"
59
- value={username}
60
- onChange={(e) => setUsername(e.target.value)}
61
- autoComplete="username"
62
- className="w-full bg-white/[0.05] border border-white/[0.08] rounded-xl px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground outline-none input-glow"
63
- />
64
- <input
65
- type="password"
66
- placeholder="Password"
67
- value={password}
68
- onChange={(e) => setPassword(e.target.value)}
69
- autoComplete="current-password"
70
- className="w-full bg-white/[0.05] border border-white/[0.08] rounded-xl px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground outline-none input-glow"
71
- />
72
- {error && (
73
- <div className="bg-red-500/8 border border-red-500/15 rounded-lg px-3 py-2 text-xs text-red-400">
74
- {error}
75
- </div>
76
- )}
77
- <button
78
- type="submit"
79
- disabled={loading || !username || !password}
80
- className="w-full bg-gradient-brand text-white font-medium rounded-full py-3 text-sm hover:opacity-90 transition-opacity disabled:opacity-50"
81
- >
82
- {loading ? 'Signing in...' : 'Sign In'}
83
- </button>
84
- </form>
85
- </div>
86
- );
87
- }
88
-
89
- /** Chat view — only mounted when authenticated or first-run */
90
- function ChatView({ botName }: { botName: string }) {
13
+ function FluxyApp() {
91
14
  const clientRef = useRef<WsClient | null>(null);
92
15
  const [connected, setConnected] = useState(false);
16
+ const [botName, setBotName] = useState('Fluxy');
93
17
  const [whisperEnabled, setWhisperEnabled] = useState(false);
94
18
  const [menuOpen, setMenuOpen] = useState(false);
95
19
  const [showWizard, setShowWizard] = useState(false);
@@ -97,20 +21,78 @@ function ChatView({ botName }: { botName: string }) {
97
21
  const menuRef = useRef<HTMLDivElement>(null);
98
22
  const wasConnected = useRef(false);
99
23
 
24
+ // Auth state
25
+ const [authChecked, setAuthChecked] = useState(false);
26
+ const [authRequired, setAuthRequired] = useState(false);
27
+ const [authenticated, setAuthenticated] = useState(false);
28
+
29
+ // Check auth on mount
30
+ useEffect(() => {
31
+ (async () => {
32
+ try {
33
+ const res = await fetch('/api/onboard/status');
34
+ const data = await res.json();
35
+ if (!data.portalConfigured) {
36
+ // No password set — skip auth entirely
37
+ setAuthRequired(false);
38
+ setAuthenticated(true);
39
+ setAuthChecked(true);
40
+ return;
41
+ }
42
+
43
+ setAuthRequired(true);
44
+
45
+ // Check if we have a valid token in localStorage
46
+ const token = getAuthToken();
47
+ if (token) {
48
+ const vRes = await fetch('/api/portal/validate-token', {
49
+ method: 'POST',
50
+ headers: { 'Content-Type': 'application/json' },
51
+ body: JSON.stringify({ token }),
52
+ });
53
+ const vData = await vRes.json();
54
+ if (vData.valid) {
55
+ setAuthenticated(true);
56
+ setAuthChecked(true);
57
+ return;
58
+ }
59
+ clearAuthToken();
60
+ }
61
+
62
+ setAuthenticated(false);
63
+ setAuthChecked(true);
64
+ } catch {
65
+ // Worker not ready — skip auth, let it retry later
66
+ setAuthenticated(true);
67
+ setAuthChecked(true);
68
+ }
69
+ })();
70
+ }, []);
71
+
72
+ const handleLogin = (token: string) => {
73
+ setAuthToken(token);
74
+ setAuthenticated(true);
75
+ };
76
+
77
+ // Connect WebSocket only when authenticated
100
78
  useEffect(() => {
79
+ if (!authenticated) return;
80
+
101
81
  const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
102
82
  const host = location.host;
103
- const client = new WsClient(`${proto}//${host}/fluxy/ws`);
83
+ const client = new WsClient(`${proto}//${host}/fluxy/ws`, getAuthToken);
104
84
  clientRef.current = client;
105
85
 
106
86
  const unsub = client.onStatus((isConnected) => {
107
87
  setConnected(isConnected);
88
+ // On reconnect, trigger a reload from DB to catch missed messages
108
89
  if (isConnected && wasConnected.current) {
109
90
  setReloadTrigger((n) => n + 1);
110
91
  }
111
92
  wasConnected.current = isConnected;
112
93
  });
113
94
 
95
+ // Forward rebuild/HMR events to parent (dashboard) via postMessage
114
96
  const unsubRebuilding = client.on('app:rebuilding', () => {
115
97
  console.log('[fluxy] app:rebuilding received');
116
98
  window.parent?.postMessage({ type: 'fluxy:rebuilding' }, '*');
@@ -137,19 +119,22 @@ function ChatView({ botName }: { botName: string }) {
137
119
  unsubBuildError();
138
120
  unsubHmr();
139
121
  client.disconnect();
140
- clientRef.current = null;
141
122
  };
142
- }, []);
123
+ }, [authenticated]);
143
124
 
125
+ // Try to load settings (will work when worker is up, fail silently when down)
144
126
  useEffect(() => {
145
- fetch('/api/settings')
127
+ if (!authenticated) return;
128
+ authFetch('/api/settings')
146
129
  .then((r) => r.json())
147
130
  .then((s) => {
131
+ if (s.agent_name) setBotName(s.agent_name);
148
132
  if (s.whisper_enabled === 'true') setWhisperEnabled(true);
149
133
  })
150
134
  .catch(() => {});
151
- }, []);
135
+ }, [authenticated]);
152
136
 
137
+ // Close menu on outside click
153
138
  useEffect(() => {
154
139
  if (!menuOpen) return;
155
140
  const handler = (e: MouseEvent) => {
@@ -162,11 +147,34 @@ function ChatView({ botName }: { botName: string }) {
162
147
  const { messages, streaming, streamBuffer, tools, sendMessage, stopStreaming, clearContext } =
163
148
  useFluxyChat(clientRef.current, reloadTrigger);
164
149
 
150
+ // Auth gate: show spinner while checking, login screen if needed
151
+ if (!authChecked) {
152
+ return (
153
+ <div className="flex items-center justify-center h-dvh">
154
+ <div className="w-6 h-6 border-2 border-white/10 border-t-white/50 rounded-full animate-spin" />
155
+ </div>
156
+ );
157
+ }
158
+
159
+ if (authRequired && !authenticated) {
160
+ return <LoginScreen onLogin={handleLogin} />;
161
+ }
162
+
165
163
  return (
166
- <>
167
- {/* Status + menu in header area */}
168
- <div className="flex items-center gap-2 shrink-0" style={{ position: 'absolute', right: 12, top: 10 }}>
164
+ <div className="flex flex-col h-dvh overflow-hidden">
165
+ {/* Header */}
166
+ <div className="flex items-center gap-3 px-4 py-3 border-b border-border shrink-0">
167
+ <button
168
+ onClick={() => window.parent?.postMessage({ type: 'fluxy:close' }, '*')}
169
+ 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"
170
+ aria-label="Close chat"
171
+ >
172
+ <ArrowLeft className="h-5 w-5" />
173
+ </button>
174
+ <img src="/fluxy.png" alt={botName} className="h-5 w-auto" />
175
+ <span className="text-sm font-semibold">{botName}</span>
169
176
  <div className={`h-2 w-2 rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`} />
177
+ <div className="flex-1" />
170
178
  <div className="relative" ref={menuRef}>
171
179
  <button
172
180
  onClick={() => setMenuOpen((v) => !v)}
@@ -195,91 +203,30 @@ function ChatView({ botName }: { botName: string }) {
195
203
  </div>
196
204
  </div>
197
205
 
206
+ {/* Chat body */}
198
207
  <div className="flex-1 min-h-0 flex flex-col overflow-hidden">
199
208
  <MessageList messages={messages} streaming={streaming} streamBuffer={streamBuffer} tools={tools} />
200
209
  <InputBar onSend={sendMessage} onStop={stopStreaming} streaming={streaming} whisperEnabled={whisperEnabled} />
201
210
  </div>
202
211
 
212
+ {/* Setup Wizard overlay */}
203
213
  {showWizard && (
204
214
  <OnboardWizard
205
215
  onComplete={() => {
206
216
  setShowWizard(false);
207
- fetch('/api/settings')
217
+ // Reload settings (bot name, whisper, etc.)
218
+ authFetch('/api/settings')
208
219
  .then((r) => r.json())
209
220
  .then((s) => {
210
- if (s.whisper_enabled === 'true') setWhisperEnabled(true);
221
+ if (s.agent_name) setBotName(s.agent_name);
222
+ setWhisperEnabled(s.whisper_enabled === 'true');
211
223
  })
212
224
  .catch(() => {});
225
+ // Notify dashboard so it can refresh
213
226
  window.parent?.postMessage({ type: 'fluxy:onboard-complete' }, '*');
214
227
  }}
215
228
  />
216
229
  )}
217
- </>
218
- );
219
- }
220
-
221
- function FluxyApp() {
222
- const [botName, setBotName] = useState('Fluxy');
223
- const [authState, setAuthState] = useState<AuthState>('checking');
224
-
225
- // Check auth on mount
226
- useEffect(() => {
227
- (async () => {
228
- try {
229
- const meRes = await fetch('/api/auth/me');
230
- if (meRes.ok) {
231
- const me = await meRes.json();
232
- if (me.authenticated) { setAuthState('authenticated'); return; }
233
- }
234
-
235
- const cfgRes = await fetch('/api/auth/configured');
236
- const cfg = await cfgRes.json();
237
- setAuthState(cfg.configured ? 'unauthenticated' : 'first-run');
238
- } catch {
239
- setAuthState('first-run');
240
- }
241
- })();
242
- }, []);
243
-
244
- // Load bot name
245
- useEffect(() => {
246
- fetch('/api/settings')
247
- .then((r) => r.json())
248
- .then((s) => { if (s.agent_name) setBotName(s.agent_name); })
249
- .catch(() => {});
250
- }, []);
251
-
252
- const showChat = authState === 'authenticated' || authState === 'first-run';
253
-
254
- return (
255
- <div className="flex flex-col h-dvh overflow-hidden relative">
256
- {/* Header */}
257
- <div className="flex items-center gap-3 px-4 py-3 border-b border-border shrink-0">
258
- <button
259
- onClick={() => window.parent?.postMessage({ type: 'fluxy:close' }, '*')}
260
- 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"
261
- aria-label="Close chat"
262
- >
263
- <ArrowLeft className="h-5 w-5" />
264
- </button>
265
- <img src="/fluxy.png" alt={botName} className="h-5 w-auto" />
266
- <span className="text-sm font-semibold">{botName}</span>
267
- <div className="flex-1" />
268
- </div>
269
-
270
- {/* Auth gate */}
271
- {authState === 'checking' && (
272
- <div className="flex-1 flex items-center justify-center">
273
- <div className="h-6 w-6 border-2 border-muted-foreground/30 border-t-foreground rounded-full animate-spin" />
274
- </div>
275
- )}
276
-
277
- {authState === 'unauthenticated' && (
278
- <LoginForm botName={botName} onSuccess={() => setAuthState('authenticated')} />
279
- )}
280
-
281
- {/* Chat — only mounted when auth is resolved, so useFluxyChat won't fire 401s */}
282
- {showChat && <ChatView botName={botName} />}
283
230
  </div>
284
231
  );
285
232
  }
@@ -0,0 +1,87 @@
1
+ import { useState, type KeyboardEvent } from 'react';
2
+ import { Lock, LoaderCircle, ArrowRight } from 'lucide-react';
3
+
4
+ interface Props {
5
+ onLogin: (token: string) => void;
6
+ }
7
+
8
+ export default function LoginScreen({ onLogin }: Props) {
9
+ const [password, setPassword] = useState('');
10
+ const [error, setError] = useState('');
11
+ const [loading, setLoading] = useState(false);
12
+
13
+ const handleSubmit = async () => {
14
+ if (!password.trim() || loading) return;
15
+ setLoading(true);
16
+ setError('');
17
+
18
+ try {
19
+ const res = await fetch('/api/portal/login', {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json' },
22
+ body: JSON.stringify({ password }),
23
+ });
24
+ const data = await res.json();
25
+
26
+ if (res.ok && data.token) {
27
+ onLogin(data.token);
28
+ } else {
29
+ setError(data.error || 'Invalid password');
30
+ }
31
+ } catch {
32
+ setError('Could not reach server');
33
+ } finally {
34
+ setLoading(false);
35
+ }
36
+ };
37
+
38
+ const handleKeyDown = (e: KeyboardEvent) => {
39
+ if (e.key === 'Enter') handleSubmit();
40
+ };
41
+
42
+ return (
43
+ <div className="flex flex-col items-center justify-center h-dvh px-6">
44
+ <div className="w-full max-w-[320px] flex flex-col items-center">
45
+ <div className="w-14 h-14 rounded-2xl bg-white/[0.04] border border-white/[0.08] flex items-center justify-center mb-5">
46
+ <Lock className="h-6 w-6 text-white/40" />
47
+ </div>
48
+
49
+ <h1 className="text-xl font-bold text-white tracking-tight mb-1">
50
+ Welcome back
51
+ </h1>
52
+ <p className="text-white/40 text-[13px] mb-6">
53
+ Enter your portal password to continue.
54
+ </p>
55
+
56
+ {error && (
57
+ <div className="w-full bg-red-500/8 border border-red-500/15 rounded-xl px-4 py-2.5 mb-4">
58
+ <p className="text-red-400/90 text-[12px]">{error}</p>
59
+ </div>
60
+ )}
61
+
62
+ <input
63
+ type="password"
64
+ value={password}
65
+ onChange={(e) => setPassword(e.target.value)}
66
+ onKeyDown={handleKeyDown}
67
+ placeholder="Password"
68
+ autoFocus
69
+ autoComplete="current-password"
70
+ className="w-full bg-white/[0.05] border border-white/[0.08] text-white rounded-xl px-4 py-3 text-base outline-none input-glow placeholder:text-white/20 transition-all"
71
+ />
72
+
73
+ <button
74
+ onClick={handleSubmit}
75
+ disabled={!password.trim() || loading}
76
+ className="w-full mt-4 py-3 bg-gradient-brand hover:opacity-90 text-white text-[14px] font-semibold rounded-full transition-colors flex items-center justify-center gap-2 disabled:opacity-40"
77
+ >
78
+ {loading ? (
79
+ <><LoaderCircle className="h-4 w-4 animate-spin" />Signing in...</>
80
+ ) : (
81
+ <>Sign In<ArrowRight className="h-4 w-4" /></>
82
+ )}
83
+ </button>
84
+ </div>
85
+ </div>
86
+ );
87
+ }
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import type { WsClient } from '../lib/ws-client';
3
3
  import type { ChatMessage, ToolActivity, Attachment, StoredAttachment } from './useChat';
4
+ import { authFetch } from '../lib/auth';
4
5
 
5
6
  /**
6
7
  * Chat hook for the standalone Fluxy chat app.
@@ -18,11 +19,11 @@ export function useFluxyChat(ws: WsClient | null, triggerReload?: number) {
18
19
  // Load current conversation from DB
19
20
  const loadFromDb = useCallback(async () => {
20
21
  try {
21
- const ctx = await fetch('/api/context/current').then((r) => r.json());
22
+ const ctx = await authFetch('/api/context/current').then((r) => r.json());
22
23
  if (!ctx.conversationId) return;
23
24
  setConversationId(ctx.conversationId);
24
25
 
25
- const res = await fetch(`/api/conversations/${ctx.conversationId}`);
26
+ const res = await authFetch(`/api/conversations/${ctx.conversationId}`);
26
27
  if (!res.ok) return;
27
28
  const data = await res.json();
28
29
  if (!data.messages?.length) return;
@@ -0,0 +1,30 @@
1
+ const TOKEN_KEY = 'fluxy_token';
2
+
3
+ export function getAuthToken(): string | null {
4
+ return localStorage.getItem(TOKEN_KEY);
5
+ }
6
+
7
+ export function setAuthToken(token: string): void {
8
+ localStorage.setItem(TOKEN_KEY, token);
9
+ }
10
+
11
+ export function clearAuthToken(): void {
12
+ localStorage.removeItem(TOKEN_KEY);
13
+ }
14
+
15
+ export async function authFetch(url: string, options: RequestInit = {}): Promise<Response> {
16
+ const token = getAuthToken();
17
+ const headers = new Headers(options.headers);
18
+ if (token) {
19
+ headers.set('Authorization', `Bearer ${token}`);
20
+ }
21
+
22
+ const res = await fetch(url, { ...options, headers });
23
+
24
+ if (res.status === 401) {
25
+ clearAuthToken();
26
+ window.location.reload();
27
+ }
28
+
29
+ return res;
30
+ }