├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .npmrc ├── .vscode ├── .debug.script.mjs ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── TalOS-Companion ├── .eslintrc.cjs ├── .gitattributes ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── src │ ├── App.tsx │ ├── api │ │ ├── baseapi │ │ │ └── index.tsx │ │ ├── construct-settings-api │ │ │ └── index.ts │ │ ├── constructapi │ │ │ └── index.tsx │ │ ├── dbapi │ │ │ └── index.tsx │ │ ├── discordapi │ │ │ └── index.tsx │ │ ├── extrasapi │ │ │ └── index.tsx │ │ ├── llmapi │ │ │ └── index.tsx │ │ ├── sdapi │ │ │ └── index.tsx │ │ └── vectorapi │ │ │ └── index.tsx │ ├── assets │ │ ├── logo-electron.svg │ │ ├── logo-v1.svg │ │ └── logo-vite.svg │ ├── classes │ │ ├── Attachment.ts │ │ ├── Chat.ts │ │ ├── Command.ts │ │ ├── Completion.ts │ │ ├── CompletionLog.ts │ │ ├── Construct.ts │ │ ├── Instruct.ts │ │ ├── Lorebook.ts │ │ ├── Message.ts │ │ ├── Thought.ts │ │ ├── ThoughtChain.ts │ │ ├── UITheme.ts │ │ └── User.ts │ ├── components │ │ ├── accordion │ │ │ └── index.tsx │ │ ├── back-button │ │ │ └── index.tsx │ │ ├── clock │ │ │ └── index.tsx │ │ ├── confirm-modal │ │ │ └── index.tsx │ │ ├── construct-box │ │ │ ├── ConstructBox.scss │ │ │ ├── construct-quick-chat-config │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── construct-chat-config │ │ │ └── index.tsx │ │ ├── construct-crud │ │ │ ├── ConstructCrud.scss │ │ │ ├── auto-fill-generator │ │ │ │ ├── helpers │ │ │ │ │ └── index.ts │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── sprite-crud │ │ │ │ └── index.tsx │ │ ├── construct-profile │ │ │ └── index.tsx │ │ ├── construct-quick-crud │ │ │ └── index.tsx │ │ ├── construct-toolbar │ │ │ ├── helpers │ │ │ │ └── index.ts │ │ │ └── index.tsx │ │ ├── desktop-notification │ │ │ └── index.tsx │ │ ├── dev-panel │ │ │ └── index.tsx │ │ ├── image-gen │ │ │ ├── image-generation-settings │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── llm-panel │ │ │ ├── connection-box │ │ │ │ └── index.tsx │ │ │ ├── generation-settings │ │ │ │ └── index.tsx │ │ │ ├── horde-panel │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── openai-panel │ │ │ │ └── index.tsx │ │ │ └── palm-panel │ │ │ │ └── index.tsx │ │ ├── loading │ │ │ ├── index.tsx │ │ │ └── loading.scss │ │ ├── menu-theme-loader │ │ │ └── index.tsx │ │ ├── replace-all │ │ │ └── index.tsx │ │ ├── route-button │ │ │ └── index.tsx │ │ ├── shared │ │ │ └── NavBar.tsx │ │ ├── sprite │ │ │ └── index.tsx │ │ ├── streamable-text │ │ │ └── index.tsx │ │ ├── string-array-editor-cards │ │ │ ├── StringArrayEditorCards.scss │ │ │ └── index.tsx │ │ ├── string-array-editor │ │ │ ├── StringArrayEditor.scss │ │ │ └── index.tsx │ │ ├── token-textarea │ │ │ └── index.tsx │ │ └── update │ │ │ ├── Modal │ │ │ ├── index.tsx │ │ │ └── modal.module.scss │ │ │ ├── Progress │ │ │ ├── index.tsx │ │ │ └── progress.module.scss │ │ │ ├── README.md │ │ │ ├── electron-updater.d.ts │ │ │ ├── index.tsx │ │ │ └── update.module.scss │ ├── constants │ │ └── uithemes.ts │ ├── fonts │ │ └── DejaVuSans-Bold.ttf │ ├── index.scss │ ├── main.tsx │ ├── pages │ │ ├── attachments │ │ │ ├── attachment │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── chat │ │ │ ├── chat-config-pane │ │ │ │ └── index.tsx │ │ │ ├── chat-info │ │ │ │ └── index.tsx │ │ │ ├── chat-input │ │ │ │ └── index.tsx │ │ │ ├── chat-log │ │ │ │ ├── index.tsx │ │ │ │ ├── message │ │ │ │ │ └── index.tsx │ │ │ │ └── thinking │ │ │ │ │ └── index.tsx │ │ │ ├── chat-selector │ │ │ │ ├── chat-config-main │ │ │ │ │ └── index.tsx │ │ │ │ ├── chat-details │ │ │ │ │ └── index.tsx │ │ │ │ ├── chat-settings │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── helpers │ │ │ │ └── index.ts │ │ │ └── index.tsx │ │ ├── completions │ │ │ ├── completion-log-details │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── constructs │ │ │ ├── construct-edit-profile │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── discord │ │ │ ├── channel-manager │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── lorebooks │ │ │ ├── index.tsx │ │ │ ├── lorebook-crud │ │ │ │ ├── entry-crud │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ └── lorebook-info │ │ │ │ └── index.tsx │ │ ├── network │ │ │ └── index.tsx │ │ ├── settings │ │ │ ├── background-selector │ │ │ │ └── index.tsx │ │ │ ├── constructs │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── stable-diffuson │ │ │ │ └── index.tsx │ │ ├── users │ │ │ ├── index.tsx │ │ │ ├── user-crud │ │ │ │ └── index.tsx │ │ │ └── user-info │ │ │ │ └── index.tsx │ │ └── zero │ │ │ └── index.tsx │ ├── types │ │ └── index.tsx │ └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── backend ├── config.json ├── main.py └── requirements.txt ├── build ├── icon.icns ├── icon.ico └── icon.png ├── dev-run.bat ├── dev-run.sh ├── dist-electron └── main │ └── index.js ├── electron-builder.json5 ├── electron ├── electron-env.d.ts └── main │ ├── api │ ├── discord.ts │ ├── electrondb.ts │ ├── llm.ts │ ├── pouchdb.ts │ ├── sd.ts │ └── vector.ts │ ├── classes │ └── TalOSEvent.ts │ ├── controllers │ ├── ActiveConstructController.ts │ ├── ActiveConstructHelpers │ │ ├── ActionEvent.ts │ │ ├── chain.ts │ │ ├── chat.ts │ │ ├── memories.ts │ │ ├── skills.ts │ │ └── thoughts.ts │ ├── ChatController.ts │ ├── CompletionController.ts │ ├── DiscordController.ts │ ├── StatisticsController.ts │ └── commands.ts │ ├── helpers │ ├── actions-helpers.ts │ ├── chat-helpers.ts │ ├── discord-helpers.ts │ └── helpers.ts │ ├── index.ts │ ├── model-pipeline │ └── transformers.ts │ ├── server.ts │ ├── types │ ├── prompts.ts │ └── types.ts │ └── update.ts ├── environment.yml ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── public ├── backgrounds │ ├── blackdefault.svg │ ├── bluedefault.svg │ ├── greendefault.svg │ ├── home.png │ ├── homedark.png │ ├── lightdefault.svg │ ├── orangedefault.svg │ ├── purpledefault.svg │ ├── reddefault.svg │ └── yellowdefault.svg ├── chat-page-assets │ ├── code.svg │ ├── file.svg │ └── imageplus.svg ├── defaults │ └── characters │ │ └── Echidna_PLists__AliChat_Markdown_style.png ├── favicon.ico ├── node.svg ├── talos-icon.ico └── wasm │ ├── ort-wasm-simd-threaded.wasm │ ├── ort-wasm-simd.wasm │ ├── ort-wasm-threaded.wasm │ └── ort-wasm.wasm ├── src ├── App.tsx ├── api │ ├── baseapi │ │ └── index.tsx │ ├── construct-settings-api │ │ └── index.ts │ ├── constructapi │ │ └── index.tsx │ ├── dbapi │ │ └── index.tsx │ ├── discordapi │ │ └── index.tsx │ ├── extrasapi │ │ └── index.tsx │ ├── llmapi │ │ └── index.tsx │ ├── sdapi │ │ └── index.tsx │ └── vectorapi │ │ └── index.tsx ├── assets │ ├── logo-electron.svg │ ├── logo-v1.svg │ └── logo-vite.svg ├── classes │ ├── Attachment.ts │ ├── Chat.ts │ ├── Command.ts │ ├── Completion.ts │ ├── CompletionLog.ts │ ├── Construct.ts │ ├── Instruct.ts │ ├── Lorebook.ts │ ├── Message.ts │ ├── Thought.ts │ ├── ThoughtChain.ts │ ├── UITheme.ts │ └── User.ts ├── components │ ├── accordion │ │ └── index.tsx │ ├── back-button │ │ └── index.tsx │ ├── clock │ │ └── index.tsx │ ├── confirm-modal │ │ └── index.tsx │ ├── construct-box │ │ ├── ConstructBox.scss │ │ ├── construct-quick-chat-config │ │ │ └── index.tsx │ │ └── index.tsx │ ├── construct-chat-config │ │ └── index.tsx │ ├── construct-crud │ │ ├── ConstructCrud.scss │ │ ├── auto-fill-generator │ │ │ ├── helpers │ │ │ │ └── index.ts │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── sprite-crud │ │ │ └── index.tsx │ ├── construct-profile │ │ └── index.tsx │ ├── construct-quick-crud │ │ └── index.tsx │ ├── construct-toolbar │ │ ├── helpers │ │ │ └── index.ts │ │ └── index.tsx │ ├── desktop-notification │ │ └── index.tsx │ ├── dev-panel │ │ └── index.tsx │ ├── image-gen │ │ ├── image-generation-settings │ │ │ └── index.tsx │ │ └── index.tsx │ ├── llm-panel │ │ ├── connection-box │ │ │ └── index.tsx │ │ ├── generation-settings │ │ │ └── index.tsx │ │ ├── horde-panel │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── openai-panel │ │ │ └── index.tsx │ │ └── palm-panel │ │ │ └── index.tsx │ ├── loading │ │ ├── index.tsx │ │ └── loading.scss │ ├── menu-theme-loader │ │ └── index.tsx │ ├── replace-all │ │ └── index.tsx │ ├── route-button │ │ └── index.tsx │ ├── shared │ │ └── NavBar.tsx │ ├── sprite │ │ └── index.tsx │ ├── streamable-text │ │ └── index.tsx │ ├── string-array-editor-cards │ │ ├── StringArrayEditorCards.scss │ │ └── index.tsx │ ├── string-array-editor │ │ ├── StringArrayEditor.scss │ │ └── index.tsx │ ├── token-textarea │ │ └── index.tsx │ └── update │ │ ├── Modal │ │ ├── index.tsx │ │ └── modal.module.scss │ │ ├── Progress │ │ ├── index.tsx │ │ └── progress.module.scss │ │ ├── README.md │ │ ├── electron-updater.d.ts │ │ ├── index.tsx │ │ └── update.module.scss ├── constants │ └── uithemes.ts ├── fonts │ └── DejaVuSans-Bold.ttf ├── index.scss ├── main.tsx ├── pages │ ├── attachments │ │ ├── attachment │ │ │ └── index.tsx │ │ └── index.tsx │ ├── chat │ │ ├── chat-config-pane │ │ │ └── index.tsx │ │ ├── chat-info │ │ │ └── index.tsx │ │ ├── chat-input │ │ │ └── index.tsx │ │ ├── chat-log │ │ │ ├── index.tsx │ │ │ ├── message │ │ │ │ └── index.tsx │ │ │ └── thinking │ │ │ │ └── index.tsx │ │ ├── chat-selector │ │ │ ├── chat-config-main │ │ │ │ └── index.tsx │ │ │ ├── chat-details │ │ │ │ └── index.tsx │ │ │ ├── chat-settings │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── helpers │ │ │ └── index.ts │ │ └── index.tsx │ ├── completions │ │ ├── completion-log-details │ │ │ └── index.tsx │ │ └── index.tsx │ ├── constructs │ │ ├── construct-edit-profile │ │ │ └── index.tsx │ │ └── index.tsx │ ├── discord │ │ ├── channel-manager │ │ │ └── index.tsx │ │ └── index.tsx │ ├── lorebooks │ │ ├── index.tsx │ │ ├── lorebook-crud │ │ │ ├── entry-crud │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ └── lorebook-info │ │ │ └── index.tsx │ ├── network │ │ └── index.tsx │ ├── settings │ │ ├── background-selector │ │ │ └── index.tsx │ │ ├── constructs │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── stable-diffuson │ │ │ └── index.tsx │ ├── users │ │ ├── index.tsx │ │ ├── user-crud │ │ │ └── index.tsx │ │ └── user-info │ │ │ └── index.tsx │ └── zero │ │ └── index.tsx ├── types │ └── index.tsx └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | 119 | # Stores VSCode versions used for testing VSCode extensions 120 | .vscode-test 121 | 122 | # yarn v2 123 | .yarn/cache 124 | .yarn/unplugged 125 | .yarn/build-state.yml 126 | .yarn/install-state.gz 127 | .pnp.* 128 | dist-electron/ 129 | backend/venv 130 | release/ 131 | public/models/ 132 | public/backgrounds/ -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /.vscode/.debug.script.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { fileURLToPath } from 'node:url' 4 | import { createRequire } from 'node:module' 5 | import { spawn } from 'node:child_process' 6 | 7 | const pkg = createRequire(import.meta.url)('../package.json') 8 | const __dirname = path.dirname(fileURLToPath(import.meta.url)) 9 | 10 | // write .debug.env 11 | const envContent = Object.entries(pkg.debug.env).map(([key, val]) => `${key}=${val}`) 12 | fs.writeFileSync(path.join(__dirname, '.debug.env'), envContent.join('\n')) 13 | 14 | // bootstrap 15 | spawn( 16 | // TODO: terminate `npm run dev` when Debug exits. 17 | process.platform === 'win32' ? 'npm.cmd' : 'npm', 18 | ['run', 'dev'], 19 | { 20 | stdio: 'inherit', 21 | env: Object.assign(process.env, { VSCODE_DEBUG: 'true' }), 22 | }, 23 | ) -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "mrmlnc.vscode-json5" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "compounds": [ 7 | { 8 | "name": "Debug App", 9 | "preLaunchTask": "Before Debug", 10 | "configurations": [ 11 | "Debug Main Process", 12 | "Debug Renderer Process" 13 | ], 14 | "presentation": { 15 | "hidden": false, 16 | "group": "", 17 | "order": 1 18 | }, 19 | "stopAll": true 20 | } 21 | ], 22 | "configurations": [ 23 | { 24 | "name": "Debug Main Process", 25 | "type": "node", 26 | "request": "launch", 27 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", 28 | "windows": { 29 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" 30 | }, 31 | "runtimeArgs": [ 32 | "--no-sandbox", 33 | "--remote-debugging-port=9229", 34 | "." 35 | ], 36 | "envFile": "${workspaceFolder}/.vscode/.debug.env", 37 | "console": "integratedTerminal" 38 | }, 39 | { 40 | "name": "Debug Renderer Process", 41 | "port": 9229, 42 | "request": "attach", 43 | "type": "chrome", 44 | "timeout": 60000, 45 | "skipFiles": [ 46 | "/**", 47 | "${workspaceRoot}/node_modules/**", 48 | "${workspaceRoot}/dist-electron/**", 49 | // Skip files in host(VITE_DEV_SERVER_URL) 50 | "http://127.0.0.1:7777/**" 51 | ] 52 | }, 53 | ] 54 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.tsc.autoDetect": "off", 4 | "json.schemas": [ 5 | { 6 | "fileMatch": [ 7 | "/*electron-builder.json5", 8 | "/*electron-builder.json" 9 | ], 10 | "url": "https://json.schemastore.org/electron-builder" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Before Debug", 8 | "type": "shell", 9 | "command": "node .vscode/.debug.script.mjs", 10 | "isBackground": true, 11 | "problemMatcher": { 12 | "owner": "typescript", 13 | "fileLocation": "relative", 14 | "pattern": { 15 | // TODO: correct "regexp" 16 | "regexp": "^([a-zA-Z]\\:\/?([\\w\\-]\/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$", 17 | "file": 1, 18 | "line": 3, 19 | "column": 4, 20 | "code": 5, 21 | "message": 6 22 | }, 23 | "background": { 24 | "activeOnStart": true, 25 | "beginsPattern": "^.*VITE v.* ready in \\d* ms.*$", 26 | "endsPattern": "^.*\\[startup\\] Electron App.*$" 27 | } 28 | } 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /TalOS-Companion/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /TalOS-Companion/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /TalOS-Companion/.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 | -------------------------------------------------------------------------------- /TalOS-Companion/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | parserOptions: { 18 | ecmaVersion: 'latest', 19 | sourceType: 'module', 20 | project: ['./tsconfig.json', './tsconfig.node.json'], 21 | tsconfigRootDir: __dirname, 22 | }, 23 | ``` 24 | 25 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 26 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 27 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 28 | -------------------------------------------------------------------------------- /TalOS-Companion/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('tailwindcss'), 4 | require('autoprefixer'), 5 | ], 6 | } 7 | -------------------------------------------------------------------------------- /TalOS-Companion/src/api/baseapi/index.tsx: -------------------------------------------------------------------------------- 1 | import { url } from '../../App'; 2 | import { sendDesktopNotification } from '../../components/desktop-notification'; 3 | import axios from 'axios'; 4 | 5 | export async function getBackgrounds(): Promise { 6 | return axios.get(`${url}/api/get-backgrounds`) 7 | .then(response => response.data.files) 8 | .catch(error => { 9 | throw error.response.data.error; 10 | }); 11 | } 12 | 13 | export async function deleteBackground(filename: string): Promise<{ success: boolean }> { 14 | return axios.delete(`${url}/api/delete-background/${filename}`) 15 | .then(response => response.data) 16 | .catch(error => { 17 | throw error.response.data.error; 18 | }); 19 | } 20 | 21 | export function saveBackground(file: File): Promise { 22 | const name = file.name; 23 | const fileType = name.split('.').pop(); 24 | const reader = new FileReader(); 25 | 26 | return new Promise((resolve, reject) => { 27 | reader.onload = () => { 28 | const base64String = reader.result as string; 29 | const imageData = base64String.split(',')[1]; 30 | 31 | axios.post(`${url}/api/save-background`, { 32 | imageData: imageData, 33 | name: name, 34 | fileType: fileType 35 | }) 36 | .then(response => { 37 | resolve(response.data.fileName); 38 | }) 39 | .catch(error => { 40 | reject(error.response.data.error); 41 | }); 42 | }; 43 | 44 | reader.onerror = () => { 45 | reject(new Error('Failed to read file')); 46 | }; 47 | 48 | reader.readAsDataURL(file); 49 | }); 50 | } 51 | 52 | export function uploadImage(formData: FormData): void { 53 | axios.post(`${url}/api/images/upload`, formData, { 54 | headers: { 55 | 'Content-Type': 'multipart/form-data' 56 | } 57 | }) 58 | .then(response => { 59 | console.log('Upload successful:', response.data); 60 | }) 61 | .catch(error => { 62 | console.error('Error uploading:', error); 63 | }); 64 | } 65 | 66 | export async function loadModels(){ 67 | return axios.post(`${url}/api/models/load`).then((response) => { 68 | sendDesktopNotification('ConstructOS', 'Models Loaded'); 69 | }).catch((err) => { 70 | console.error(err); 71 | }); 72 | } 73 | 74 | export function getImageURL(filename: string): string { 75 | // Check if it is Base64 76 | if (filename.startsWith('data:image')) { 77 | return filename; 78 | } 79 | if(filename.startsWith('http')) return filename; 80 | return `${url}${filename}`; 81 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/api/construct-settings-api/index.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | // Random Messages 4 | export const setRandomMessages = async (value: boolean) => { 5 | return axios.post('/api/constructs/set/randommessages', { value }).then(res => res.data); 6 | } 7 | export const getRandomMessages = async () => { 8 | return axios.get('/api/constructs/randommessages').then(res => res.data); 9 | } 10 | 11 | // Random Thoughts 12 | export const setRandomThoughts = async (value: boolean) => { 13 | return axios.post('/api/constructs/set/randomthoughts', { value }).then(res => res.data); 14 | } 15 | export const getRandomThoughts = async () => { 16 | return axios.get('/api/constructs/randomthoughts').then(res => res.data); 17 | } 18 | 19 | // Random Actions 20 | export const setRandomActions = async (value: boolean) => { 21 | return axios.post('/api/constructs/set/randomactions', { value }).then(res => res.data); 22 | } 23 | export const getRandomActions = async () => { 24 | return axios.get('/api/constructs/randomactions').then(res => res.data); 25 | } 26 | 27 | // Thought Interval 28 | export const setThoughtInterval = async (value: number) => { 29 | return axios.post('/api/constructs/set/thoughtinterval', { value }).then(res => res.data); 30 | } 31 | export const getThoughtInterval = async () => { 32 | return axios.get('/api/constructs/thoughtinterval').then(res => res.data); 33 | } 34 | 35 | // Action Interval 36 | export const setActionInterval = async (value: number) => { 37 | return axios.post('/api/constructs/set/actioninterval', { value }).then(res => res.data); 38 | } 39 | export const getActionInterval = async () => { 40 | return axios.get('/api/constructs/actioninterval').then(res => res.data); 41 | } 42 | 43 | // Message Interval 44 | export const setMessageInterval = async (value: number) => { 45 | return axios.post('/api/constructs/set/messageinterval', { value }).then(res => res.data); 46 | } 47 | export const getMessageInterval = async () => { 48 | return axios.get('/api/constructs/messageinterval').then(res => res.data); 49 | } 50 | 51 | // Show Discord User Info 52 | export const setShowDiscordUserInfo = async (value: boolean) => { 53 | return axios.post('/api/constructs/set/showdiscorduserinfo', { value }).then(res => res.data); 54 | } 55 | export const getShowDiscordUserInfo = async () => { 56 | return axios.get('/api/constructs/showdiscorduserinfo').then(res => res.data); 57 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/api/vectorapi/index.tsx: -------------------------------------------------------------------------------- 1 | import { url } from "../../App"; 2 | import { Message } from "../../classes/Message"; 3 | import axios from "axios"; 4 | 5 | export async function getVector(text: string): Promise { 6 | try { 7 | const response = await axios.get(`${url}/api/vector`, { params: { text } }); 8 | return response.data; 9 | } catch (error: any) { 10 | throw new Error(error.response?.data?.error || 'Failed to get vector.'); 11 | } 12 | } 13 | 14 | export async function getAllVectors(schemaName: string) { 15 | try { 16 | const response = await axios.get(`${url}/api/vectors/${schemaName}`); 17 | return response.data; 18 | } catch (error: any) { 19 | throw new Error(error.response?.data?.error || 'Failed to get all vectors.'); 20 | } 21 | } 22 | 23 | export async function getRelaventMemories(schemaName: string, text: string) { 24 | try { 25 | const response = await axios.get(`${url}/api/memories/${schemaName}`, { params: { text } }); 26 | return response.data; 27 | } catch (error: any) { 28 | throw new Error(error.response?.data?.error || 'Failed to get relevant memories.'); 29 | } 30 | } 31 | 32 | export async function addVectorFromMessage(schemaName: string, message: Message) { 33 | try { 34 | const response = await axios.post(`${url}/api/vector/${schemaName}`, message); 35 | return response.data; 36 | } catch (error: any) { 37 | throw new Error(error.response?.data?.error || 'Failed to add vector from message.'); 38 | } 39 | } 40 | 41 | export async function removeAllMemories(schemaName: string) { 42 | try { 43 | const response = await axios.delete(`${url}/api/index/${schemaName}`); 44 | return response.data; 45 | } catch (error: any) { 46 | throw new Error(error.response?.data?.error || 'Failed to remove all memories.'); 47 | } 48 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/assets/logo-vite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /TalOS-Companion/src/classes/Attachment.ts: -------------------------------------------------------------------------------- 1 | export class Attachment{ 2 | constructor( 3 | public _id: string = (new Date().getTime()).toString(), 4 | public name: string = '', 5 | public type: string = '', 6 | public fileext: string = '', 7 | public data: string = '', 8 | public metadata: any = {} 9 | ) {} 10 | 11 | setAttachment(name: string, type: string, fileext: string, data: string, metadata: any){ 12 | this.name = name; 13 | this.type = type; 14 | this.fileext = fileext; 15 | this.data = data; 16 | this.metadata = metadata; 17 | } 18 | 19 | getAttachment(){ 20 | return { 21 | name: this.name, 22 | type: this.type, 23 | fileext: this.fileext, 24 | data: this.data, 25 | metadata: this.metadata 26 | } 27 | } 28 | 29 | getAttachmentName(){ 30 | return this.name; 31 | } 32 | 33 | getAttachmentType(){ 34 | return this.type; 35 | } 36 | 37 | getAttachmentData(){ 38 | return this.data; 39 | } 40 | 41 | setAttachmentName(name: string){ 42 | this.name = name; 43 | } 44 | 45 | setAttachmentType(type: string){ 46 | this.type = type; 47 | } 48 | 49 | setAttachmentData(data: string){ 50 | this.data = data; 51 | } 52 | 53 | getAttachmentDataAsBase64(){ 54 | return this.data; 55 | } 56 | 57 | getAttachmentDataAsBuffer(){ 58 | return Buffer.from(this.data, 'base64'); 59 | } 60 | 61 | getAttachmentDataAsBlob(){ 62 | return new Blob([this.data], {type: this.type}); 63 | } 64 | 65 | getAttachmentDataAsFile(){ 66 | return new File([this.data], this.name, {type: this.type}); 67 | } 68 | 69 | getAttachmentMetadata(){ 70 | return this.metadata; 71 | } 72 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/classes/Command.ts: -------------------------------------------------------------------------------- 1 | export class Command{ 2 | constructor( 3 | public _id: string = (new Date().getTime()).toString(), 4 | public name: string = '', 5 | ) {} 6 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/classes/Completion.ts: -------------------------------------------------------------------------------- 1 | import { CompletionType } from "../types"; 2 | 3 | export type AuthorType = 'AI' | 'User'; 4 | 5 | // Completion.ts 6 | export class Completion { 7 | constructor( 8 | public _id: string = (new Date().getTime()).toString(), 9 | public type: CompletionType = 'novel', 10 | public content: string = '', 11 | public author: AuthorType = 'User', 12 | public dateCreated: number = new Date().getTime(), 13 | public lastEdited: number = new Date().getTime(), 14 | ) {} 15 | 16 | setCompletion(type: CompletionType, content: string, author: AuthorType, dateCreated: number, lastEdited: number){ 17 | this.type = type; 18 | this.content = content; 19 | this.author = author; 20 | this.dateCreated = dateCreated; 21 | this.lastEdited = lastEdited; 22 | } 23 | 24 | getCompletion(){ 25 | return { 26 | type: this.type, 27 | content: this.content, 28 | author: this.author, 29 | dateCreated: this.dateCreated, 30 | lastEdited: this.lastEdited, 31 | } 32 | } 33 | 34 | getCompletionType(){ 35 | return this.type; 36 | } 37 | 38 | getCompletionContent(){ 39 | return this.content; 40 | } 41 | 42 | getCompletionAuthor(){ 43 | return this.author; 44 | } 45 | 46 | getCompletionDateCreated(){ 47 | return this.dateCreated; 48 | } 49 | 50 | getCompletionLastEdited(){ 51 | return this.lastEdited; 52 | } 53 | 54 | setCompletionType(type: CompletionType){ 55 | this.type = type; 56 | } 57 | 58 | setCompletionContent(content: string){ 59 | this.content = content; 60 | } 61 | 62 | setCompletionAuthor(author: AuthorType){ 63 | this.author = author; 64 | } 65 | 66 | setCompletionDateCreated(dateCreated: number){ 67 | this.dateCreated = dateCreated; 68 | } 69 | 70 | setCompletionLastEdited(lastEdited: number){ 71 | this.lastEdited = lastEdited; 72 | } 73 | 74 | updateCompletion(content: string){ 75 | this.content = content; 76 | this.lastEdited = new Date().getTime(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /TalOS-Companion/src/classes/CompletionLog.ts: -------------------------------------------------------------------------------- 1 | import { CompletionType } from "../types"; 2 | import { Completion } from "./Completion"; 3 | 4 | export class CompletionLog{ 5 | constructor( 6 | public _id: string = (new Date().getTime()).toString(), 7 | public name: string = 'New Completion Log', 8 | public type: CompletionType = 'novel', 9 | public completions: Completion[] = [], 10 | public lastCompletion: Completion = new Completion(), 11 | public lastCompletionDate: number = new Date().getTime(), 12 | ) {} 13 | 14 | setCompletionLog(name: string, type: CompletionType, completions: Completion[]){ 15 | this.name = name; 16 | this.type = type; 17 | this.completions = completions; 18 | } 19 | 20 | getCompletionLog(){ 21 | return { 22 | name: this.name, 23 | type: this.type, 24 | completions: this.completions, 25 | } 26 | } 27 | 28 | getCompletionLogName(){ 29 | return this.name; 30 | } 31 | 32 | getCompletionLogType(){ 33 | return this.type; 34 | } 35 | 36 | getCompletionLogCompletions(){ 37 | return this.completions; 38 | } 39 | 40 | setCompletionLogName(name: string){ 41 | this.name = name; 42 | } 43 | 44 | setCompletionLogType(type: CompletionType){ 45 | this.type = type; 46 | } 47 | 48 | setCompletionLogCompletions(completions: Completion[]){ 49 | this.completions = completions; 50 | this.lastCompletion = completions[completions.length - 1]; 51 | this.lastCompletionDate = new Date().getTime(); 52 | } 53 | 54 | addCompletion(completion: Completion){ 55 | this.completions.push(completion); 56 | this.lastCompletion = completion; 57 | this.lastCompletionDate = new Date().getTime(); 58 | } 59 | 60 | getLastCompletion(){ 61 | return this.lastCompletion; 62 | } 63 | 64 | getLastCompletionDate(){ 65 | return this.lastCompletionDate; 66 | } 67 | 68 | setLastCompletion(completion: Completion){ 69 | this.lastCompletion = completion; 70 | } 71 | 72 | setLastCompletionDate(date: number){ 73 | this.lastCompletionDate = date; 74 | } 75 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/classes/Instruct.ts: -------------------------------------------------------------------------------- 1 | export class Instruct{ 2 | constructor( 3 | public _id: string = (new Date().getTime()).toString(), 4 | public name: string = '', 5 | public randomEvents: string[] = [], 6 | ) {} 7 | 8 | setName(name: string) { 9 | this.name = name; 10 | } 11 | 12 | getName(){ 13 | return this.name; 14 | } 15 | 16 | setRandomEvents(randomEvents: string[]) { 17 | this.randomEvents = randomEvents; 18 | } 19 | 20 | getRandomEvents(){ 21 | return this.randomEvents; 22 | } 23 | 24 | addRandomEvent(randomEvent: string){ 25 | this.randomEvents.push(randomEvent); 26 | } 27 | 28 | removeRandomEvent(randomEvent: string){ 29 | this.randomEvents.splice(this.randomEvents.indexOf(randomEvent), 1); 30 | } 31 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/classes/Thought.ts: -------------------------------------------------------------------------------- 1 | import { Emotion } from "../types"; 2 | import { Attachment } from "./Attachment"; 3 | 4 | export class Thought{ 5 | constructor( 6 | public _id: string = (new Date().getTime()).toString(), 7 | public timestamp: number = new Date().getTime(), 8 | public userID: string = '', 9 | public avatar: string = '', 10 | public user: string = '', 11 | public text: string = '', 12 | public task: string = '', 13 | public isUserInput: boolean = false, 14 | public attachments: Attachment[] = [], 15 | public emotion: Emotion = 'neutral', 16 | public actions: any[] = [], 17 | ) {} 18 | 19 | public static fromJSON(json: any): Thought { 20 | return new Thought( 21 | json._id, 22 | json.user, 23 | json.avatar, 24 | json.text, 25 | json.userID, 26 | json.timestamp, 27 | json.attachments, 28 | json.emotion, 29 | json.actions, 30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/classes/ThoughtChain.ts: -------------------------------------------------------------------------------- 1 | import { Thought } from "./Thought"; 2 | 3 | export class ThoughtChain{ 4 | constructor( 5 | public _id: string = (new Date().getTime()).toString(), 6 | public agents: string[] = [], 7 | public thoughts: Thought[] = [], 8 | public task: string = '', 9 | public timestamp: number = new Date().getTime(), 10 | ) {} 11 | 12 | public static fromJSON(json: any): ThoughtChain { 13 | return new ThoughtChain( 14 | json._id, 15 | json.agents, 16 | json.thoughts, 17 | json.task, 18 | json.timestamp, 19 | ); 20 | } 21 | 22 | getThoughts(){ 23 | return this.thoughts; 24 | } 25 | 26 | getThoughtsByAgent(agent: string){ 27 | return this.thoughts.filter((thought) => thought.userID === agent); 28 | } 29 | 30 | getThoughtsByTask(task: string){ 31 | return this.thoughts.filter((thought) => thought.task === task); 32 | } 33 | 34 | getThoughtsByAgentAndTask(agent: string, task: string){ 35 | return this.thoughts.filter((thought) => thought.userID === agent && thought.task === task); 36 | } 37 | 38 | getThoughtsByAgentAndTaskAndTimestamp(agent: string, task: string, timestamp: number){ 39 | return this.thoughts.filter((thought) => thought.userID === agent && thought.task === task && thought.timestamp === timestamp); 40 | } 41 | 42 | getThoughtsByAgentAndTimestamp(agent: string, timestamp: number){ 43 | return this.thoughts.filter((thought) => thought.userID === agent && thought.timestamp === timestamp); 44 | } 45 | 46 | getThoughtsByTaskAndTimestamp(task: string, timestamp: number){ 47 | return this.thoughts.filter((thought) => thought.task === task && thought.timestamp === timestamp); 48 | } 49 | 50 | getThoughtsByTimestamp(timestamp: number){ 51 | return this.thoughts.filter((thought) => thought.timestamp === timestamp); 52 | } 53 | 54 | addThought(thought: Thought){ 55 | this.thoughts.push(thought); 56 | } 57 | 58 | removeThought(thought: Thought){ 59 | this.thoughts = this.thoughts.filter((t) => t._id !== thought._id); 60 | } 61 | 62 | getAgents(){ 63 | return this.agents; 64 | } 65 | 66 | addAgent(agent: string){ 67 | this.agents.push(agent); 68 | } 69 | 70 | removeAgent(agent: string){ 71 | this.agents = this.agents.filter((a) => a !== agent); 72 | } 73 | 74 | getTask(){ 75 | return this.task; 76 | } 77 | 78 | setTask(task: string){ 79 | this.task = task; 80 | } 81 | 82 | getTimestamp(){ 83 | return this.timestamp; 84 | } 85 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/components/accordion/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { AiOutlineDown, AiOutlineUp } from "react-icons/ai"; 3 | interface AccordionProps { 4 | title: string; 5 | children: React.ReactNode; 6 | className?: string; 7 | } 8 | const Accordion = (props: AccordionProps) => { 9 | const { title, children, className } = props; 10 | const [isExpanded, setIsExpanded] = useState(false); 11 | 12 | useEffect(() => { 13 | if(localStorage.getItem(title)){ 14 | let state = JSON.parse(localStorage.getItem(title)?.toString() || '{}'); 15 | if(state.isExpanded === true){ 16 | setIsExpanded(true); 17 | } 18 | } 19 | }, [title]); 20 | 21 | useEffect(() => { 22 | localStorage.setItem(title, JSON.stringify({isExpanded: isExpanded})); 23 | }, [isExpanded, title]); 24 | 25 | return ( 26 |
27 |
setIsExpanded(!isExpanded)}> 28 |
29 |
30 | {title} 31 |
32 |
33 | 36 |
37 |
38 |
39 |
40 | {children} 41 |
42 |
43 | ) 44 | 45 | } 46 | 47 | export default Accordion; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/back-button/index.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom" 2 | import { BiArrowBack } from "react-icons/bi"; 3 | interface Props { 4 | to?: string; 5 | } 6 | const BackButton = (props: Props) => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default BackButton; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/clock/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | const Clock = () => { 3 | const [time, setTime] = useState({ 4 | minutes: new Date().getMinutes(), 5 | hours: new Date().getHours(), 6 | seconds: new Date().getSeconds() 7 | }) 8 | 9 | useEffect(() => { 10 | const intervalId = setInterval(() => { 11 | const date = new Date(); 12 | setTime({ 13 | minutes: date.getMinutes(), 14 | hours: date.getHours(), 15 | seconds: date.getSeconds() 16 | }) 17 | }, 1000) 18 | 19 | return () => clearInterval(intervalId); 20 | }, []) 21 | 22 | const convertToTwoDigit = (number: number) => { 23 | return number.toLocaleString('en-US', { 24 | minimumIntegerDigits: 2 25 | }) 26 | } 27 | 28 | return ( 29 |
30 | {convertToTwoDigit(time.hours)}: 31 | {convertToTwoDigit(time.minutes)}: 32 | {convertToTwoDigit(time.seconds)} 33 | {time.hours >= 12 ? ' PM' : ' AM'} 34 |
35 | ); 36 | } 37 | export default Clock; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/confirm-modal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | interface ConfirmModalProps { 5 | message: string; 6 | note?: string | null; 7 | } 8 | 9 | const ConfirmModal = ({ message, note }: ConfirmModalProps) => { 10 | const [isVisible, setIsVisible] = useState(true); 11 | 12 | return ( 13 | isVisible && ( 14 |
15 |
16 |

{message}

17 | {note &&

{note}

} 18 |
19 | 28 | 37 |
38 |
39 |
40 | ) 41 | ); 42 | }; 43 | 44 | let confirmModalPromiseResolver: (value: boolean) => void; 45 | 46 | export function confirmModal(message: string, note?: string): Promise { 47 | return new Promise((resolve) => { 48 | confirmModalPromiseResolver = resolve; 49 | const div = document.createElement('div'); 50 | document.body.appendChild(div); 51 | ReactDOM.render(, div); 52 | }); 53 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/components/construct-box/ConstructBox.scss: -------------------------------------------------------------------------------- 1 | .agent-image-default { 2 | width: 20rem; 3 | height: 25rem; 4 | word-wrap: break-word; 5 | cursor: pointer; 6 | border-radius: 10%; 7 | overflow: hidden; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | color: var(--theme-text); 12 | background-color: var(--theme-accent); 13 | box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); 14 | object-fit: cover; 15 | @apply rounded-theme-border-radius object-cover bg-theme-box border-theme-border-width border-theme-border; 16 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/components/construct-crud/ConstructCrud.scss: -------------------------------------------------------------------------------- 1 | .construct-image { 2 | width: 20rem; 3 | height: 25rem; 4 | word-wrap: break-word; 5 | cursor: pointer; 6 | aspect-ratio: 1/1.5; 7 | border-radius: 10%; 8 | overflow: hidden; 9 | object-fit: cover; 10 | box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); 11 | @apply rounded-theme-border-radius; 12 | } 13 | 14 | .construct-image-default { 15 | width: 20rem; 16 | height: 25rem; 17 | word-wrap: break-word; 18 | cursor: pointer; 19 | border-radius: 10%; 20 | overflow: hidden; 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | color: var(--theme-text); 25 | background-color: var(--theme-accent); 26 | box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); 27 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/components/construct-crud/sprite-crud/index.tsx: -------------------------------------------------------------------------------- 1 | import { getImageURL, uploadImage } from "../../../api/baseapi"; 2 | import { Sprite } from "../../../classes/Construct" 3 | import { Emotion } from "../../../types"; 4 | import { useEffect, useState } from "react"; 5 | import { RiQuestionMark } from "react-icons/ri"; 6 | 7 | interface SpriteCrudProps { 8 | sprite: Sprite | undefined, 9 | addSprite: (sprite: Sprite, emotion: any) => void, 10 | emotion: any, 11 | } 12 | const SpriteCrud = (props: SpriteCrudProps) => { 13 | const { sprite, addSprite, emotion } = props; 14 | const [spriteImage, setSpriteImage] = useState(null); 15 | const [spriteEmotion, setSpriteEmotion] = useState(null); 16 | 17 | useEffect(() => { 18 | if (sprite) { 19 | setSpriteImage(sprite.image64); 20 | setSpriteEmotion(sprite.emotion); 21 | } 22 | }, [sprite]); 23 | 24 | const uploadSprite = async (event: React.ChangeEvent, emotion: Emotion) => { 25 | const file = event.target.files?.[0]; 26 | const newSprite = new Sprite(emotion); 27 | if (file) { 28 | const newName = Date.now().toString()+`-${emotion}` + '.' + file.name.split('.').pop(); 29 | const formData = new FormData(); 30 | formData.append('image', file, newName); 31 | uploadImage(formData); 32 | setSpriteImage(`./api/images/${newName}`); 33 | newSprite.image64 = `./api/images/${newName}`; 34 | addSprite(newSprite, emotion); 35 | } else { 36 | // Handle the case where no file was selected, if necessary 37 | } 38 | } 39 | 40 | return ( 41 | <> 42 |
43 | Sprite for {emotion.label} 44 | 57 |
58 | 59 | ); 60 | } 61 | export default SpriteCrud -------------------------------------------------------------------------------- /TalOS-Companion/src/components/construct-profile/index.tsx: -------------------------------------------------------------------------------- 1 | import { getImageURL } from "../../api/baseapi"; 2 | import { Construct } from "../../classes/Construct"; 3 | import { PlusIcon } from "lucide-react"; 4 | import React, { useState, useEffect } from "react"; 5 | 6 | interface Props { 7 | character: Construct; 8 | onClick?: (construct: Construct) => void; 9 | active?: boolean; 10 | } 11 | 12 | const ConstructProfile: React.FC = ({ character, onClick, active }) => { 13 | const [characterImage, setCharacterImage] = useState(character.avatar); 14 | const [characterName, setCharacterName] = useState(character.name); 15 | const [isHovered, setIsHovered] = useState(false); // State for hover 16 | 17 | useEffect(() => { 18 | setCharacterImage(character.avatar); 19 | setCharacterName(character.name); 20 | }, [character]); 21 | 22 | return ( 23 |
setIsHovered(true)} 26 | onMouseLeave={() => setIsHovered(false)} 27 | onClick={() => { if (onClick !== undefined) onClick(character) }} 28 | > 29 | {characterName} 30 |

31 | {characterName} 32 |

33 | 34 | {isHovered && ( 35 |
36 | 37 | New Chat 38 |
39 | 40 |
41 |
42 | )} 43 | 44 | {active && !isHovered && ( 45 |
46 |
47 |
48 | )} 49 |
50 | ); 51 | 52 | } 53 | 54 | export default ConstructProfile; 55 | -------------------------------------------------------------------------------- /TalOS-Companion/src/components/construct-toolbar/helpers/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/talos-bots/TalOS/9bf4f1b621c0c923b51282ac4e586e298e3c3d68/TalOS-Companion/src/components/construct-toolbar/helpers/index.ts -------------------------------------------------------------------------------- /TalOS-Companion/src/components/construct-toolbar/index.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/talos-bots/TalOS/9bf4f1b621c0c923b51282ac4e586e298e3c3d68/TalOS-Companion/src/components/construct-toolbar/index.tsx -------------------------------------------------------------------------------- /TalOS-Companion/src/components/desktop-notification/index.tsx: -------------------------------------------------------------------------------- 1 | export function sendDesktopNotification(title: string, body: string, onClickCallback?: () => void) { 2 | // Check if Notification API is supported 3 | if (!("Notification" in window)) { 4 | console.warn("This browser does not support desktop notification."); 5 | return; 6 | } 7 | 8 | // Function to actually create the notification 9 | function createNotification() { 10 | let notification = new Notification(title, { body }); 11 | if (typeof onClickCallback === 'function') { 12 | notification.onclick = onClickCallback; 13 | } 14 | } 15 | 16 | // Check for permission 17 | if (Notification.permission === "granted") { 18 | // If it's okay, let's create a notification 19 | createNotification(); 20 | } 21 | // Otherwise, we need to ask the user for permission 22 | else if (Notification.permission !== "denied") { 23 | Notification.requestPermission().then(permission => { 24 | // If the user accepts, let's create a notification 25 | if (permission === "granted") { 26 | createNotification(); 27 | } 28 | }); 29 | } 30 | } -------------------------------------------------------------------------------- /TalOS-Companion/src/components/dev-panel/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import RouteButton from "../route-button"; 3 | import { defaultThemes } from "../../constants/uithemes"; 4 | import { setStorageValue } from "../../api/dbapi"; 5 | 6 | const DevPanel = () => { 7 | const [isMinimized, setMinimized] = useState(true); 8 | 9 | const setTheme = async (themeID: string) => { 10 | await setStorageValue("uiTheme", themeID); 11 | window.location.reload(); 12 | }; 13 | return isMinimized ? ( 14 | 20 | ) : ( 21 |
22 |
23 |

Dev Panel

24 | 27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 |
45 |
46 | 47 | 48 |
49 |
50 |
51 |
52 |
53 |

Themes

54 |
55 | {Array.isArray(defaultThemes) && defaultThemes.map((theme, index) => { 56 | return ( 57 | 60 | ) 61 | })} 62 |
63 |
64 |
65 | ); 66 | } 67 | 68 | export default DevPanel; 69 | -------------------------------------------------------------------------------- /TalOS-Companion/src/components/image-gen/image-generation-settings/index.tsx: -------------------------------------------------------------------------------- 1 | const ImageGenSettings = () => { 2 | return ( 3 | <> 4 | 5 | ) 6 | }; 7 | export default ImageGenSettings; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/image-gen/index.tsx: -------------------------------------------------------------------------------- 1 | const ImageGen = () => { 2 | return ( 3 | <> 4 | 5 | ) 6 | } 7 | export default ImageGen; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/llm-panel/horde-panel/index.tsx: -------------------------------------------------------------------------------- 1 | import { setLLMModel } from "../../../api/llmapi"; 2 | import { useState, useEffect } from "react"; 3 | interface HordePanelProps { 4 | selectedModel: string; 5 | setSelectedModel: (model: string) => void; 6 | } 7 | const HordePanel = (props: HordePanelProps) => { 8 | const { selectedModel, setSelectedModel } = props; 9 | const [models, setModels] = useState([]); 10 | 11 | useEffect(() => { 12 | fetchModels(); 13 | }, []); 14 | 15 | const fetchModels = async () => { 16 | try { 17 | const response = await fetch('https://aihorde.net/api/v2/status/models?type=text'); 18 | const data = await response.json(); 19 | const formattedData = data.map((model: any) => ({ 20 | value: model.name, 21 | label: model.name, 22 | })); 23 | setModels(formattedData); 24 | if (formattedData.length > 0) { 25 | setSelectedModel(formattedData[0].value); 26 | } 27 | } catch (error) { 28 | console.error('Error fetching models:', error); 29 | } 30 | }; 31 | 32 | useEffect(() => { 33 | if (selectedModel === '') return; 34 | if (selectedModel === undefined) return; 35 | if (selectedModel === null) return; 36 | console.log('Setting LLM model:', selectedModel); 37 | setLLMModel(selectedModel); 38 | }, [selectedModel]); 39 | 40 | return ( 41 |
42 | 43 | 50 |
51 | ); 52 | }; 53 | export default HordePanel; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/llm-panel/index.tsx: -------------------------------------------------------------------------------- 1 | import ConnectionBox from "./connection-box"; 2 | import GenerationSettings from "./generation-settings"; 3 | 4 | interface LLMPanelProps { 5 | }; 6 | const LLMPanel = (props: LLMPanelProps) => { 7 | return( 8 |
9 |
10 |

Connection Details

11 | 12 |
13 |
14 |

Generation Settings

15 | 16 |
17 |
18 | ); 19 | }; 20 | 21 | export default LLMPanel; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/llm-panel/openai-panel/index.tsx: -------------------------------------------------------------------------------- 1 | import { OAI_Model, getLLMOAIModel, setLLMOAIModel } from "../../../api/llmapi"; 2 | import { useEffect, useState } from "react"; 3 | interface OpenAIPanelProps { 4 | selectedModel: string; 5 | setSelectedModel: (model: OAI_Model) => void; 6 | } 7 | const OpenAIPanel = (props: OpenAIPanelProps) => { 8 | const { selectedModel, setSelectedModel } = props; 9 | const models = ['gpt-3.5-turbo-16k', 'gpt-4', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k-0613', 'gpt-3.5-turbo-0613', 'gpt-3.5-turbo-0301', 'gpt-4-0314', 'gpt-4-0613'] 10 | 11 | useEffect(() => { 12 | getLLMOAIModel().then((model) => { 13 | setSelectedModel(model as OAI_Model); 14 | }); 15 | }, []); 16 | 17 | useEffect(() => { 18 | if (selectedModel === '') return; 19 | if (selectedModel === undefined) return; 20 | if (selectedModel === null) return; 21 | console.log('Setting LLM OpenAI model:', selectedModel); 22 | setLLMOAIModel(selectedModel); 23 | }, [selectedModel]); 24 | 25 | return ( 26 |
27 | 28 | 35 |
36 | ); 37 | }; 38 | export default OpenAIPanel; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/loading/index.tsx: -------------------------------------------------------------------------------- 1 | import './loading.scss' 2 | const Loading = () => { 3 | return ( 4 |
5 |
6 |
7 |
8 |
9 | L 10 | o 11 | a 12 | d 13 | i 14 | n 15 | g 16 | . 17 | . 18 |
19 |
20 |
21 | ); 22 | } 23 | 24 | export default Loading; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/loading/loading.scss: -------------------------------------------------------------------------------- 1 | #loading { 2 | position: fixed; 3 | width: 100%; 4 | height: 100%; 5 | top: 0; 6 | left: 0; 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | font-size: 2em; 12 | z-index: 9999; 13 | } 14 | 15 | .spinner { 16 | border: 2px solid #f3f3f3; 17 | border-radius: 50%; 18 | border-top: 2px solid var(--theme-flavor-text); 19 | width: 60px; 20 | height: 60px; 21 | animation: spin 1s linear infinite; 22 | } 23 | 24 | .loading-gif { 25 | width: 160px; 26 | } 27 | 28 | .desktop-box { 29 | border-radius: 10px; 30 | background-color: rgba(0, 0, 0, 0.75); 31 | width: 25vw; 32 | height: 20vw; 33 | border: 2px solid #000000; 34 | display: flex; 35 | justify-content: center; 36 | align-items: center; 37 | flex-direction: column; 38 | } 39 | 40 | .is-loading-letter { 41 | animation: bounce 1s infinite; 42 | display: inline-block; 43 | } 44 | 45 | .is-loading-letter:nth-child(1) { animation-delay: 0.1s; } 46 | .is-loading-letter:nth-child(2) { animation-delay: 0.2s; } 47 | .is-loading-letter:nth-child(3) { animation-delay: 0.3s; } 48 | .is-loading-letter:nth-child(4) { animation-delay: 0.4s; } 49 | .is-loading-letter:nth-child(5) { animation-delay: 0.5s; } 50 | .is-loading-letter:nth-child(6) { animation-delay: 0.6s; } 51 | .is-loading-letter:nth-child(7) { animation-delay: 0.7s; } 52 | .is-loading-letter:nth-child(8) { animation-delay: 0.8s; } 53 | .is-loading-letter:nth-child(9) { animation-delay: 0.9s; } 54 | .is-loading-letter:nth-child(10) { animation-delay: 0.10s; } 55 | .is-loading-letter:nth-child(11) { animation-delay: 0.11s; } 56 | 57 | @keyframes bounce { 58 | 0%, 100% { 59 | transform: translateY(0); 60 | } 61 | 50% { 62 | transform: translateY(-10px); 63 | } 64 | } 65 | 66 | /* Spinner animation */ 67 | @keyframes spin { 68 | 0% { transform: rotate(0deg); } 69 | 100% { transform: rotate(360deg); } 70 | } 71 | 72 | /* Add CSS for animation here */ 73 | @keyframes flyIn { 74 | from {transform: translateX(-100%);} 75 | to {transform: translateX(0);} 76 | } 77 | 78 | .fly-in-text { 79 | animation: flyIn 2s; 80 | } 81 | 82 | @keyframes flyOut { 83 | from { 84 | transform: translateX(0); 85 | opacity: 1; 86 | } 87 | to { 88 | transform: translateX(100%); 89 | opacity: 0; 90 | } 91 | } 92 | 93 | .fly-out-text { 94 | animation: flyOut 2s; 95 | } 96 | -------------------------------------------------------------------------------- /TalOS-Companion/src/components/replace-all/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | interface ReplaceAllProps { 4 | text: string; 5 | setText: (text: string) => void; 6 | } 7 | const ReplaceAll = (props: ReplaceAllProps) => { 8 | const { text, setText } = props; 9 | const [caseSensitive, setCaseSensitive] = useState(false); 10 | const [search, setSearch] = useState(""); 11 | const [replace, setReplace] = useState(""); 12 | 13 | const replaceAll = () => { 14 | const regex = new RegExp(search, caseSensitive ? "g" : "gi"); 15 | setText(text.replace(regex, replace)); 16 | }; 17 | 18 | return ( 19 |
20 | setSearch(e.target.value)} 25 | /> 26 | setReplace(e.target.value)} 31 | /> 32 | 33 |
34 | setCaseSensitive(e.target.checked)} 39 | /> 40 |
41 | 42 |
43 | ); 44 | } 45 | export default ReplaceAll; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/route-button/index.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | interface RouteButtonProps { 4 | to: string; 5 | text: string; 6 | icon?: any; 7 | onClick?: () => void; 8 | className?: string; 9 | id?: string; 10 | children?: React.ReactNode; 11 | } 12 | const RouteButton = (props: RouteButtonProps) => { 13 | return ( 14 | 15 | {props.icon} 16 | {props.text} 17 | {props.children} 18 | 19 | ); 20 | } 21 | 22 | export default RouteButton; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/sprite/index.tsx: -------------------------------------------------------------------------------- 1 | import { getImageURL } from "../../api/baseapi"; 2 | import { getConstruct } from "../../api/dbapi"; 3 | import { Construct, Sprite } from "../../classes/Construct"; 4 | import { Emotion } from "../../types"; 5 | import { useEffect, useState } from "react"; 6 | 7 | interface SpriteProps { 8 | constructID: string; 9 | emotion: Emotion, 10 | sendPoke: () => void 11 | } 12 | const SpriteDisplay = (props: SpriteProps) => { 13 | const { constructID, emotion, sendPoke } = props; 14 | const [construct, setConstruct] = useState(null); 15 | const [spriteData, setSprite] = useState(null); 16 | 17 | useEffect(() => { 18 | if (constructID) { 19 | getConstruct(constructID).then((newConstruct) => { 20 | setConstruct(newConstruct); 21 | const foundSprite = newConstruct.sprites.find((sprite) => sprite.emotion === emotion); 22 | if (foundSprite){ 23 | setSprite(foundSprite); 24 | } 25 | }) 26 | } 27 | }, [constructID]); 28 | 29 | useEffect(() => { 30 | if (construct) { 31 | const foundSprite = construct.sprites.find((sprite) => sprite.emotion === emotion); 32 | if (foundSprite){ 33 | setSprite(foundSprite); 34 | } 35 | } 36 | }, [emotion]); 37 | 38 | return ( 39 |
40 | {spriteData?.image64 !== "" && spriteData?.image64 && ( 41 | {spriteData?.emotion} sendPoke()}/> 42 | )} 43 |
44 | ); 45 | } 46 | export default SpriteDisplay; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/streamable-text/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | interface Props { 4 | text: string; 5 | delay?: number; 6 | typingSpeed?: number; 7 | isDisabled?: boolean; 8 | } 9 | 10 | const StreamableText: React.FC = ({ text, delay = 0, typingSpeed, isDisabled = false }) => { 11 | const [streamedText, setStreamedText] = useState(''); 12 | const [typingSpeedState, setTypingSpeedState] = useState(100); 13 | 14 | useEffect(() => { 15 | if(typingSpeed) { 16 | setTypingSpeedState(typingSpeed); 17 | } 18 | }, [typingSpeed]); 19 | 20 | useEffect(() => { 21 | let textToStream = ''; 22 | let i = 0; 23 | let timeoutId: NodeJS.Timeout | null = null; 24 | 25 | // This function will add characters to streamedText over time 26 | const streamText = () => { 27 | if(i < text.length && !isDisabled) { // Check if isDisabled is true before streaming next character 28 | textToStream += text.charAt(i); 29 | setStreamedText(textToStream); 30 | i++; 31 | timeoutId = setTimeout(streamText, typingSpeedState); 32 | } 33 | } 34 | 35 | // Use setTimeout to delay the start of text streaming 36 | timeoutId = setTimeout(() => { 37 | if(text.length > 0 && !isDisabled) { // Check if isDisabled is true before starting text stream 38 | streamText(); 39 | } 40 | }, delay * 1000); // Convert delay from seconds to milliseconds 41 | 42 | // If the component is unmounted or text changes, clear the timeout to prevent a memory leak 43 | return () => { 44 | if(timeoutId) { 45 | clearTimeout(timeoutId); 46 | } 47 | }; 48 | }, [text, delay, isDisabled === false]); // Watch isDisabled state as well 49 | 50 | return ( 51 | <> 52 | {isDisabled ? text : streamedText} 53 | 54 | ); 55 | } 56 | 57 | export default StreamableText; -------------------------------------------------------------------------------- /TalOS-Companion/src/components/string-array-editor-cards/StringArrayEditorCards.scss: -------------------------------------------------------------------------------- 1 | .scrollable-container { 2 | max-height: 3rem; /* Set the maximum height */ 3 | overflow-y: auto; /* Set vertical overflow to auto */ 4 | } 5 | -------------------------------------------------------------------------------- /TalOS-Companion/src/components/string-array-editor/StringArrayEditor.scss: -------------------------------------------------------------------------------- 1 | .scrollable-container { 2 | max-height: 3rem; /* Set the maximum height */ 3 | overflow-y: auto; /* Set vertical overflow to auto */ 4 | } 5 | -------------------------------------------------------------------------------- /TalOS-Companion/src/components/string-array-editor/index.tsx: -------------------------------------------------------------------------------- 1 | import './StringArrayEditor.scss' 2 | interface MultiInputEditorPropsBase { 3 | value: string[]; 4 | className?: string; 5 | id?: string; 6 | disabled?: boolean; 7 | } 8 | 9 | interface MultiInputEditorPropsEnabled extends MultiInputEditorPropsBase { 10 | disabled?: false; 11 | onChange: (value: string[]) => void; 12 | } 13 | 14 | interface MultiInputEditorPropsDisabled extends MultiInputEditorPropsBase { 15 | disabled: true; 16 | onChange?: (value: string[]) => void; 17 | } 18 | 19 | type MultiInputEditorProps = MultiInputEditorPropsEnabled | MultiInputEditorPropsDisabled; 20 | 21 | const StringArrayEditor = (props: MultiInputEditorProps) => { 22 | const { value, onChange, className, id, disabled } = props; 23 | 24 | return ( 25 |
26 | {disabled ? null : ( 27 | 37 | )} 38 |
39 | {value.map((item, index) => ( 40 |
41 |