├── .prettierrc ├── src ├── vite-env.d.ts ├── utils │ ├── mod.ts │ ├── nodeId.ts │ ├── rand.ts │ ├── apikey.ts │ ├── qparams.ts │ ├── clipboard.ts │ ├── resize.ts │ ├── debounce.ts │ ├── lstore.ts │ ├── types.ts │ ├── color.ts │ ├── fluxEdge.ts │ ├── constants.ts │ ├── prompt.ts │ ├── fluxNode.ts │ └── chakra.tsx ├── index.css ├── components │ ├── utils │ │ ├── BigButton.tsx │ │ ├── APIKeyInput.tsx │ │ ├── TextAndCodeBlock.tsx │ │ ├── LabeledInputs.tsx │ │ └── NavigationBar.tsx │ ├── modals │ │ ├── APIKeyModal.tsx │ │ └── SettingsModal.tsx │ ├── nodes │ │ └── LabelUpdaterNode.tsx │ ├── Prompt.tsx │ └── App.tsx └── main.tsx ├── public ├── dave.jpg ├── meta.jpg ├── t11s.jpg ├── favicon.ico ├── meta-full.png ├── paradigm-full.svg └── paradigm.svg ├── vite.config.ts ├── tsconfig.node.json ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── tsconfig.json ├── index.html ├── LICENSE ├── package.json ├── README.md └── CONTRIBUTING.md /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 90 3 | } 4 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/dave.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey/flux/main/public/dave.jpg -------------------------------------------------------------------------------- /public/meta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey/flux/main/public/meta.jpg -------------------------------------------------------------------------------- /public/t11s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey/flux/main/public/t11s.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey/flux/main/public/favicon.ico -------------------------------------------------------------------------------- /public/meta-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey/flux/main/public/meta-full.png -------------------------------------------------------------------------------- /src/utils/mod.ts: -------------------------------------------------------------------------------- 1 | export function mod(n: number, m: number) { 2 | return ((n % m) + m) % m; 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/nodeId.ts: -------------------------------------------------------------------------------- 1 | export function generateNodeId() { 2 | return Math.random().toString().replace("0.", ""); 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/rand.ts: -------------------------------------------------------------------------------- 1 | export function randomNumber(min: number, max: number) { 2 | return Math.random() * (max - min) + min; 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/apikey.ts: -------------------------------------------------------------------------------- 1 | export function isValidAPIKey(apiKey: string | null) { 2 | return apiKey?.length == 51 && apiKey?.startsWith("sk-"); 3 | } 4 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react-swc"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }); 8 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/qparams.ts: -------------------------------------------------------------------------------- 1 | export function getQueryParam(parameterName: string) { 2 | const urlParams = new URLSearchParams(window.location.search); 3 | return urlParams.get(parameterName); 4 | } 5 | 6 | export function resetURL() { 7 | history.replaceState({}, document.title, window.location.pathname); 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/clipboard.ts: -------------------------------------------------------------------------------- 1 | export const copySnippetToClipboard = async (text: string): Promise => { 2 | try { 3 | await navigator.clipboard.writeText(text); 4 | return true; 5 | } catch (err) { 6 | console.error("Failed to copy to clipboard", err); 7 | return false; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | .selected { 2 | box-shadow: 0px 0px 0px 2px red, 0px 0px 30px 2px red !important; 3 | } 4 | 5 | .react-flow__node { 6 | border-radius: 6px; 7 | border-width: 0px; 8 | } 9 | 10 | .react-flow__node:not(.selected):hover { 11 | box-shadow: 0 0 0 0.5px #1a192b !important; 12 | } 13 | 14 | /* Enable if you 15 | want bigger handles: 16 | .react-flow__handle { 17 | width: 20px; 18 | height: 7px; 19 | border-radius: 5px; 20 | } */ 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "fix: " 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | **Platform** 10 | The OS and browser you're using. 11 | 12 | **Description** 13 | Enter your issue details here. 14 | One way to structure the description: 15 | 16 | [short summary of the bug] 17 | 18 | I tried doing this: 19 | 20 | [screenshot or video that causes the bug] 21 | 22 | I expected to see this happen: [explanation] 23 | 24 | Instead, this happened: [explanation] 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ## Motivation 7 | 8 | 13 | 14 | ## Solution 15 | 16 | 20 | -------------------------------------------------------------------------------- /src/components/utils/BigButton.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonProps, Button, Tooltip } from "@chakra-ui/react"; 2 | 3 | import { adjustColor } from "../../utils/color"; 4 | 5 | export function BigButton({ 6 | color, 7 | tooltip, 8 | ...others 9 | }: { color: string; tooltip: string } & ButtonProps) { 10 | return ( 11 | 12 | 43 | ); 44 | }; 45 | 46 | const TitleBar = ({ language, code }: { language?: string; code: string }) => { 47 | return ( 48 | 60 | {language || "plaintext"} 61 | 62 | 63 | ); 64 | }; 65 | 66 | export const TextAndCodeBlock = memo(({ text }: { text: string }) => { 67 | let remainingText = text; 68 | 69 | const elements: React.ReactNode[] = []; 70 | 71 | while (remainingText.length > 0) { 72 | const match = CODE_BLOCK_DETECT_REGEX.exec(remainingText); 73 | 74 | if (!match) { 75 | elements.push({remainingText}); 76 | break; 77 | } 78 | 79 | const before = remainingText.substring(0, match.index); 80 | const language = 81 | CODE_BLOCK_LANGUAGE_DETECT_REGEX.exec(match[1])?.[0]?.substring(3) || "plaintext"; 82 | const code = match[2].trim(); 83 | const after = remainingText.substring(match.index + match[0].length); 84 | 85 | if (before.length > 0) 86 | elements.push( 87 | 88 | {before} 89 | 90 | ); 91 | 92 | elements.push( 93 | 94 | 95 | 102 | {code} 103 | 104 | 105 | ); 106 | 107 | remainingText = after; 108 | } 109 | 110 | return {elements}; 111 | }); 112 | -------------------------------------------------------------------------------- /src/components/modals/SettingsModal.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { 4 | Button, 5 | Text, 6 | Modal, 7 | ModalBody, 8 | ModalCloseButton, 9 | ModalContent, 10 | ModalFooter, 11 | ModalHeader, 12 | ModalOverlay, 13 | Checkbox, 14 | } from "@chakra-ui/react"; 15 | 16 | import { LabeledSelect, LabeledSlider } from "../utils/LabeledInputs"; 17 | 18 | import { APIKeyInput } from "../utils/APIKeyInput"; 19 | import { Settings, FluxNodeType } from "../../utils/types"; 20 | import { getFluxNodeTypeDarkColor } from "../../utils/color"; 21 | import { DEFAULT_SETTINGS, SUPPORTED_MODELS } from "../../utils/constants"; 22 | 23 | export const SettingsModal = React.memo(function SettingsModal({ 24 | isOpen, 25 | onClose, 26 | settings, 27 | setSettings, 28 | apiKey, 29 | setApiKey, 30 | }: { 31 | isOpen: boolean; 32 | onClose: () => void; 33 | settings: Settings; 34 | setSettings: (settings: Settings) => void; 35 | apiKey: string | null; 36 | setApiKey: (apiKey: string) => void; 37 | }) { 38 | const reset = () => 39 | confirm( 40 | "Are you sure you want to reset your settings to default? This cannot be undone!" 41 | ) && setSettings(DEFAULT_SETTINGS); 42 | 43 | const hardReset = () => { 44 | if ( 45 | confirm( 46 | "Are you sure you want to delete ALL data (including your saved API key, conversations, etc?) This cannot be undone!" 47 | ) && 48 | confirm( 49 | "Are you 100% sure? Reminder this cannot be undone and you will lose EVERYTHING!" 50 | ) 51 | ) { 52 | // Clear local storage. 53 | localStorage.clear(); 54 | 55 | // Ensure that the page is reloaded even if there are unsaved changes. 56 | window.onbeforeunload = null; 57 | 58 | // Reload the window. 59 | window.location.reload(); 60 | } 61 | }; 62 | 63 | return ( 64 | 65 | 66 | 67 | Settings 68 | 69 | 70 | setSettings({ ...settings, model: v })} 75 | /> 76 | 77 | 78 | 79 | setSettings({ ...settings, temp: v })} 84 | color={getFluxNodeTypeDarkColor(FluxNodeType.User)} 85 | max={1.25} 86 | min={0} 87 | step={0.01} 88 | /> 89 | 90 | setSettings({ ...settings, n: v })} 95 | color={getFluxNodeTypeDarkColor(FluxNodeType.User)} 96 | max={10} 97 | min={1} 98 | step={1} 99 | /> 100 | 101 | 107 | setSettings({ ...settings, autoZoom: event.target.checked }) 108 | } 109 | > 110 | Auto Zoom 111 | 112 | 113 | 114 | 115 | 118 | 119 | 122 | 123 | 124 | 125 | ); 126 | }); 127 | -------------------------------------------------------------------------------- /src/components/utils/LabeledInputs.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | import { 4 | Box, 5 | BoxProps, 6 | Button, 7 | Input, 8 | InputGroup, 9 | InputRightElement, 10 | Link, 11 | Select, 12 | Slider, 13 | SliderFilledTrack, 14 | SliderThumb, 15 | SliderTrack, 16 | Textarea, 17 | } from "@chakra-ui/react"; 18 | import { Row } from "../../utils/chakra"; 19 | import { ExternalLinkIcon } from "@chakra-ui/icons"; 20 | 21 | export function LabeledSlider({ 22 | label, 23 | value, 24 | setValue, 25 | color, 26 | max, 27 | min, 28 | step, 29 | ...others 30 | }: { 31 | label: string; 32 | value: number; 33 | setValue: (value: number) => void; 34 | color: string; 35 | max: number; 36 | min: number; 37 | step: number; 38 | } & BoxProps) { 39 | return ( 40 | 41 | {label}: {value} 42 | setValue(v)} 47 | max={max} 48 | min={min} 49 | step={step} 50 | > 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | ); 59 | } 60 | 61 | export function LabeledInput({ 62 | label, 63 | value, 64 | setValue, 65 | ...others 66 | }: { 67 | label: string; 68 | value: string; 69 | setValue: (value: string) => void; 70 | } & BoxProps) { 71 | return ( 72 | 73 | {label}: 74 | setValue(e.target.value)} value={value} /> 75 | 76 | ); 77 | } 78 | 79 | export function LabeledPasswordInputWithLink({ 80 | label, 81 | linkLabel, 82 | placeholder, 83 | link, 84 | value, 85 | setValue, 86 | ...others 87 | }: { 88 | label: string; 89 | linkLabel: string; 90 | placeholder?: string; 91 | link: string; 92 | value: string; 93 | setValue: (value: string) => void; 94 | } & BoxProps) { 95 | const [show, setShow] = useState(false); 96 | 97 | return ( 98 | 99 | 100 | {label}: 101 | 108 | {linkLabel} 109 | 110 | 111 | 112 | 113 | setValue(e.target.value)} 118 | /> 119 | 120 | 130 | 131 | 132 | 133 | ); 134 | } 135 | 136 | export function LabeledTextArea({ 137 | label, 138 | value, 139 | setValue, 140 | textAreaId, 141 | ...others 142 | }: { 143 | label: string; 144 | value: string; 145 | textAreaId: string | undefined; 146 | setValue: (value: string) => void; 147 | } & BoxProps) { 148 | return ( 149 | 150 | {label}: 151 |