Compare commits

...

1181 Commits

Author SHA1 Message Date
Enrico Ros a58c3a6a52 Merge branch 'Penagwin-groq-provider' 2024-03-01 15:35:13 -08:00
Enrico Ros 6147f1131b Groq review: perfect. 2024-03-01 15:34:48 -08:00
Enrico Ros 26552aa996 Update Groq icon 2024-03-01 15:28:26 -08:00
Paul Lang 17cc31f376 Added support to fetch models for groq 2024-03-01 13:23:20 -05:00
Paul Lang 41f7a63392 Added Groq as an endpoint 2024-02-29 13:06:56 -05:00
Enrico Ros 70474ce517 Chat Drawer: improve view menu 2024-02-28 03:47:37 -08:00
Enrico Ros 365f144c57 System messages: improve menu 2024-02-28 03:07:22 -08:00
Enrico Ros ff1e1c249f System messages: differentiate looks 2024-02-28 03:02:41 -08:00
Enrico Ros e3ed6f802d Browse: disambiguate more 2024-02-27 00:48:06 -08:00
Enrico Ros b5ed078260 Stable: fix news disappearing 2024-02-27 00:46:23 -08:00
Enrico Ros 64310292da Browse: disambiguate 2024-02-27 00:43:21 -08:00
Enrico Ros 2656d0dfa5 GA: Infrastructure. Enables data analysis for product improvement. 2024-02-27 00:26:13 -08:00
Enrico Ros 70a7f0aaf4 GA: Build-time validation 2024-02-26 23:24:32 -08:00
Enrico Ros d405dcaa3a GA: Docs 2024-02-26 23:24:24 -08:00
Enrico Ros 5ecef67855 GA: Docker and GitHub actions support 2024-02-26 23:24:12 -08:00
Enrico Ros 8f6d9f8c31 Debug: add frontend variables (as a reminder, they're set at build time by next.js) 2024-02-26 23:22:36 -08:00
Enrico Ros 8662437b1a Bring back Dev mode settings 2024-02-26 14:34:16 -08:00
Enrico Ros ce3e5629e7 Bits 2024-02-26 14:26:39 -08:00
Enrico Ros d4c487534d Optimize heavily: ChatMessages can finally be memoed 2024-02-26 14:25:35 -08:00
Enrico Ros 2b9577b87d Beam: subordinate to option 2024-02-26 13:28:48 -08:00
Enrico Ros 6a0f8564f3 Beam: as command 2024-02-26 13:12:28 -08:00
Enrico Ros e9f74946e3 Beam: cleanups 2024-02-26 13:00:10 -08:00
Enrico Ros e043ab8710 Temp: hide Draw and Workspaces 2024-02-26 12:45:43 -08:00
Enrico Ros 79dd2f5f6b Update #434: online models with a 🌐 2024-02-26 12:42:00 -08:00
Enrico Ros 76e6ca8f0c Update #434: deprecated as hidden, and new sort by capabilities, descending 2024-02-26 12:28:09 -08:00
Enrico Ros 0f310e866f Merge pull request #434
Added Perplexity's new models, updated context lengths, new deprecations
2024-02-26 12:15:21 -08:00
Enrico Ros 1f66221bbd Mistral: elegance. 2024-02-26 12:14:10 -08:00
Enrico Ros 635b70fb6c Roll packages 2024-02-26 12:14:00 -08:00
Enrico Ros d113801b18 Beam: begin 2024-02-26 12:05:45 -08:00
Enrico Ros ac74efed4a Cleanups 2024-02-26 12:05:43 -08:00
Enrico Ros 52e1dc2fb2 Mistral: support for Large models 2024-02-26 12:04:29 -08:00
Paul Lang 7564fd5e03 Added Perplexity's new models, updated context lengths, new deprecations
https://docs.perplexity.ai/changelog/api-updates-february-2024
2024-02-26 14:25:54 -05:00
Enrico Ros 96810328ee Remove Ephemerals from the Chat Store 2024-02-26 04:32:36 -08:00
Enrico Ros 5603a98df9 Reposition Ephemerals 2024-02-26 02:47:40 -08:00
Enrico Ros 5c800e35f2 Extract simil-controllers for the chat 2024-02-26 02:30:50 -08:00
Enrico Ros dd15eecdf1 Improve debug message 2024-02-26 02:26:51 -08:00
Enrico Ros b6cb68bfcf Fix: react to the change 2024-02-25 19:02:53 -08:00
Enrico Ros 07c5143f1e Fix: don't hide the persona of the current chat in the dropdown 2024-02-25 19:00:05 -08:00
Enrico Ros e8c0cf3306 LocalAI: user-configurable API Key. Fixes #432. Additionally, full server-side config is allowed. 2024-02-23 05:24:31 -08:00
Enrico Ros 5e86d16442 LocalAI: support for backend configuration env-vars. Part 1/2 of #432.
- LOCALAI_API_HOST: Sets the URL of the LocalAI server, or defaults to http://127.0.0.1:8080
 - LOCALAI_API_KEY: The (Optional) API key for LocalAI
2024-02-23 04:46:24 -08:00
Enrico Ros 5ff246a241 Gemini: detect max_tokens, and safer parsing 2024-02-23 04:20:48 -08:00
Enrico Ros 58d54682ab Tryfix #431 2024-02-23 03:02:11 -08:00
Enrico Ros 5ab547d434 Improve usability of the llm list dialog 2024-02-23 02:57:11 -08:00
Enrico Ros 96a5868543 Fonts: rationalize sizes 2024-02-23 02:18:31 -08:00
Enrico Ros 0422c03efe Comments 2024-02-22 23:22:56 -08:00
Enrico Ros 2745c7295e Fix the client variable destructuring 2024-02-22 23:06:54 -08:00
Enrico Ros 82f6ec5839 Bits 2024-02-22 22:56:52 -08:00
Enrico Ros 8e1a155cff Document the PlantUML Server variable, and improve error checking in the renderer. 2024-02-22 22:55:16 -08:00
Fred Liu 521578c4aa Enable custom PlantUML server, Fixes #415
(cherry picked from commit 49392acfd6ab55cc4ba8a951272e921b7e8ff64c, fredliubojin main)
2024-02-22 22:34:14 -08:00
Enrico Ros a04f5f8c94 Call: fix overflow on Telephone 2024-02-22 10:49:40 -08:00
Enrico Ros fb6f96689b Improve Image warning & style. Closes #419 2024-02-22 10:36:49 -08:00
Enrico Ros 69a12d45f3 Restore and improve calls on the main branch. Closes #424 2024-02-22 09:19:54 -08:00
Enrico Ros bf4dd37a1b Calls: improve message looks 2024-02-22 09:18:00 -08:00
Enrico Ros b1230a9758 Clarify isVercelFromBackendOrSSR 2024-02-22 08:55:46 -08:00
Enrico Ros 23621c57ed Fix Share title 2024-02-22 08:49:02 -08:00
Enrico Ros 5f49a9f8ef [bug] Hide personas from the dropdown if hidden in the selector. 2024-02-22 08:48:17 -08:00
Enrico Ros c5b31c3975 Fix -kebab-case 2024-02-22 08:47:39 -08:00
Enrico Ros 74dbe11d4a Improve gfx on split screen 2024-02-22 08:40:23 -08:00
Enrico Ros 64b18c0a0a Dev2: prefer tables 2024-02-22 08:30:50 -08:00
Enrico Ros 7c6cec8eea Persona selection: improve first time experience 2024-02-22 08:06:17 -08:00
Enrico Ros 2b1869e1b3 Bits 2024-02-22 07:54:16 -08:00
Enrico Ros 87e5a155ba Revert 2024-02-22 07:52:18 -08:00
Enrico Ros d5c7071f1b Default to ContentScaling: 'sm' 2024-02-22 07:45:49 -08:00
Enrico Ros 04eb2210e6 Use a less sharp edge 2024-02-22 07:42:53 -08:00
Enrico Ros 4748b00be1 Show a warning when the page is being translated. Closes #429 2024-02-22 07:33:11 -08:00
Enrico Ros 18968ba985 Button to unhide models. Closes #430 2024-02-22 06:50:45 -08:00
Enrico Ros 59b300b71e Merge remote-tracking branch 'opensource/main-stable' 2024-02-22 06:39:12 -08:00
Enrico Ros 5916ef74f9 Gemini: support for createTunedModel 2024-02-22 06:38:45 -08:00
Enrico Ros f5602723c7 Improve outputs 2024-02-21 16:34:13 -08:00
Enrico Ros 59795dcd22 Improve export scripts 2024-02-21 16:17:24 -08:00
Enrico Ros 127a5cbf96 LocalAI: update docs 2024-02-21 15:02:07 -08:00
Enrico Ros 2b040664cb Use /info/debug to export App and Browser info for debugging 2024-02-21 02:10:39 -08:00
Enrico Ros 4ffbdfd16c Improve Placeholder App 2024-02-21 01:25:11 -08:00
Enrico Ros e200cbf312 Update name 2024-02-21 01:24:57 -08:00
Enrico Ros f4edd192fd Package min chunks of 40kb 2024-02-21 00:34:30 -08:00
Enrico Ros dd07167087 Roll packages 2024-02-20 21:36:51 -08:00
Enrico Ros 81aa8468a7 Merge remote-tracking branch 'opensource/main-stable'
# Conflicts:
#	package-lock.json
#	package.json
2024-02-20 21:31:47 -08:00
Enrico Ros 871e72b655 Update Vercel packages 2024-02-20 21:30:25 -08:00
Enrico Ros 9825d8e2f3 Specialize React->Next 2024-02-20 21:09:05 -08:00
Enrico Ros 58c5569beb Bundle: min 50kb - optimization trial 2024-02-20 17:20:43 -08:00
Enrico Ros c975511c74 Merge remote-tracking branch 'opensource/main-stable' 2024-02-20 16:51:59 -08:00
Enrico Ros e3c52fb1f9 Flush 2024-02-20 16:51:49 -08:00
Enrico Ros 397517e666 Tryfix CLS with delayed attraction 2024-02-20 16:51:26 -08:00
Enrico Ros 09088febe8 Tryfix CLS with fast bootup routing 2024-02-20 16:50:48 -08:00
Enrico Ros bbf5dc078e Increase the sample rate
(cherry picked from commit ce0dca86ac)
2024-02-20 15:31:00 -08:00
Enrico Ros 14d57aa622 Use Vercel components only on Vercel deployments.
(cherry picked from commit 72bb31881a)
2024-02-20 15:31:00 -08:00
Enrico Ros bcfc4921ca Publish docs 2024-02-20 04:26:13 -08:00
Enrico Ros cff70ebadd Fix derived logo 2024-02-20 03:03:22 -08:00
Enrico Ros 4b9c958d65 Docs: cleanup - in preparation for web docs 2024-02-20 01:51:47 -08:00
Enrico Ros 7dc7116a2f Docs: cleanup and add index (README.md). 2024-02-20 01:32:05 -08:00
Enrico Ros 92a2c93644 Roll package 2024-02-19 05:25:11 -08:00
Enrico Ros 7be0d88794 Remove Experiment 2024-02-19 05:25:11 -08:00
Enrico Ros ff6ca01813 Flush changes 2024-02-19 05:25:11 -08:00
Enrico Ros ce0dca86ac Increase the sample rate 2024-02-19 00:32:50 -08:00
Enrico Ros 6c51a36dbc Prevent standalone builds from modifying the tsconfig.json. 2024-02-18 15:12:59 -08:00
Enrico Ros 72bb31881a Use Vercel components only on Vercel deployments. 2024-02-18 15:12:24 -08:00
Enrico Ros c6fcad03cd Add Sharp as suggested by Next 2024-02-18 14:57:36 -08:00
Enrico Ros 70de7133a9 Electron: skeleton 2024-02-18 02:20:04 -08:00
Enrico Ros ef36751eac Electron: structure 2024-02-18 02:19:44 -08:00
Enrico Ros dee1461b9c Draggable App Bar 2024-02-18 01:29:10 -08:00
Enrico Ros 3b775fc817 Export: automate static exports 2024-02-18 00:39:10 -08:00
Enrico Ros da52eff9d3 Try fixing the dockerfile again. 2024-02-17 20:49:19 -08:00
Enrico Ros a7efaa7720 Export Frontend: to the 'dist/' folder 2024-02-17 20:13:56 -08:00
Enrico Ros a42587c498 Fix Docker build after moving Prisma 2024-02-17 20:04:48 -08:00
Enrico Ros d29265f042 Relocate Prisma to src/server/prisma 2024-02-17 19:44:36 -08:00
Enrico Ros c305b44c41 Fix Chat interactivity on drag-to-collapse 2024-02-17 19:07:30 -08:00
Enrico Ros 32ff65be1c Roll packages 2024-02-17 18:33:11 -08:00
Enrico Ros b550cbdfc7 Pipeline, add support for .MJS 2024-02-17 18:09:54 -08:00
Enrico Ros f767ad81ce Export Frontend: work around NextJS aborting on the nodejs API.
This introduces a pre-build step on Next Build, which hides the files
in the app/api directory when the EXPORT_FRONTEND environment
variable is true-ish.

Hopefully there won't be disruption due to the post-processing step.

Also check https://github.com/vercel/next.js/issues/61213 for
upstream updates.
2024-02-17 18:03:21 -08:00
Enrico Ros 35d04055ac Update OpenAI models cutoff date, as OpenAI swapped it for existing models... Fixes #422. 2024-02-17 17:59:16 -08:00
Enrico Ros c7fe75829f Export Frontend: first steps
Note: needs deletion of app/api/trpc-node/[trpc]/route.ts due to
an upstream issue
2024-02-17 16:52:32 -08:00
Enrico Ros 8299b4c148 Clearly mark frontend fetches 2024-02-17 15:30:35 -08:00
Enrico Ros 5bb84f8930 Elevenlabs: mode handler to module 2024-02-17 15:17:07 -08:00
Enrico Ros 047c9a2f07 Backend -> Services fetchers 2024-02-17 15:06:59 -08:00
Enrico Ros 8c11925444 Delete also when searching. 2024-02-16 16:30:42 -08:00
Enrico Ros 1cbb4fd11a Update to parse more. #361 2024-02-16 10:14:02 -08:00
Enrico Ros 0a8d9ebd55 Render Latex when in \[..\] blocks. Requires newlines "\[\n ... \n\]\n". Fixes #361 2024-02-16 10:11:57 -08:00
Enrico Ros 386724655e Pre-select the last added Model Source (not the earliest) 2024-02-16 09:54:17 -08:00
Enrico Ros 7b37b9e204 Google Gemini: auto-detect symlink targets 2024-02-16 09:49:23 -08:00
Enrico Ros 3b02612124 Google Gemini: show symlink models 2024-02-16 09:45:23 -08:00
Enrico Ros 32b040cbcf Search results heading 2024-02-16 09:33:40 -08:00
Enrico Ros 75a15a12a6 Message when no chats in active folder/search. Fixes #394 2024-02-16 09:28:33 -08:00
Enrico Ros 0cb7be8381 Fix state 2024-02-16 09:10:44 -08:00
Enrico Ros 20d3c267a3 Persist user edited model settings. Fixes #398 2024-02-16 08:50:59 -08:00
Enrico Ros 84313ffa8c Inconclusive. #401 2024-02-16 08:37:06 -08:00
Enrico Ros be66ce0f32 Perplexity: full support. Fixes #407 2024-02-16 08:21:49 -08:00
Enrico Ros 12c1194009 Move Discord icon 2024-02-16 07:39:24 -08:00
Enrico Ros 82b83a39dd Import/Export All: save/restore folders (and folder presence state). Fixes #416 2024-02-16 07:11:07 -08:00
Enrico Ros ac617de4ae Update 2024-02-15 15:57:29 -08:00
Enrico Ros b6731c9afa Less is more 2024-02-14 05:30:58 -08:00
Enrico Ros 3a7ece6508 Improve 2024-02-14 05:27:28 -08:00
Enrico Ros 2c69d2805d While at it 2024-02-14 05:21:48 -08:00
Enrico Ros 87b03c67ec Expandable sections 2024-02-14 05:19:34 -08:00
Enrico Ros 569b08288e More moves 2024-02-14 05:12:05 -08:00
Enrico Ros 049fa90832 More moves 2024-02-14 05:06:42 -08:00
Enrico Ros f23347de7e Refer to CoolAGI 2024-02-14 05:02:09 -08:00
Enrico Ros 0272283f94 LocalAI: mention Voice Cloning after the chat with @mudler 2024-02-13 23:04:05 -08:00
Enrico Ros 64640c1331 Group by date by default 2024-02-13 19:28:53 -08:00
Enrico Ros ff1471cfe8 Call it like it is 2024-02-13 04:15:46 -08:00
Enrico Ros aae3783f67 LocalAI: Model Gallery Admin panel. Fixes #411 2024-02-13 04:12:14 -08:00
Enrico Ros 053aa12a91 Improve messages 2024-02-13 02:13:51 -08:00
Enrico Ros 17a006db8f LocalAI: status of integration 2024-02-13 01:41:41 -08:00
Enrico Ros 56d912da3d Add Sx and expandedVariant to ExpanderAccordion 2024-02-13 01:41:30 -08:00
Enrico Ros 3c60284e6e LocalAI: raise to 4 max instances 2024-02-13 00:02:58 -08:00
Enrico Ros 76ddff4820 Source Add Dialog: improvement, esp. icons and badges 2024-02-12 15:25:42 -08:00
Enrico Ros 1bd6dc0a1a LocalAI: update icon 2024-02-12 14:39:30 -08:00
Enrico Ros 5c7d289123 Placeholder 2024-02-12 13:56:45 -08:00
Enrico Ros 8f6d646a1f Perfect diagrams auto-resize on mobile 2024-02-12 13:48:59 -08:00
Enrico Ros c42123fe2a Fix mobile zIndex 2024-02-12 13:42:15 -08:00
Enrico Ros 58bd84b600 Grouping by Date or Persona 2024-02-12 03:50:18 -08:00
Enrico Ros 621eb4a54c Chat: filtered deletion + navigation Rendrer tree != chats list. Fixes #324
This is a good feature. The 'Delete All' will always operate on the current selection.

If a Folder restricts to 10 conversasions, and a search narrows it down to 3,
then the 3 will be deleted. This works really
well for quick cleanups.
2024-02-12 03:04:53 -08:00
Enrico Ros 9073cff1c1 AppChat: perfect filtered deletion 2024-02-12 02:59:55 -08:00
Enrico Ros d69516df5c Add a ProcessingQueue, fixes #409 2024-02-11 22:36:34 -08:00
Enrico Ros 7322280d3d Try a change for #408 2024-02-11 21:50:42 -08:00
Enrico Ros 5f79569ea9 Style fix 2024-02-11 13:26:54 -08:00
Enrico Ros fe8b8472b7 Share Blocks
Note: there's one dependency to ../../app/chat inside
2024-02-11 12:55:30 -08:00
Enrico Ros cb2b1a89b5 Draw: temp click to remove 2024-02-11 03:38:59 -08:00
Enrico Ros 6ece7b884a Draw: mobile/desktop grid seems fine 2024-02-11 03:37:18 -08:00
Enrico Ros 04fc9264cb Draw: draw xN images 2024-02-11 03:13:42 -08:00
Enrico Ros 016c2df942 Draw: show the first images 2024-02-11 02:57:10 -08:00
Enrico Ros bf6a2b60b9 Draw: improbetterment 2024-02-11 02:42:59 -08:00
Enrico Ros 5093e70552 ditto 2024-02-11 02:39:53 -08:00
Enrico Ros 3bd50e1b45 More targeted error message 2024-02-11 02:37:40 -08:00
Enrico Ros 793383f70d Slight change to the error format 2024-02-11 02:23:32 -08:00
Enrico Ros 3b84e42932 Draw: Begin rendering 2024-02-11 02:03:51 -08:00
Enrico Ros 09efc9b148 Draw: Designer: uids per each prompt coming out 2024-02-11 02:00:34 -08:00
Enrico Ros 90c2542486 Image Renderer: cleanups 2024-02-11 01:59:38 -08:00
Enrico Ros 9259fa3b6d Improved New Chat button - fits better 2024-02-11 00:54:30 -08:00
Enrico Ros 0c8f102830 Folders: dynamic scaling 2024-02-11 00:13:52 -08:00
Enrico Ros 02972a0fb6 Folders: move button in the pane 2024-02-11 00:12:41 -08:00
Enrico Ros 2a4a65f129 Improve Icon 2024-02-10 23:44:04 -08:00
Enrico Ros e16270e1ec Chat Drawer: Folder Toggle Icons 2024-02-10 23:40:55 -08:00
Enrico Ros 201a884828 Add dependency 2024-02-10 23:26:35 -08:00
Enrico Ros 2a32139be3 Text Scaling: scale the drawer contents 2024-02-10 23:26:14 -08:00
Enrico Ros 7955bf2b86 Show error messages - where they belong. 2024-02-10 21:57:47 -08:00
Enrico Ros a5d70e4ca3 Memory Optimization in ChatMessages: Memo the non-in-flux (and only the root), and within the in-flux'd memo only the baked parts (and not the on the fly) - massive GC savings 2024-02-10 21:10:01 -08:00
Enrico Ros 12eb08ee08 Optimized Composer down to 2.8ms: stable callbacks, stable const styles, memoed Buttons. 2024-02-10 21:07:36 -08:00
Enrico Ros fe74583bae Optimize Tooltip timeouts 2024-02-10 20:07:09 -08:00
Enrico Ros b8b1dd2cfb Optimize 2024-02-10 20:06:35 -08:00
Enrico Ros 9723b328c3 Chat Drawer Item: denser menu, with disappearing items on Delete Arming, for Focus 2024-02-10 19:48:28 -08:00
Enrico Ros edc3ab6d00 Branch Icon on the Chats 2024-02-10 19:47:35 -08:00
Enrico Ros 0e243cd167 Update {{LocaleNow}} and 'Generic' 2024-02-10 10:19:50 -08:00
Enrico Ros b8e0064381 Adding {{LocaleNow}} with enough info to get on the same page as the user 2024-02-10 10:18:24 -08:00
Enrico Ros 018c77901d Labs Setting: Performance Mode - Unlocks updates (otherwise visually capped at 12Hz) 2024-02-09 22:38:44 -08:00
Enrico Ros 5849fd9c94 Turning on revealing client-side debug messages. 2024-02-09 22:07:41 -08:00
Enrico Ros 6a5d1eb5c2 Write Scheduler for iDB 2024-02-09 21:37:21 -08:00
Enrico Ros fc70857fae Limit Assistant responses editMessages to 12Hz and decrease sqrt with the number of chats 2024-02-09 21:18:35 -08:00
Enrico Ros 5cd6fe23d8 Sharing: add the title on native shares 2024-02-09 21:13:05 -08:00
Enrico Ros beffcdcba9 Clenaups on the streaming client, to clarify incrementalness 2024-02-09 21:13:05 -08:00
Enrico Ros cdd39457ff Begin cleanup of the streaming client 2024-02-09 21:13:05 -08:00
Enrico Ros 937b2806ef roll packages 2024-02-09 01:29:41 -08:00
Enrico Ros 34552190c6 Merge placeholder - will remove the feature-promptFX branch 2024-02-09 00:36:10 -08:00
Enrico Ros 7e762d5ddc Alt Chat Title 2024-02-09 00:33:28 -08:00
Enrico Ros 8e78b21a5c AI Auto-Title: async 2024-02-09 00:32:21 -08:00
Enrico Ros ae85fdf59f Open Code only on complete blocks 2024-02-09 00:31:19 -08:00
Enrico Ros e39dc428cc Fix CSS 2024-02-09 00:31:07 -08:00
Enrico Ros cc178efacb Labs: toggle Chat title 2024-02-08 23:52:21 -08:00
Enrico Ros 8a7a3afc10 LinkChat: mark messages as mobile to auto-scale charts on mobile 2024-02-08 23:18:26 -08:00
Enrico Ros e0f1689125 LinkChat: move indications 2024-02-08 23:12:10 -08:00
Enrico Ros 3acdd75863 Update CodePen, add StackBlitz, JSFiddle 2024-02-08 22:58:53 -08:00
Enrico Ros 1ca5ff726c Remove Replit support - Replit does not support to be sent code anymore. Looking for alternatives. 2024-02-08 21:02:12 -08:00
Enrico Ros 464051c319 LinkChat: renames 2024-02-08 20:53:27 -08:00
Enrico Ros 548859fa65 Customize existing prompts into new 2024-02-08 20:50:44 -08:00
Enrico Ros f57c10508f Dev2: reduce annoyances 2024-02-08 20:50:06 -08:00
Enrico Ros b7f53d965f Merge remote-tracking branch 'opensource/main-stable' 2024-02-08 18:13:11 -08:00
Enrico Ros 28b1090fd7 Enable horizontally scrollable attachments. Fixes #406 2024-02-08 18:11:12 -08:00
Enrico Ros 566bf8d38e Share Link: font size setting 2024-02-08 15:16:35 -08:00
Enrico Ros 663306bd3b Improve spacings / list buttons sizes 2024-02-08 07:20:00 -08:00
Enrico Ros 165a5e60d3 Scaling: improve 2024-02-08 06:56:48 -08:00
Enrico Ros 3b01a26eed Scaling: clean 2024-02-08 06:21:52 -08:00
Enrico Ros 65f997a2ba Roll packages 2024-02-08 05:31:53 -08:00
Enrico Ros c1217ed8ed No tRPC fixes 2024-02-08 04:38:13 -08:00
Enrico Ros 6ae76c553f Improve DallE 1hr dialog. 2024-02-08 04:09:08 -08:00
Enrico Ros 141096eace Links: open them externally so big-AGI is not interrupted. 2024-02-08 03:11:00 -08:00
Enrico Ros c4003a888a Shortcuts for text size: ctrl + shift + '+' / '-' 2024-02-08 02:56:39 -08:00
Enrico Ros d1c22e12a7 Consolidate the 3 dynamic imports into 1 - faster, smaller. 2024-02-08 02:44:42 -08:00
Enrico Ros 9461cab182 Save 3kb from dynamically importing this module 2024-02-08 02:15:04 -08:00
Enrico Ros dcceead4ca Remove unused icon (~0.6kb bundle) 2024-02-08 02:02:04 -08:00
Enrico Ros ae8ac5111c Reduce total bundle size for React-Player (YouTube only) 2024-02-08 01:49:34 -08:00
Enrico Ros 1e35fceb61 Best-Of: custom icon 2024-02-08 01:35:11 -08:00
Enrico Ros 88d0ffd712 Best-Of: input wires 2024-02-08 01:35:11 -08:00
Enrico Ros 6cbc3fbf28 1.14: begin mentioning 2024-02-08 01:35:00 -08:00
Enrico Ros 4eb6f6da9d Re-open the 2 Dev Sections 2024-02-08 01:34:19 -08:00
Enrico Ros 5bc320385f Update README 2024-02-08 00:26:07 -08:00
Enrico Ros 3d39a35c03 Release fix: decrease visual clutter 2024-02-07 23:52:24 -08:00
Enrico Ros 5ca9475bb6 1.13.0: Update README 2024-02-07 23:50:07 -08:00
Enrico Ros f12386c614 Merge branch 'release-1.13.0' 2024-02-07 23:47:29 -08:00
Enrico Ros 485dd0d91f 1.13.0: README and Changelog 2024-02-07 23:46:51 -08:00
Enrico Ros fc137176bd 1.13.0: Rename 2024-02-07 23:22:25 -08:00
Enrico Ros b34fe2f9f6 1.13.0: Disable Draw & Workspace for release 2024-02-07 23:16:07 -08:00
Enrico Ros 3b7916c536 1.13.0: Fix date 2024-02-07 23:13:22 -08:00
Enrico Ros d11a2b59ee Move release Covers 2024-02-07 23:13:15 -08:00
Enrico Ros 63d1ec4c30 Fix Cover Image sizing to absorb the border 2024-02-07 23:01:10 -08:00
Enrico Ros 4ed49be67e 1.13.0: Cover Image 2024-02-07 23:00:54 -08:00
Enrico Ros 3a0749c5b2 1.13.0: News 2024-02-07 22:16:10 -08:00
Enrico Ros 63470adc0f Explicitly call out the code line height 2024-02-07 21:35:50 -08:00
Enrico Ros 0bbfad4b41 1.13.0: Version 2024-02-07 20:58:07 -08:00
Enrico Ros f9cb97ca49 For later 2024-02-07 20:57:49 -08:00
Enrico Ros b63636cf2f Style 2024-02-07 20:51:44 -08:00
Enrico Ros 54b388c9ae Reorder Developer2 2024-02-07 18:14:25 -08:00
Enrico Ros d233f0946f Zen mode: do not show chat list underbars 2024-02-07 18:13:27 -08:00
Enrico Ros 671ac36946 PMix: notes 2024-02-07 18:11:58 -08:00
Enrico Ros e6ba217302 PMix: improve local time 2024-02-07 18:02:20 -08:00
Enrico Ros b9a18a5442 Dev2: add icon 2024-02-07 17:53:39 -08:00
Enrico Ros f8d0f25f72 On mobile, auto-fit mermaid and PlantUML by default. 2024-02-07 17:45:46 -08:00
Enrico Ros 2213c61760 Reuse 2024-02-07 17:36:43 -08:00
Enrico Ros e7edffa237 Add a Dev2 Example/Preview 2024-02-07 17:33:47 -08:00
Enrico Ros fd83aca7a4 Bare bones prompt mixer 2024-02-07 17:33:30 -08:00
Enrico Ros bdc2d07747 PersonaSelector: show prompt 2024-02-07 17:03:36 -08:00
Enrico Ros 1953f7d31a Can Scale (up/dn) SVG, Mermaid and PlantUMLs 2024-02-07 09:57:53 -08:00
Enrico Ros 054ed80bbe GitHub Markdown style: scaleable spacing. #399 2024-02-07 09:08:58 -08:00
Enrico Ros 13b64e65c3 Dynamic Text Size switching. Fixes #399 2024-02-07 09:07:19 -08:00
Enrico Ros ee9ee72505 Fix a few styling issues on the blocks 2024-02-07 07:59:19 -08:00
Enrico Ros 1b631a91b3 Improve Markdown rendering spacing. Blocks break the top/bottom margins. 2024-02-07 07:52:17 -08:00
Enrico Ros 118d2cb2ad Nits. 2024-02-07 07:26:03 -08:00
Enrico Ros b6acfa9d49 LM Studio Config: add @techfren's video 2024-02-07 06:59:46 -08:00
Enrico Ros 4798ba3fd0 Dynamic Video Player 2024-02-07 06:59:00 -08:00
Enrico Ros 14608f97da Roll packages - hold back Joy which depends on yet another version of MUI 2024-02-07 06:29:48 -08:00
Enrico Ros 901d590159 Update config-lmstudio.md 2024-02-07 06:10:50 -08:00
Enrico Ros 28e71d4ac7 LMStudio: make the doc and link the Video by @techfren 2024-02-07 05:36:15 -08:00
Enrico Ros 7f958c9e66 Multi-Chats: super-power to create new 2024-02-07 04:25:09 -08:00
Enrico Ros 910f0c5556 New Chats: improve appearance 2024-02-07 04:25:08 -08:00
Enrico Ros 427ef8c108 MultiChat: show where windows are open, nicely 2024-02-07 04:08:23 -08:00
Enrico Ros 2efdfca7e5 MultiChat: improve color, to better relate to the drawer 2024-02-07 04:00:56 -08:00
Enrico Ros bc113b08f7 Do a better job at signaling which window is where 2024-02-07 03:59:39 -08:00
Enrico Ros 262a6d2560 Bring branch with split 2024-02-07 03:31:15 -08:00
Enrico Ros f9224aa25d Split-open: say it's already open 2024-02-07 03:29:11 -08:00
Enrico Ros 6d0f7949f8 Persona Selector: show newly missing 2024-02-07 03:29:10 -08:00
Enrico Ros 1a679bcf90 Use the Memo RenderMarkdown 2024-02-07 03:06:52 -08:00
Enrico Ros 5de34fe3af Split Screen: Duplicate into new (but disable this while testing it) 2024-02-07 03:02:13 -08:00
Enrico Ros 420b4565dd Add a command (/clear all) to reset chats. 2024-02-07 02:23:47 -08:00
Enrico Ros 27eb9adb16 Memo code and markdown rendering for the current message. Shall help vigorously. #402. It's a tradeoff with mem tho. 2024-02-07 02:05:10 -08:00
Enrico Ros c4277b9ef0 Optimization on the message being typed - recycles references to speed up React. Fixes #402 2024-02-07 01:53:28 -08:00
Enrico Ros ec39c58474 Message Render: cleanup diffing pipeline 2024-02-07 01:31:07 -08:00
Enrico Ros 3ce2e86a66 Reminders for #401 2024-02-07 00:56:59 -08:00
Enrico Ros d62757d94a Blocks Renderers: extraction, cleanups, more maintainable and optimized 2024-02-07 00:15:58 -08:00
Enrico Ros 7ba315c796 Font Size: UI Setting 2024-02-06 21:34:36 -08:00
Enrico Ros 75e909e0e7 Font Size: add persisted variable 2024-02-06 21:33:41 -08:00
Enrico Ros 285c6a3fac Update Labels width 2024-02-06 21:26:55 -08:00
Enrico Ros 9bcdbf8db6 PersonaSelector: support for imageUri 2024-02-06 20:17:18 -08:00
Enrico Ros ae9d85d2cd Fix accessibility 2024-02-06 20:09:17 -08:00
Enrico Ros ad3191fcaf Optimize with negligible loss of functionality 2024-02-06 19:39:32 -08:00
Enrico Ros d6c98bd304 Models: auto symlink labeling 2024-02-06 17:35:09 -08:00
Enrico Ros 52c1be20d9 Update knowledge cutoff function 2024-02-06 17:27:03 -08:00
Enrico Ros 69fb879439 Update default models 2024-02-06 17:26:23 -08:00
Enrico Ros 135153464a Fix build. 2024-02-06 17:11:23 -08:00
Enrico Ros 87e556d6c4 PersonaSelector: collapse examples on Custom 2024-02-06 16:54:08 -08:00
Enrico Ros 46866ac061 PersonaSelector: fix h-scroll 2024-02-06 16:51:38 -08:00
Enrico Ros 9f222caadf Increase resiliency, and relax deletion/creation of new chats. 2024-02-06 16:42:49 -08:00
Enrico Ros f82ac7a476 PersonaSelector: improve highlight 2024-02-06 15:58:42 -08:00
Enrico Ros 4fa5d875e9 PersonaSelector: animated collapse 2024-02-06 15:47:33 -08:00
Enrico Ros e2b1c6aff0 PersonaSelector: toggleable examples 2024-02-06 15:40:05 -08:00
Enrico Ros 16b25fcc1f PersonaSelector: recycle tile 2024-02-06 15:13:00 -08:00
Enrico Ros 17cd765d00 PersonaSelector: style 2024-02-06 14:53:57 -08:00
Enrico Ros 1ea8b42e5f PersonaSelector: smaller tiles 2024-02-06 14:53:57 -08:00
Enrico Ros 6b5a207522 Merge pull request #397 from oblivio/main
Update config-database.md
2024-02-06 06:53:55 -08:00
Enrico Ros 85d5fef3fb Further improve the Persona selector 2024-02-06 06:12:46 -08:00
Enrico Ros e9a77abd83 Nit 2024-02-06 05:28:21 -08:00
Enrico Ros 9d2857d41e Persona Selector: improve layouts 2024-02-06 05:24:59 -08:00
Enrico Ros 62e71307d0 Explain Shift+Enter 2024-02-06 03:23:47 -08:00
Enrico Ros f517f12b7e Composer: improve layout (but keep the grid that stacks on mobile, for now) 2024-02-06 03:05:06 -08:00
Enrico Ros 510b1d178d MultiChat: button on mobile 2024-02-06 02:05:11 -08:00
Enrico Ros 890e8afd47 Fix for issue reported by @frigjord 2024-02-06 01:03:36 -08:00
Enrico Ros c25ce6db9d Multiple panes splits 2024-02-06 00:25:00 -08:00
Enrico Ros ec789de1d1 Improve the 'Clear folder' and no-folders appearance 2024-02-05 22:56:27 -08:00
Enrico Ros e96ac16d85 Branch: assign to the same folder 2024-02-05 22:51:53 -08:00
Enrico Ros 9d6fe97b11 Assign to folder 2024-02-05 22:51:36 -08:00
Enrico Ros 8e90552fec PageBarDropdowns: extensive improvements 2024-02-05 22:42:45 -08:00
Fabian Valle 71c8d5527e Update config-database.md
Include specific changes required when using MongoDB Atlas. The LinkStorage model needs to change, as well as the db in the Prisma configuration.
2024-02-05 22:49:00 -05:00
Enrico Ros 9fef95303a News: fix CLS 2024-02-05 18:28:38 -08:00
Enrico Ros 8458da826e Merge branch 'main-stable' 2024-02-05 18:15:16 -08:00
Enrico Ros df59f5eb6b News: improve layout, move roadmap as the second item 2024-02-05 18:15:10 -08:00
Enrico Ros 7c0ec8677f News: improve layout, move roadmap as the second item 2024-02-05 18:10:32 -08:00
Enrico Ros 2e23026690 Support for Cover images for releases
(cherry picked from commit 7bc110820e)
2024-02-05 18:10:29 -08:00
Enrico Ros 7bc110820e Support for Cover images for releases 2024-02-05 17:08:16 -08:00
Enrico Ros d3cddd5b60 Merge pull request #393 from oblivio/main
MongoDB Atlas Support
2024-02-05 14:17:32 -08:00
Fabian Valle 24cff721dc update database docs 2024-02-05 10:40:45 -05:00
Fabian Valle 054df44e05 update database docs 2024-02-05 10:39:56 -05:00
Fabian Valle 2dc3af3761 update database docs 2024-02-05 10:38:50 -05:00
Fabian Valle 3d9bf70c85 update database docs 2024-02-05 10:34:44 -05:00
Fabian Valle 30f4f6e7b8 update database docs 2024-02-05 10:33:25 -05:00
Enrico Ros c5c71859f9 Merge branch 'main-stable' 2024-02-05 01:39:05 -08:00
Enrico Ros b1a12d88a1 Delay the Models dialog to the idle cycles (for CLS) 2024-02-05 01:38:53 -08:00
Enrico Ros 78d06e79a5 Merge branch 'main-stable' 2024-02-05 00:07:27 -08:00
Enrico Ros 7580f1526f Optimize Persona Selector (includes fixing CLS). 2024-02-05 00:07:15 -08:00
Fabian Valle 198e76c291 update documentation to explain how to setup MongoDB by modifying the schema.prisma file 2024-02-04 21:10:58 -05:00
Fabian Valle f47bb1484c modify prisma back to original for backwards compatibility 2024-02-04 21:10:23 -05:00
Enrico Ros 91f5136e29 Clarify News button 2024-02-04 15:08:47 -08:00
Enrico Ros da3be58eec Move Files in Chats 2024-02-04 14:20:59 -08:00
Fabian Valle 94432b496b Update env.mjs
+MDB_URI
2024-02-04 10:57:06 -05:00
Fabian Valle eab2550b88 Update backend.router.ts
+MDB_URI
2024-02-04 10:55:56 -05:00
Fabian Valle 179a496737 Update schema.prisma
+MDB_URI
2024-02-04 10:54:47 -05:00
Fabian Valle 8f62c2ab78 Update environment-variables.md
+MDB_URI
2024-02-04 10:53:45 -05:00
Enrico Ros 9eaee22e3b Optimize rendering of PageBarDropdowns 2024-02-04 04:18:20 -08:00
Enrico Ros 2bdfe8399d Optimize rendering of DrawerItems - the Memo is working now 2024-02-04 03:15:00 -08:00
Enrico Ros 001570464c Show Split chats in the Drawer. Fixes #389 2024-02-04 01:58:27 -08:00
Enrico Ros 90e77010bb Merge branch 'aj47-main' 2024-02-03 21:55:56 -08:00
Enrico Ros 6b73294186 Style the button 2024-02-03 21:55:23 -08:00
Enrico Ros 101237aa75 Merge branch 'main' of https://github.com/aj47/big-AGI into aj47-main 2024-02-03 21:05:31 -08:00
Arash Joobandi 8d3377aeb3 misssing commit 2024-02-04 15:51:21 +11:00
Arash Joobandi 3ad350b10b implement react-csv download 2024-02-04 15:49:47 +11:00
Arash Joobandi ce00480d99 add download csv button 2024-02-04 15:31:41 +11:00
Enrico Ros 2e7f2b6004 Rename 'broadcast' to 'multicast' in code, and much improve the Panes and Multicase modes - #388 2024-02-03 19:59:27 -08:00
Enrico Ros aad0eae1b2 Split Chats: Broadcast mode. Fixes #388 2024-02-03 18:40:41 -08:00
Enrico Ros be3e64b1aa Improve Chat Page Menu 2024-02-03 15:46:47 -08:00
Enrico Ros c089ea7499 Chat Split: land. Controls in Page Menu. Fixes #208 2024-02-03 15:31:59 -08:00
Enrico Ros 190010b3e3 Uniform PageMenu (vs. ChatMessage Menu) looks 2024-02-03 14:53:15 -08:00
Enrico Ros 4dcdc175ee Style: slightly smaller radios 2024-02-03 14:46:17 -08:00
Enrico Ros 35fe54c713 Let's just do the opposite, shall we 2024-02-03 14:45:51 -08:00
Enrico Ros fd22d55835 Split view: layout panes vertically on mobile 2024-02-03 14:32:23 -08:00
Enrico Ros c978d78bd4 Improve Menus fit on mobile 2024-02-03 14:22:36 -08:00
Enrico Ros fb488596b8 Fix build 2024-02-03 04:55:18 -08:00
Enrico Ros 9edfa48e23 Split panes: perfect radius 2024-02-03 04:44:07 -08:00
Enrico Ros 25360c5fba Fix drag to resize chat panes: close on drag, 'gray-out' effect, perfect duplicate. 2024-02-03 04:08:33 -08:00
Enrico Ros e8ed346f20 Move Vendor icons 2024-02-03 03:17:10 -08:00
Enrico Ros 507a35a826 Panes: correctly remove when un-splitting 2024-02-03 02:08:29 -08:00
Enrico Ros e604cf97ae Rename closeOpsMenu 2024-02-03 02:08:29 -08:00
Enrico Ros 510753ae1c UXLabs: improve settings 2024-02-03 01:21:10 -08:00
Enrico Ros 828dfb56a2 Screenshots: attach window/screen captures (in 'labs' mode for now). Fixes #387 2024-02-03 01:13:06 -08:00
Enrico Ros 843a8dcd69 HTML5Video ops: use async/await 2024-02-03 00:33:52 -08:00
Enrico Ros 53255d5524 Extract HTML5 Video Frame rendering utils 2024-02-03 00:19:13 -08:00
Enrico Ros 0f8a5149b5 Readme: remove outdated screenshots 2024-02-02 16:30:52 -08:00
Enrico Ros 442d7e5fb5 Readme: update picture 2024-02-02 16:26:05 -08:00
Enrico Ros 11011d5367 OpenAI: improve model sorting, and update the 3.5-Turbo symlink and 3.5 0125 model description. Fixes #380 2024-02-02 16:15:33 -08:00
Enrico Ros b80afca458 Improve Export/Import looks and behavior - Fixes #375 2024-02-02 16:01:30 -08:00
Enrico Ros a93d9aab08 Roll packages 2024-02-02 15:10:11 -08:00
Enrico Ros 721d31d98d Uniform Menu appearances (smaller icons, dense by default). Fixes #382 2024-02-02 15:06:45 -08:00
Enrico Ros 8d83cff966 Share ZIndex 2024-02-02 15:06:44 -08:00
Enrico Ros 7643ee7749 ChatMessage: reorder operation menu items 2024-02-02 15:06:44 -08:00
Enrico Ros 78b0d5eb96 Draw: multiplier (mock) 2024-02-01 02:56:45 -08:00
Enrico Ros 517252240a Draw: roll placeholder 2024-02-01 02:45:53 -08:00
Enrico Ros 173635cfd1 Draw: show count 2024-02-01 02:45:09 -08:00
Enrico Ros 051a05435e Draw: Vector indicator 2024-02-01 02:44:15 -08:00
Enrico Ros cb367596d1 Draw: improve layout 2024-02-01 02:15:42 -08:00
Enrico Ros 37de238f92 Workspace: add placeholder 2024-02-01 01:09:25 -08:00
Enrico Ros b977c0e31c Call: recolor PTT 2024-02-01 01:06:56 -08:00
Enrico Ros f58c4ec8d7 Call: use <ScrollToBottom/> 2024-02-01 01:04:50 -08:00
Enrico Ros 48b0815363 Hamburger: animate click 2024-02-01 00:22:28 -08:00
Enrico Ros 4f15c9f749 Roll packages 2024-02-01 00:04:38 -08:00
Enrico Ros 7dd5175063 Merge branch 'main-stable' 2024-01-30 14:48:52 -08:00
Enrico Ros cb9c6739cb Avoid 404s on this asset 2024-01-30 14:48:40 -08:00
Enrico Ros e541430891 Roll packages 2024-01-30 02:53:59 -08:00
Enrico Ros 60057716ae LLMOptions: fix corners 2024-01-29 18:03:17 -08:00
Enrico Ros f684442cc0 Update text description 2024-01-29 17:27:02 -08:00
Enrico Ros d4246d305e Draw: Commit to v1.13 2024-01-29 17:21:40 -08:00
Enrico Ros d13fafb2da Azure: improve model naming for deployments named exactly after OpenAI models. 2024-01-29 17:10:35 -08:00
Enrico Ros 4c79b95ddc Merge remote-tracking branch 'opensource/main-stable'
# Conflicts:
#	package-lock.json
2024-01-26 05:00:38 -08:00
Enrico Ros 720945f903 Maintainers template update 2024-01-26 04:41:41 -08:00
Enrico Ros 7ee8f218f6 Maintainers command update 2024-01-26 04:36:24 -08:00
Enrico Ros 72f9e01e60 Merge branch 'release-1.12.0' 2024-01-26 04:35:39 -08:00
Enrico Ros b4bae3ba20 1.12.0: Changelog 2024-01-26 04:35:23 -08:00
Enrico Ros 7c67dbd1f2 1.12.0: Readme Video 2024-01-26 04:33:01 -08:00
Enrico Ros ac8da8dfbf 1.12.0: Readme Update 2024-01-26 04:11:06 -08:00
Enrico Ros 1d778a699a Release Template update 2024-01-26 04:10:45 -08:00
Enrico Ros 0ac3033320 1.12.0: news.data.tsx 2024-01-26 04:10:33 -08:00
Enrico Ros c65aa99f9e 1.12.0: Version 2024-01-26 02:32:42 -08:00
Enrico Ros b22d54254a Nav: move back from the bottom to the menu 2024-01-26 02:14:00 -08:00
Enrico Ros 3eeb4aa157 Rounder numbers 2024-01-26 02:13:40 -08:00
Enrico Ros fac237638f Focus Mode: disable 2024-01-26 02:05:00 -08:00
Enrico Ros 7b617c5d03 Lints 2024-01-26 01:47:53 -08:00
Enrico Ros 3a579f3468 Roll deps 2024-01-26 01:47:41 -08:00
Enrico Ros 2bf407a989 Ollama: update available upstream models
Can't wait for https://github.com/ollama/ollama/issues/1473 enough.
2024-01-26 01:43:09 -08:00
Enrico Ros 18a16294bc Ollama: extract contextWindow from num_ctx. Closes #309
Note that from testing, only yarn-mistral has a number set that's not 4096,
while some models don't have parameters, don't have a 'num_ctx' value to parse
within, or have it set to 4096.
2024-01-26 01:04:55 -08:00
Enrico Ros db1346fe3e Ollama: extract Zod parsers 2024-01-26 00:01:35 -08:00
Enrico Ros 2b3477feb0 Drawing: disable by default, add option, and disable that too 2024-01-25 23:52:19 -08:00
Enrico Ros b7bc715b36 OpenAI models: 1106 visible 2024-01-25 17:18:54 -08:00
Enrico Ros bc237dee1c OpenAI models: sync with today's released/announced models 2024-01-25 17:12:12 -08:00
Enrico Ros 6131556bab OpenAI models: improve sorting 2024-01-25 17:11:19 -08:00
Enrico Ros 3d42bc51f3 Roll it 2024-01-25 14:49:13 -08:00
Enrico Ros 3f3f3c67bf Draw Mode: Begin some wiring 2024-01-25 04:31:26 -08:00
Enrico Ros eeaa87bde3 Draw Mode: Improve Layout 2024-01-25 03:58:52 -08:00
Enrico Ros f854f0182f Draw Mode: Extract ProviderConfigure 2024-01-25 03:52:37 -08:00
Enrico Ros 302e327d2d Remove hook dependency 2024-01-25 02:54:01 -08:00
Enrico Ros 2d18a81654 Draw Mode: Extract Prompt Designer 2024-01-25 02:40:52 -08:00
Enrico Ros 71a97e1c4e Draw Mode: Prompt Designer layout 2024-01-25 00:27:57 -08:00
Enrico Ros 542b47ba78 Draw Mode: propagate isMobile 2024-01-25 00:07:13 -08:00
Enrico Ros d27f269abc Draw Mode: ideas 2024-01-25 00:06:59 -08:00
Enrico Ros b0484e24af Style: adjust dividers 2024-01-24 23:06:18 -08:00
Enrico Ros fa8c4a30d8 Export: remove from the Chat Menu 2024-01-24 23:00:58 -08:00
Enrico Ros f6163b5a22 Export: to Drawer Item 2024-01-24 22:56:38 -08:00
Enrico Ros 8f945f11e7 Sharing: change a couple of strings 2024-01-24 21:53:07 -08:00
Enrico Ros fa7a7bdf1d Edit: use accessible Icons, no Text 2024-01-24 21:45:42 -08:00
Enrico Ros fe7a2caf2c Mobile Nav: land. 2024-01-24 16:45:19 -08:00
Enrico Ros 6ae11d07eb Nav: improvements 2024-01-24 15:49:33 -08:00
Enrico Ros 58896a7052 Export improvements and Export to Markdown, Closes #337 2024-01-24 15:26:00 -08:00
Enrico Ros 1f83210792 Ollama: track upstream ticket - https://github.com/ollama/ollama/issues/1473 2024-01-24 14:47:41 -08:00
Enrico Ros 0a4a452bee Optimize GithubMarkdown dark/light 2024-01-24 06:31:54 -08:00
Enrico Ros 8063ee34b3 GithubMarkdown: update from upstream 2024-01-24 06:05:46 -08:00
Enrico Ros 72e2fa41aa Accessibility: finish with some good improvements, #358 2024-01-24 04:59:01 -08:00
Enrico Ros 1c3f8ba8ec Accessibility: improve social links (was 2 tabIndexes), #358 2024-01-24 04:38:03 -08:00
Enrico Ros e1802cb0f8 Misc UX cleanups 2024-01-24 04:32:41 -08:00
Enrico Ros afeab71da1 Actiles: shall support Mobile now 2024-01-24 04:06:00 -08:00
Enrico Ros 8d492702f2 Cleanups 2024-01-24 03:20:49 -08:00
Enrico Ros 64e8cfcb03 Accessibility: Call: fix buttons, #358 2024-01-24 02:55:48 -08:00
Enrico Ros 2167d0ef1e Accessibility: improve HTML elements for manage models, preferences, closing the dialogs, model list, model selection, model unhide, #358 2024-01-24 02:33:16 -08:00
Enrico Ros 977b14494b Lint 2024-01-24 01:23:10 -08:00
Enrico Ros 3b408c8173 Optimization: sx stability 2024-01-24 00:49:55 -08:00
Enrico Ros 9547b25835 Debouncer: min 2 chars (no single-letter searches, as there are too many positives) 2024-01-24 00:49:45 -08:00
Enrico Ros 9c53557183 Chat Item Folder: fix button size 2024-01-24 00:29:45 -08:00
Enrico Ros 3cc8d48b75 Roll packages 2024-01-24 00:22:45 -08:00
Enrico Ros 71dbc653a9 Accessibility: use nav/aside/main/header. #358 2024-01-24 00:19:18 -08:00
Enrico Ros f1e8bf3d1f Wording 2024-01-23 22:16:58 -08:00
Enrico Ros 0c8dd4a4d9 Large Optimizations 2024-01-23 22:16:43 -08:00
Enrico Ros 4911f39793 Optimizations (more) 2024-01-23 21:40:44 -08:00
Enrico Ros daaf33a69e Optimizations 2024-01-23 21:14:41 -08:00
Enrico Ros 8b04d38ce3 Folder selection: remove fading - Closes #360 2024-01-23 20:45:22 -08:00
Enrico Ros 4a35701def Folders: change Folder from the Drawer Chat Item as well, #321 2024-01-23 20:36:54 -08:00
Enrico Ros 8800cae62f Remove unused setting 2024-01-23 18:56:45 -08:00
Enrico Ros aebf7b99f4 Folders: drawer cleanup code 2024-01-23 18:54:19 -08:00
Enrico Ros a9ea4070ff Folders: uniform 'active' name for folders and chats 2024-01-23 16:49:23 -08:00
Enrico Ros 3fb8d91ab1 Folders: minutia 2024-01-23 16:31:48 -08:00
Enrico Ros a9943e26af Folders: de-confuse variable 2024-01-23 16:23:24 -08:00
Enrico Ros 514ecedf1c Prompts store: improve deletion 2024-01-23 16:00:53 -08:00
Enrico Ros 74a277a6f3 Prompts store: stay (significantly)below localStorage quota 2024-01-23 15:56:03 -08:00
Enrico Ros b14cd47a7b Tiktoken [x4, port]: successfully defer the library load, with large interactivity improvements
The rationals that TTFP would be a more important metric than
awaiting the 1.2MB dependency at every page load

(cherry picked from commit 3a8195a02b)
(cherry picked from commit 808077bc2b)
(cherry picked from commit 76f6c7917c)
(cherry picked from commit fc1fc91845)
2024-01-23 05:03:20 -08:00
Enrico Ros c1a29d76d5 Roll 'tiktoken' (fka. @dqbd/tiktoken) 2024-01-23 05:01:17 -08:00
Enrico Ros 3a8195a02b TikToken delayed loading: debug removal 2024-01-23 04:56:27 -08:00
Enrico Ros f70b0474ad Disabled debug(prod) code 2024-01-23 04:53:15 -08:00
Enrico Ros 808077bc2b Tiktoken: successfully defer the library load, with large interactivity improvements 2024-01-23 04:52:44 -08:00
Enrico Ros 76f6c7917c Tiktoken: do not warm-up the encoding for the current model
The rationals that TTFP would be a more important metric than
awaiting the 1.2MB dependency at every page load
2024-01-23 03:04:47 -08:00
Enrico Ros fc1fc91845 Tiktoken: make sharing of the 100k explicit 2024-01-23 03:00:18 -08:00
Enrico Ros 72d5a8f5f0 Roll 'tiktoken' (fka. @dqbd/tiktoken) 2024-01-23 03:00:04 -08:00
Enrico Ros 53226da794 Roll Packages 2024-01-23 02:47:57 -08:00
Enrico Ros 638bd1e780 Roll NextJS 2024-01-23 02:10:59 -08:00
Enrico Ros 046d193af8 Chat Drawer: rename 2024-01-23 01:48:32 -08:00
Enrico Ros ff0cc09505 DALL·E: salvage successful requests, and throw a more descriptive errors. #353 2024-01-23 01:39:02 -08:00
Enrico Ros b52468dd54 Link: fix size 2024-01-22 19:03:10 -08:00
Enrico Ros 76cadaed18 Docs: fix npm start vs next start (thanks @motocycle) 2024-01-22 18:54:32 -08:00
Enrico Ros 2e68172fa9 Nav: bring back hide on mobile 2024-01-22 18:54:00 -08:00
Enrico Ros 4bbed2adb1 Link: option to show the deletion keys in the drawer 2024-01-22 18:12:52 -08:00
Enrico Ros fb4a62be16 Merge branch 'issue-356'. Fixes #356 2024-01-22 18:05:12 -08:00
Enrico Ros 5da3a887c4 Link: enable delete. Fixes #356 2024-01-22 18:04:13 -08:00
Enrico Ros 2df49977c2 Link: improve deletion 2024-01-22 18:03:40 -08:00
Enrico Ros d275ee0f7d LinkChat: improve appearance 2024-01-22 17:11:18 -08:00
Enrico Ros 19ec67bf3c Links: more uniform route names 2024-01-22 17:10:22 -08:00
Enrico Ros 9dc8aaa9aa Links: renames 2024-01-22 17:09:59 -08:00
Enrico Ros 15cfef0f8b Links: land to former conversations #356 2024-01-22 16:03:59 -08:00
Enrico Ros 695af02cee Nav: functions for nav/icon visibility #356 2024-01-22 15:53:42 -08:00
Enrico Ros 1ed86b6ebc Disable Nav items debug code 2024-01-22 15:41:25 -08:00
Enrico Ros e18ac02af9 Links: update text 2024-01-22 14:44:57 -08:00
Enrico Ros a4d89c9e2c Links: Import as New/Over #356 2024-01-22 14:43:28 -08:00
Enrico Ros 911c46ebe2 Bring back debug conv IDs. 2024-01-22 14:42:30 -08:00
Enrico Ros f0073133c3 Trade: update deletion key #356 2024-01-22 14:35:09 -08:00
Enrico Ros db3a435027 Trade: cleanups 2024-01-22 14:00:44 -08:00
Enrico Ros a94f2c6df3 Trade: move the store 2024-01-22 13:50:26 -08:00
Enrico Ros 0b7eaf69ba Trade: extract Publish (paste.gg) 2024-01-22 13:47:52 -08:00
Enrico Ros 326f49bafc Trade: extract ChatLinkManager 2024-01-22 13:32:58 -08:00
Enrico Ros 6195c8954d Draw: improve layout 2024-01-21 22:09:02 -08:00
Enrico Ros 1586377ead Draw: (used) selector 2024-01-21 20:12:45 -08:00
Enrico Ros 97b1f15121 Draw: TextToImage: layout 2024-01-21 19:23:34 -08:00
Enrico Ros 6d185119ac useToggleableBoolean: remember last state in-mem 2024-01-21 19:07:34 -08:00
Enrico Ros 296eff7278 Dall-E: disable advanced by default 2024-01-21 18:56:13 -08:00
Enrico Ros 84b1825895 Draw: Service Configuration 2024-01-21 18:52:27 -08:00
Enrico Ros a69c067530 Draw: select Service Provider 2024-01-21 18:35:43 -08:00
Enrico Ros 0043b39293 Preferences: remove hardcodings 2024-01-21 18:03:57 -08:00
Enrico Ros 8123c237e3 Draw: begin breakdown 2024-01-21 17:47:00 -08:00
Enrico Ros 5a0fb1bb63 Draw: t2i settings 2024-01-20 20:05:50 -08:00
Enrico Ros a507d53d34 Draw: keep the placeholder 2024-01-20 20:03:09 -08:00
Enrico Ros 60cbcdaedb Draw: header 2024-01-20 20:02:30 -08:00
Enrico Ros 96b4f502f1 Draw: placeholder. 2024-01-20 19:47:24 -08:00
Enrico Ros 846b3cddaf Fix possible exception when fetching from ElevenLabs 2024-01-20 19:41:28 -08:00
Enrico Ros 1b66dce9f0 Desktop Layout: show App separator + bits 2024-01-20 19:37:02 -08:00
Enrico Ros c7952ae974 Call: hide hidden personas (fixed) 2024-01-20 19:11:09 -08:00
Enrico Ros ed2284716b Call: hide hidden personas 2024-01-20 19:09:21 -08:00
Enrico Ros d64ed69371 Change hint text 2024-01-20 19:05:55 -08:00
Enrico Ros e73bf2ddec Call: persist all 3 settings 2024-01-20 18:59:12 -08:00
Enrico Ros 19609e5ccd Call: simplify~1 2024-01-20 18:41:38 -08:00
Enrico Ros 3adc2f4654 Call: allow for Gray UI, and cleanup CSS 2024-01-20 18:25:15 -08:00
Enrico Ros 2b95b6ace1 Calls: customizable Contacts page 2024-01-20 17:35:23 -08:00
Enrico Ros 5720de1224 Page Bar: Custom Title and big-AGI (=back) button 2024-01-20 17:21:49 -08:00
Enrico Ros 1b110f5a38 Remove Shared Page Drawer 2024-01-20 17:19:16 -08:00
Enrico Ros 0785961581 Call: add support link 2024-01-20 17:17:38 -08:00
Enrico Ros f1cc92727c Call: fix looping on missing Conversation 2024-01-19 22:40:39 -08:00
Enrico Ros b36197ffad Call: fix double-message on error 2024-01-19 22:35:12 -08:00
Enrico Ros eae3d78911 Call: take out of Beta
Also remove the option to call from the Dropdown menu, which
was an initial workaround anyway.
2024-01-19 22:04:41 -08:00
Enrico Ros 12a93fdcb7 Update Prodia Default 2024-01-19 21:55:07 -08:00
Enrico Ros c98ab8cb9d Call: do not display "Re:" if no call 2024-01-19 21:50:13 -08:00
Enrico Ros 8619a9ca1d Call App: style++. 2024-01-19 20:19:47 -08:00
Enrico Ros 2b182a4209 When the bar is shown, show the menu (for Dark/Light mode) 2024-01-19 15:19:32 -08:00
Enrico Ros ddc7d571d2 Call App: Done. Beauty & Function. #175. Closes #354. 2024-01-19 15:19:07 -08:00
Enrico Ros 3de693e5e3 Enabled the Call app 2024-01-18 18:41:01 -08:00
Enrico Ros 770fbdef72 Bits 2024-01-18 18:40:28 -08:00
Enrico Ros 80d9f458bb Style: fill active icons 2024-01-18 17:14:01 -08:00
Enrico Ros 52f91dd328 Style: fill active icons 2024-01-18 17:13:35 -08:00
Enrico Ros 22550f7efb Style: update icons 2024-01-18 16:59:54 -08:00
Enrico Ros f811b59919 Style: bring back the former behavior of the Links 2024-01-18 16:51:46 -08:00
Enrico Ros d2344e5010 Style the Desktop Nav Panel 2024-01-18 16:33:26 -08:00
Enrico Ros 6fee9a6238 Call: improve styling, honor dark/bright mode - #175 2024-01-17 16:58:53 -08:00
Enrico Ros 08730002a4 Call: some top-level structure 2024-01-17 16:01:16 -08:00
Enrico Ros 20adb796c0 Call: update strings 2024-01-17 15:36:13 -08:00
Enrico Ros 0e7cbfe0e4 Improve Mobile Insert - save space - looks better. Fixes #315 2024-01-17 14:48:21 -08:00
Enrico Ros 46ef5d9b45 Update roadmap request 2024-01-17 14:15:19 -08:00
Enrico Ros f249b39db5 Drawer: radius on mobile, and optimize 2024-01-17 14:01:39 -08:00
Enrico Ros 280bb2e424 Attachments: when pasted from cliboard (no ref), add a "<!DOCTYPE html>" to render as HTML block - Fixes #348. 2024-01-17 13:15:42 -08:00
Enrico Ros 8c206aedb9 Simplify Structural BgColors 2024-01-17 13:09:34 -08:00
Enrico Ros d74b7df41d Select All -> Select #. 2024-01-16 05:12:33 -08:00
Enrico Ros 571a04cf6c Rename/Auto-Name conversations and New UI Conversation Item. Fixes #222, Fixes #297. 2024-01-16 04:49:44 -08:00
Enrico Ros 216dae9423 InlineTextarea: support inverted soft colors 2024-01-16 04:49:02 -08:00
Enrico Ros ef09d50715 AutoTitle: make it force-able 2024-01-16 04:11:43 -08:00
Enrico Ros 1e851bbb6c Style: more consistent gaps and paddings across settings 2024-01-16 02:10:46 -08:00
Enrico Ros 3c63593141 Models list: stop the never ending scrolling by absorbing it on the LLMs list 2024-01-16 01:41:35 -08:00
Enrico Ros 6ef32e52ba Mic Continuation: stop if error. Also reset the error state on external manual stop. Fixes #302 2024-01-16 01:12:32 -08:00
Enrico Ros 682c168372 Attachments: further improve heuristics, mainly for powerpoint. #286 2024-01-16 01:02:28 -08:00
Enrico Ros 48f039517d Attachments: show when attaching a rich table or rich html. #286 2024-01-16 00:48:21 -08:00
Enrico Ros 7ebeea3550 Attachments: Excel: paste as Table/HTML/Text rather than image. Fixes #286 2024-01-16 00:38:17 -08:00
Enrico Ros a7a234ecca Attachments: debug option 2024-01-16 00:11:02 -08:00
Enrico Ros a237e53580 Roll pdfjs 2024-01-15 23:51:44 -08:00
Enrico Ros 584544d037 Roll packages 2024-01-15 23:37:18 -08:00
Enrico Ros a601dfa4cf VercelSpeedInsights: 10% sampling rate, to reduce Speed Insights volume 2024-01-15 23:36:57 -08:00
Enrico Ros dbee0d7b87 Update README.md 2024-01-15 22:30:07 -08:00
Enrico Ros ff4857b9ac Merge branch 'release-1.11.0' 2024-01-15 22:11:12 -08:00
Enrico Ros 5b557705e7 1.11.0: Readme and Changelog 2024-01-15 22:10:39 -08:00
Enrico Ros cd70c4dd84 1.11.0: news.data.tsx 2024-01-15 21:39:14 -08:00
Enrico Ros 9eb2ef05de 1.11.0: Version 2024-01-15 17:07:32 -08:00
Enrico Ros 8fae15d343 Together AI: improve icon 2024-01-15 16:42:13 -08:00
Enrico Ros bca5a1ac78 Update vendors count 2024-01-15 16:02:03 -08:00
Enrico Ros d899fb7e3b Persona Creator Drawer: selection mode 2024-01-15 16:00:29 -08:00
Enrico Ros 0f05b70e3b Bits 2024-01-15 15:01:22 -08:00
Enrico Ros 7b121a3a95 Together AI: implement free-tier rate limiting 2024-01-15 14:49:45 -08:00
Enrico Ros d4e414f99c Together AI: add popular models (with context window sizes) 2024-01-15 14:23:43 -08:00
Enrico Ros a7f322ef38 Together AI Vendor support 2024-01-15 14:10:21 -08:00
Enrico Ros d4494bf2e0 OpenAI transports: do not include n=1 in the payload 2024-01-11 09:35:09 -08:00
Enrico Ros 78cf74e3f2 Persona Creator: Drawer/Drawer Items - storage OK. Closes #301 2024-01-10 02:57:03 -08:00
Enrico Ros cfaed03603 PageDrawerList: add onClick for list callbacks 2024-01-10 02:52:29 -08:00
Enrico Ros a8e3183733 Persona Creator: store 2024-01-10 01:48:34 -08:00
Enrico Ros 9395db0fd5 Persona Creator: move Creator stuff to ./creator 2024-01-10 00:26:03 -08:00
Enrico Ros 8c75061178 Move useFormEditTextArray 2024-01-10 00:23:24 -08:00
Enrico Ros de0cdded87 Persona Creator: move the YouTube module 2024-01-10 00:23:11 -08:00
Enrico Ros d225541da2 bits 2024-01-09 23:13:06 -08:00
Enrico Ros 7a0008de5a Move useLLMChain 2024-01-09 22:57:46 -08:00
Enrico Ros 0bdd817d6d Persona Creator: bits 2024-01-09 21:48:19 -08:00
Enrico Ros d606975584 Persona Creator: improve LLM selection 2024-01-09 21:22:05 -08:00
Enrico Ros af56c2c1af GoodDropdown -> PageBarDropdown 2024-01-09 20:14:23 -08:00
Enrico Ros 73de7df0fb Mobile Nav: add Personas 2024-01-09 19:56:07 -08:00
Enrico Ros 3ca80d6a6e This is much better 2024-01-09 19:43:36 -08:00
Enrico Ros eb9e5362fe Begin reducing LLMs dependencies 2024-01-09 19:42:07 -08:00
Enrico Ros 45d1ca7437 PersonaCreator: debug (find issues) 2024-01-09 15:22:01 -08:00
Enrico Ros e492ccfb04 Improve the useLLMChain hook 2024-01-09 15:20:39 -08:00
Enrico Ros d01b6acd51 Persona Creation: enable user prompts, fixes #336 2024-01-09 04:35:32 -08:00
Enrico Ros eec81d5d73 Persona Creation: improve layout 2024-01-09 03:28:08 -08:00
Enrico Ros 03423ce58c Persona Creation: improve progress 2024-01-09 02:36:55 -08:00
Enrico Ros e2e7ea972d Persona Creation: use cancelable streaming, - Fixes #316, #328. 2024-01-09 02:31:04 -08:00
Enrico Ros 91b770d2c8 Persona Creation: extract the Tabs 2024-01-09 00:56:48 -08:00
Enrico Ros 79500e6d8b Persona Creation: extract YouTube Transcript downloader 2024-01-09 00:30:18 -08:00
Enrico Ros 4ede66cf2b Improve OpenAI API Endpoint Tooltip #323 2024-01-08 21:00:59 -08:00
Enrico Ros 40bff32442 Allow up to 5 OpenAI Endpoints. Fixes #323 2024-01-08 20:49:30 -08:00
Enrico Ros 3fc8e8efa0 LLM Source re-numbering, #323 2024-01-08 20:30:45 -08:00
Enrico Ros 12ea5f218d LLM auto-selection: ignore hidden, unless there's nothing else 2024-01-08 19:47:04 -08:00
Enrico Ros d47c0e45af AutoTitle: fix exception when an immediate call to chat-gen fails 2024-01-08 19:44:06 -08:00
Enrico Ros 298d0201d2 (disabled) Folder reveal animation 2024-01-08 19:24:53 -08:00
Enrico Ros a6bde2377e Reduce MenuList usage 2024-01-08 19:24:53 -08:00
Enrico Ros 76778c5ab7 Action Tiles framework - for commands and attachments 2024-01-08 00:42:54 -08:00
Enrico Ros 11565f5ac8 Commands: add arguments 2024-01-08 00:42:49 -08:00
Enrico Ros 6c5131996b Drawer width: less than half a percent skinnier 2024-01-07 22:14:33 -08:00
Enrico Ros 9b4301cd90 Export: undo the flip 2024-01-07 22:05:46 -08:00
Enrico Ros c73bbaf0d4 Chat Drawer Item: frequency as bar basis, and move the frequency count at the env (stable items) 2024-01-07 22:01:37 -08:00
Enrico Ros 163257e052 Bits 2024-01-07 21:49:30 -08:00
Enrico Ros cf689ca9a9 Chat Titles fixes 2024-01-07 21:41:15 -08:00
Enrico Ros 4a65389b71 Mobile Chat Drawer: do not close when clicking the active item 2024-01-07 21:22:59 -08:00
Enrico Ros 5de7762238 Fix a layout bug introduced moving away from MenuList to List 2024-01-07 21:10:58 -08:00
Enrico Ros 06655ced46 Title Edit - cancellation 2024-01-07 13:39:35 -08:00
Enrico Ros 60a775b869 Fix keyboard de-focus on Search chats.
Move away from MenuList to List - as the Menu does some focus stealing behind the scenes.
Will minimize or remove MenuList usage going forward.
2024-01-07 13:29:45 -08:00
Enrico Ros 5a3645bd43 Merge pull request #330 from joriskalz/dev-fixes
Refactored DebounceInput as external component, added clear input functionality with keyboard navigation support
2024-01-07 12:37:05 -08:00
Joris Kalz 54d37e663a Create component and add clear icon for search input 2024-01-07 12:26:42 +01:00
Enrico Ros f4c056fa9f Update README.md 2024-01-06 10:18:37 -08:00
Enrico Ros 8f53fa7407 Update README.md 2024-01-06 10:17:22 -08:00
Enrico Ros 2f9a4ea00f Merge pull request #329 from joriskalz/main
Enhanced Search: Frequency Ranking and In-Message Querying #324
2024-01-06 03:33:07 -08:00
Joris Kalz ee7dae827e Merge branch 'enricoros:main' into main 2024-01-06 12:23:24 +01:00
Joris Kalz 6fe94e344a Show number of results 2024-01-06 12:20:42 +01:00
Joris Kalz 3376867966 Debounced Input field 300ms 2024-01-06 12:05:31 +01:00
Enrico Ros 4a8a2b9c5d First user experience - highlight the need to configure models 2024-01-06 02:57:05 -08:00
Joris Kalz 7f84160a62 Enable Search 2024-01-06 11:55:26 +01:00
Enrico Ros fb5b349866 News: improve 2024-01-06 02:22:10 -08:00
Enrico Ros f5c7b96ff6 Chat Drawer: Import and Export 2024-01-06 01:37:40 -08:00
Enrico Ros 7c430cc5c8 Export: improve dialog 2024-01-06 01:29:45 -08:00
Enrico Ros 8c7d069189 Style: Chat List: soft instead of solid - for now 2024-01-06 00:57:44 -08:00
Enrico Ros f50d040d8a Update maintainers/release 2024-01-06 00:39:54 -08:00
Enrico Ros aa10f87c7d Update maintainers/release 2024-01-06 00:39:06 -08:00
Enrico Ros 4e96a5b5e5 Merge branch 'release-1.10.0' 2024-01-05 23:08:59 -08:00
Enrico Ros 329456f287 1.10.0: README/Changelog 2024-01-05 23:08:30 -08:00
Enrico Ros 6f8368d7cb 1.10.0: news.data.tsx 2024-01-05 23:00:43 -08:00
Enrico Ros 9c2b0cb7ca 1.10.0: version 2024-01-05 22:18:16 -08:00
Enrico Ros 1e15c4c4d1 Auto-Scrolling to the bottom on /link/chat. Fixes #319 2024-01-05 22:00:42 -08:00
Enrico Ros 9f209526a0 Update bug template 2024-01-05 21:50:42 -08:00
Enrico Ros 60ab9bd239 Update bug template 2024-01-05 21:50:08 -08:00
Enrico Ros 70e51b2e71 Trying out the Vercel Speed Insights functionality on Vercel deployments. +3kb 2024-01-05 21:47:36 -08:00
Enrico Ros 2d6edde12c Ani: Revert Bits 2024-01-04 11:48:55 -08:00
Enrico Ros d2fb0c2425 Ani: Bits 2024-01-04 11:47:48 -08:00
Enrico Ros 122bbf0034 LLMs: make maxTokens optional 2024-01-04 03:38:40 -08:00
Enrico Ros e79449b38c OpenAI Transport: make maxTokens optional 2024-01-04 03:38:22 -08:00
Enrico Ros fcad6495e1 Anthropic: relax max tokens 2024-01-04 03:38:06 -08:00
Enrico Ros 330d35a24c LM Studio: improve model name 2024-01-04 03:36:38 -08:00
Enrico Ros a8ec58c732 Fix mouse jumpiness on avatar icon and improve spacing 2024-01-04 02:14:37 -08:00
Enrico Ros 8054c8b328 Cleanup 2024-01-04 02:07:40 -08:00
Enrico Ros 7d6f2317e4 Clenup mobile Nav, and social links 2024-01-04 02:07:25 -08:00
Enrico Ros 10dd83bb2b Mobile Nav: make it dynamic 2024-01-04 01:43:04 -08:00
Enrico Ros 7bf285f26a Bits 2024-01-04 01:07:37 -08:00
Enrico Ros fde7a8cd9b Style: improve dark color scheme, with consistent shading 2024-01-04 01:07:32 -08:00
Enrico Ros 49ae5abba5 Dark theme improvement. Much better bars. 2024-01-04 00:53:18 -08:00
Enrico Ros f50ae4e7e2 Persona Creator: slight cleanup 2024-01-04 00:34:22 -08:00
Enrico Ros 99ff5cd7ad Persona Creator: render as markdown 2024-01-03 23:57:22 -08:00
Enrico Ros f80facb191 LLMs: support context window/max tokens not provided, and handle 'fallbacks' more explicitly 2024-01-03 23:38:01 -08:00
Enrico Ros ea8d2fff3e Fix parsing of OpenAI tokens message 2024-01-03 22:55:11 -08:00
Enrico Ros e3f1a5c54d LM Studio: actually, don't replace the hyphen 2024-01-03 22:22:57 -08:00
Enrico Ros fdafc1207b Re-rank local model providers 2024-01-03 22:21:43 -08:00
Enrico Ros 5d3971c21f Support LM Studio 2024-01-03 22:21:31 -08:00
Enrico Ros f8a4002a41 Fix Folder options on Mobile, #321 2024-01-03 16:16:39 -08:00
Enrico Ros 38a3eeef21 Folders: Toggle support.
This makes sure the folders can be disabled with a single button press in
case there are unexpected issues. Will get user testing and feedback.
Also very important on mobile, where the "select folder" UX
component makes the toolbar wrap.
2024-01-03 15:32:40 -08:00
Enrico Ros bf54807fb2 New UI: Improve the 'new title' 2024-01-03 15:15:23 -08:00
Enrico Ros 1aaabec28f New UI: Improve the 'new chat' button 2024-01-03 15:15:14 -08:00
Enrico Ros 8ec3927f02 New UI: Drawer: extract the PageDrawerHeader 2024-01-03 15:10:22 -08:00
Enrico Ros 73f201b8ac Hand-optimize the Chat items, for faster display and avoid refresh-while-type 2024-01-03 06:38:05 -08:00
Enrico Ros 0b61c9a49e Folders: use our Closeable menus instead of the Dropdown 2024-01-03 05:31:01 -08:00
Enrico Ros ee82911d8f Merge branch 'joriskalz-folders'
# Conflicts:
#	src/common/layout/optima/PageDrawer.tsx
2024-01-03 05:08:42 -08:00
Enrico Ros 89fa3fe633 New UI: Improve Drawer Names 2024-01-03 05:03:03 -08:00
Enrico Ros da56db7502 New UI: show the back arrow on desktop/no-nav 2024-01-03 05:03:01 -08:00
Enrico Ros 1d0f99a9a5 New UI: fix bug with desktop h-layout 2024-01-03 05:02:58 -08:00
Enrico Ros 8254443d29 New UI: Popups: denser 'dense' looks 2024-01-03 05:02:54 -08:00
Enrico Ros e1d6536102 New UI: Nav: support /link/chat 2024-01-03 05:02:50 -08:00
Enrico Ros c9fbbc1ab1 Folders: Complete review 2024-01-03 04:59:22 -08:00
Enrico Ros ae2e9b8f56 Folders: AppChat - review, simplify 2024-01-03 04:13:52 -08:00
Enrico Ros 64ca896ea7 Folders: store: cleanup, looks good 2024-01-03 03:15:04 -08:00
Enrico Ros 9bed685fe2 Folders: dropdown: ability to remove a folder association 2024-01-03 03:14:29 -08:00
Enrico Ros 9432084342 Folders: pre-select a folder color 2024-01-03 02:42:19 -08:00
Enrico Ros 0b7ffd16ab Folders: Reuse InlineTextArea both in New Folder & Edit Title. 2024-01-03 02:18:28 -08:00
Enrico Ros 3437888bf4 Folders: Style More: AddFolderButton 2024-01-03 02:14:41 -08:00
Enrico Ros 9b02be8861 Folders: Style: AddFolderButton and ChatFolderList 2024-01-03 01:40:28 -08:00
Enrico Ros 953d8434c3 Folders: Style: auto-size 2024-01-03 00:19:19 -08:00
Enrico Ros f9484ee3e9 Folders: Style: re-z-order 2024-01-03 00:11:34 -08:00
Enrico Ros 4a3956d743 Folders: Style: transfer shadow 2024-01-03 00:09:50 -08:00
Enrico Ros 785139e7bc New UI: Improve Drawer Names 2024-01-03 00:06:40 -08:00
Enrico Ros d45fbff28d New UI: show the back arrow on desktop/no-nav 2024-01-03 00:06:17 -08:00
Enrico Ros fce6ecaf5f New UI: fix bug with desktop h-layout 2024-01-02 23:58:34 -08:00
Enrico Ros 847d199dd8 New UI: Popups: denser 'dense' looks 2024-01-02 23:53:03 -08:00
Enrico Ros 274525a727 New UI: Nav: support /link/chat 2024-01-02 23:52:25 -08:00
Enrico Ros 4d807ecf5c New UI: transfer App Drawer lists into the Plugged 2024-01-02 23:49:21 -08:00
Enrico Ros 37a25f0117 Preferences 2024-01-02 18:14:18 -08:00
Enrico Ros 7d5ab95c20 Merge branch 'folders' of https://github.com/joriskalz/big-AGI-dev into joriskalz-folders 2024-01-02 18:11:10 -08:00
Enrico Ros 7fe8dd776f ScrollToBottom: fix edge case of the edge case fix 2024-01-02 18:09:58 -08:00
Enrico Ros 0a85d8d104 Desktop: back to 5 rows, we have the space 2024-01-02 17:57:19 -08:00
Enrico Ros cfd563b200 Apps: override fullWidth (for Call only, for now) 2024-01-02 17:56:32 -08:00
Enrico Ros 311a8d0ba0 Use Anybburger menu 2024-01-02 17:56:14 -08:00
Enrico Ros 06cd386c6e Only close the drawer when clicking items within a mobile drawer 2024-01-02 17:45:25 -08:00
Enrico Ros 2632133ba4 New UI: Buttery-smooth transitions 2024-01-02 17:34:31 -08:00
Enrico Ros 1fe43cdc2e Theme: centralized zIndex 2024-01-02 17:12:49 -08:00
Enrico Ros e76939fb5d Root style: change some var names 2024-01-02 17:11:43 -08:00
Enrico Ros 5f4250e3d2 Squircle: fix 2024-01-02 17:10:55 -08:00
Joris Kalz 5653044b1e Fancy colors 2024-01-02 00:33:08 +01:00
Joris Kalz d4da34561d Removed unused items 2024-01-02 00:27:11 +01:00
Joris Kalz fa25e830d5 Removed Folder Title 2024-01-02 00:25:13 +01:00
Joris Kalz c90139923c Add folder selector 2024-01-02 00:23:32 +01:00
Joris Kalz fa5007cb3b Assign new conversation to selected folder 2024-01-02 00:12:38 +01:00
Joris Kalz b979e1313c Enable deletion of all items in a folder 2024-01-02 00:05:00 +01:00
Joris Kalz 1f1bf65c14 Filter by selected folder 2024-01-01 23:50:18 +01:00
Joris Kalz 2bc6a15256 Enable Selection of folders 2024-01-01 23:39:54 +01:00
Joris Kalz dbcdbaa893 Display Folders 2024-01-01 23:28:31 +01:00
Joris Kalz d0ac1d8e1a Refactor "Add Folder" button into a separate file 2024-01-01 23:09:46 +01:00
Joris Kalz 3929e501d8 Add Folder Button 2024-01-01 23:02:47 +01:00
Joris Kalz fa3ae7b821 UI, adding FolderList 2024-01-01 22:49:42 +01:00
Joris Kalz 79052f988c add store to persist folders in local storage 2024-01-01 22:33:47 +01:00
Enrico Ros 18e6e235f3 Merge New UI - details inside:
- OptimaLayout: new responsive UI framework, with nav and drawer for desktop and mobile
 - Nav: new top-level navigation framework (will replace 'routes' going forward)
 - The new (App) Panel is more stable for UI operations (vs. the former Popup)
 - Improved looks on desktop, and uses Drawer on mobile
 - Missing bottom-Nav on mobile, to replace the PageMenu nav
 - Closes #298, #201.

Landing as-is on `main`, will fix smaller bits later.
2023-12-31 17:55:43 -08:00
Enrico Ros 388e897466 New UI: disable mobilenav 2023-12-31 17:43:17 -08:00
Enrico Ros e05a3bc3e9 New UI: bits 2023-12-31 17:33:59 -08:00
Enrico Ros 5bb832f83d New UI: Add Personas 2023-12-31 17:30:36 -08:00
Enrico Ros 43cb19df83 New UI: PageBar hide when not needed on desktop 2023-12-31 17:30:29 -08:00
Enrico Ros 1d770ce012 New UI: desktop nav button 2023-12-31 17:30:10 -08:00
Enrico Ros 550e3e0173 News: gradient 2023-12-31 17:30:01 -08:00
Enrico Ros 043a5f48e8 New UI: enable split branch toggle 2023-12-31 16:28:21 -08:00
Enrico Ros 0b69e0a9d1 New UI: revert show split branching 2023-12-31 16:27:50 -08:00
Enrico Ros 5d8d752693 New UI: fix desktop drawer 2023-12-31 16:26:06 -08:00
Enrico Ros e7067ed4fb New UI: Page Menu working best 2023-12-31 16:17:43 -08:00
Enrico Ros d181e27555 ScrollToBottom: restore 60 2023-12-31 16:17:42 -08:00
Enrico Ros 47d8b220a3 UI: Mobile: improve PageBar 2023-12-31 15:53:34 -08:00
Enrico Ros cc5e310174 UI: Fix layout/2 2023-12-31 15:53:17 -08:00
Enrico Ros 8006f578cd UI: Fix layout 2023-12-31 15:52:57 -08:00
Enrico Ros a303bf7224 Small layout fixes 2023-12-31 06:06:32 -08:00
Enrico Ros dc0ca6d5bc Nav: +News 2023-12-31 05:53:19 -08:00
Enrico Ros 2db3917c1c New UI Layout - #299
Full skeleton of the new 2.0 structure.
2023-12-31 05:53:05 -08:00
Enrico Ros 0c2ae290b0 New UI: uniform inverted bar 2023-12-31 03:53:17 -08:00
Enrico Ros 24dcfeb952 [Nav] #299 2023-12-31 03:51:59 -08:00
Enrico Ros acd7a24cff SquircleIcon: support inversion 2023-12-31 03:51:13 -08:00
Enrico Ros 88c29cf32c Composer: desktop: less gap between buttons 2023-12-31 03:50:54 -08:00
Enrico Ros 26f472b396 Try to use a second React Context for the Optima drawer, to optimize state changes 2023-12-31 03:46:34 -08:00
Enrico Ros 68c5e0b940 Move Optima Layout providers in optima layout 2023-12-31 03:45:58 -08:00
Enrico Ros 03fca40b74 Use a Focused mode on mobile 2023-12-31 03:45:16 -08:00
Enrico Ros 35aff7798e Rename to PreferencesTab 2023-12-31 03:43:44 -08:00
Enrico Ros 6a8cf08ef0 Nav: placeholder application 2023-12-31 01:25:28 -08:00
Enrico Ros 53a9f9acef Love magic numbers 2023-12-31 01:24:11 -08:00
Enrico Ros d4c02dde1d Move the NextLoading progress bar after the single-page check, but before the backend roundtrip 2023-12-31 01:19:47 -08:00
Enrico Ros 660fda8485 Support CSS mime for file attachments. 2023-12-31 00:33:39 -08:00
Enrico Ros 049dfec794 ScrollToBottom: use a smaller sticky margin 2023-12-31 00:26:37 -08:00
Enrico Ros 2e6f1939dc UI: useNextLoadProgress as hook, and import style 2023-12-30 22:51:32 -08:00
Enrico Ros f3b1e4698a UI: extract and move icons 2023-12-30 18:44:50 -08:00
Enrico Ros 34e0102d82 UI: Add app CSS 2023-12-30 18:43:16 -08:00
Enrico Ros 3f5aed6f9b Merge pull request #318 from kursad-k/patch-1
Update config-browse.md
2023-12-30 17:32:21 -08:00
kursad-k 29647ad106 Update config-browse.md
added internet proxy settings
2023-12-30 19:31:19 -06:00
Enrico Ros 9426a45b88 Reduce uses of useRouter()
Note: the Link component is still using them really aggressively.
2023-12-30 16:24:21 -08:00
Enrico Ros 5b52544c6c Composer: some style fixes 2023-12-30 04:07:59 -08:00
Enrico Ros fc1c15ba87 Small image download hint 2023-12-30 03:57:03 -08:00
Enrico Ros e973fce3f7 UI: restyle IconButtons
The size of the picture inside the icon stays the same, 24x24, but the overall IconButton
and Button go down to 36x36 (was 40).

This includes a revert from a style change that originated from:
https://github.com/mui/material-ui/commit/7f81475ea148a416ec8fab252120ce6567c62897#diff-45dca083057933d78377b59e031146804cfedb68fe1514955bc8a5b3c38d7c44

The overall layout is getting smaller, so let's adapt to smaller IconButtons
2023-12-30 03:44:18 -08:00
Enrico Ros 99759654f2 Bug_report: improve copy 2023-12-29 23:33:41 -08:00
Enrico Ros 390a1effb1 Bug_report: test newlines 2023-12-29 23:31:41 -08:00
Enrico Ros f357291560 Improve placeholders 2023-12-29 23:30:59 -08:00
Enrico Ros c3a8b7e859 Update label 2023-12-29 23:28:15 -08:00
Enrico Ros 8931544349 Removed steps to reproduce 2023-12-29 23:27:43 -08:00
Enrico Ros 865e420e34 Update the BUG issue template, following the great example of tRPC 2023-12-29 23:26:27 -08:00
Enrico Ros 574c2b936e Create bug_test.yml 2023-12-29 23:18:10 -08:00
Enrico Ros 4f6a596cc7 Hold trpc back - bundle size increased by 20k 2023-12-29 22:54:44 -08:00
Enrico Ros edd36ea780 Roll packages 2023-12-29 19:01:37 -08:00
Enrico Ros 5a325b98ee Merge 'feature-newi' Phase 2 - details below:
- ChatCommands abstraction and registration (execution still specialized)
 - ReAct: improve display of steps, and UI
 - lineHeight: use unified number for consistency of rhythm
 - OptimaLayout: begin breakdown
2023-12-29 18:30:35 -08:00
Enrico Ros 8f6e2a3b5f Commands: registration framework 2023-12-29 18:05:27 -08:00
Enrico Ros cf2fc96107 Composer: move text interception 2023-12-29 02:45:06 -08:00
Enrico Ros 8837a1fc65 Chat Panes: alt+click to remove focus 2023-12-29 02:45:06 -08:00
Enrico Ros 91970f088e Composer: move buttons 2023-12-29 02:11:08 -08:00
Enrico Ros f59f77e50a Ephemerals: improve looks 2023-12-29 02:08:15 -08:00
Enrico Ros 50b1f00b5a Commands: much improve the parser 2023-12-29 01:38:32 -08:00
Enrico Ros 4f98a8a319 UI: github markdown: deviate from upstream, and don't redefine font properties 2023-12-29 01:02:38 -08:00
Enrico Ros fb8aa3936b UI: font: removed from html 2023-12-29 01:02:16 -08:00
Enrico Ros 335876555f UI: chat message: smaller avatar text 2023-12-29 01:01:42 -08:00
Enrico Ros 7da3b1f4c4 UI: rhythm (line heights) 2023-12-29 01:01:10 -08:00
Enrico Ros e80bc4cea7 reAct: improve logging 2023-12-29 00:39:03 -08:00
Enrico Ros 448755ff8d OptimaL: destructure 2023-12-28 22:41:14 -08:00
Enrico Ros 3a4c23840a Panes: extract Resize handler 2023-12-28 21:20:28 -08:00
Enrico Ros 13c69111f9 Stop button: soft warning 2023-12-28 20:43:53 -08:00
Enrico Ros 0b9feb9fda Scroll To Bottom: fix one edge case #312 2023-12-28 20:42:48 -08:00
Enrico Ros 677facb867 Merge branch 'release-1.9.0' 2023-12-28 14:49:58 -08:00
Enrico Ros 494086765b 1.9.0: README and Changelog 2023-12-28 14:47:04 -08:00
Enrico Ros 59ca03e17d Release: update template 2023-12-28 14:32:19 -08:00
Enrico Ros e0e56d70c9 1.9.0: News 2023-12-28 14:29:11 -08:00
Enrico Ros b408267e6e DALL·E: reorder options 2023-12-28 14:09:17 -08:00
Enrico Ros 6385d7aa84 DALL·E: raw prompting for DALL·E 3 as well 2023-12-28 14:04:14 -08:00
Enrico Ros fa811c951c 1.9.0: Version 2023-12-28 13:26:40 -08:00
Enrico Ros 7085c3a7aa DALL·E: temporary image notice 2023-12-28 13:22:53 -08:00
Enrico Ros 2333318cb4 Release: update template 2023-12-28 13:00:54 -08:00
Enrico Ros 3aebcb360c Release: update template 2023-12-28 12:56:59 -08:00
Enrico Ros bf60d699e3 Release: update template 2023-12-28 12:51:19 -08:00
Enrico Ros d775d47623 New UI - Part 1 - Details inside:
- Optima Layout: new Context based pluggable layout system
   - Now children have context functions, for better behaviors
   - Removed `store-applayout`
   - using withLayout on top-level Pages
 - ScrollToBottom: grounds-up subsystem for smooth scrolling with snap-to-bottom
 - Panes subsystem: use react-resizeable-panels together with our Panes subsystem
   - New: Split window chats, Drag to close windows, Button to split
   - using: https://github.com/bvaughn/react-resizable-panels
 - Cosmetic: Colors: update Light and Dark themes
 - Bootstrap Logic provider: will enable Mobile use cases
 - Removed NoSSR (the backend provided natually acts as the same)
 - Next load progress: loading indicator for slower pages (>300ms)
 - withLayout() system

Additional benefits include: no-pluggable-flashing, pane-ready,
fixed X-scrolling on Firefox, and more.

Closes #308, #304, #255, #59.
Progress on #305, #201, #296, #233, #208, #203.
2023-12-28 02:16:55 -08:00
Enrico Ros 2eb3397394 Scroll-To-Bottom: complete Framework. Fixes #304, Fixes #60, Fixes #59 2023-12-28 02:13:14 -08:00
Enrico Ros e27c35373d Update year, almost there 2023-12-28 02:02:11 -08:00
Enrico Ros 5e1966af5f Scroller: begins to work well 2023-12-28 00:36:41 -08:00
Enrico Ros 7cbcf01ca9 Scroller: vastly improve the framework 2023-12-27 23:41:50 -08:00
Enrico Ros 6898fa6cc1 Scroller: framework (incomplete), Fixes #59 2023-12-27 20:08:28 -08:00
Enrico Ros 1e796299a2 Scroller: straighten messages (remove bottoms) 2023-12-27 19:50:07 -08:00
Enrico Ros 7026024da5 Scroller: straighten messages 2023-12-27 18:16:59 -08:00
Enrico Ros 3ed52fa92f Panes: move 2023-12-27 16:55:22 -08:00
Enrico Ros a3e04f5973 Panes: duplicate current 2023-12-27 16:16:27 -08:00
Enrico Ros 8bf90e3622 Use react-resizable-panels instead of the flexbox
Also Fix #255 due to the large layout restructuring.
2023-12-27 05:21:07 -08:00
Enrico Ros cdc2de5018 Composer: fix attachments layout 2023-12-27 02:29:53 -08:00
Enrico Ros b26370a85a OptimaLayout: begin 2023-12-27 00:52:49 -08:00
Enrico Ros adf0197a9e Disable debug 2023-12-26 22:29:21 -08:00
Enrico Ros c00c41a160 AppBar: begin cleanup 2023-12-26 22:28:43 -08:00
Enrico Ros 09c74e6cf4 OptimaLayout: migrate to Context for better React usage 2023-12-26 22:23:50 -08:00
Enrico Ros 304e66b098 Routing shall be homogeneous now 2023-12-26 20:04:12 -08:00
Enrico Ros 64b6b08652 Routing bits 2023-12-26 19:59:30 -08:00
Enrico Ros cbea304a97 Improve routing 2023-12-26 19:58:01 -08:00
Enrico Ros c3e73fa9c8 BootstrapProvider: check for mobile 2023-12-26 19:46:12 -08:00
Enrico Ros 4c978020d9 Add the Bootstrap Logic provider 2023-12-26 19:39:49 -08:00
Enrico Ros 481b85bdad Providers into a dedicated folder 2023-12-26 19:32:20 -08:00
Enrico Ros b80fd0494a Move this (unused) utility 2023-12-26 19:31:22 -08:00
Enrico Ros c7dea43d1a Move providers 2023-12-26 19:30:51 -08:00
Enrico Ros 726053ffcd GoodDropdown shared 2023-12-26 13:01:17 -08:00
Enrico Ros ee4e2c265b Next Router Loading Progress 2023-12-26 12:55:43 -08:00
Enrico Ros a5332d2c82 Deflate bundle by reverting to per-page Layouts (keep the typings at least) 2023-12-26 01:11:21 -08:00
Enrico Ros 2f45ce48fa Fix 2023-12-26 00:43:15 -08:00
Enrico Ros 104922dc20 Dynamic layouting 2023-12-26 00:36:18 -08:00
Enrico Ros d68ccd9dfb Optimize 2023-12-25 23:57:59 -08:00
Enrico Ros 676bcadd17 Remove NoSSR: the Backend provider does the same and doesn't seem to flash the screen that much 2023-12-25 22:36:59 -08:00
Enrico Ros c08e83c618 More uniform App backgrounds 2023-12-25 22:16:06 -08:00
Enrico Ros 7a69b32506 Theme: update background shades for Light and Dark 2023-12-25 22:12:50 -08:00
Enrico Ros a9e1a968e8 DallE3: support multiple parallel image request for count>1 2023-12-23 03:36:06 -08:00
Enrico Ros dc30a7a55a Pixels 2023-12-23 03:08:43 -08:00
Enrico Ros f570627b09 Settings: light outline 2023-12-23 02:44:46 -08:00
Enrico Ros e601302db8 Dall-E: show pricing when changing settings 2023-12-23 02:35:46 -08:00
Enrico Ros f9e207ff7c RenderImage: larger tooltip 2023-12-23 02:07:33 -08:00
Enrico Ros 8100c5cfd1 Merge branch 'feature-dalle'
Fixes #212
2023-12-23 02:00:02 -08:00
Enrico Ros 0b0c3891bb Bits 2023-12-23 01:58:58 -08:00
Enrico Ros b4cdd5546d T2I: Final Naming and Cleanups. Closes #212 2023-12-23 01:55:32 -08:00
Enrico Ros 8444b32db2 T2I: tti -> t2i 2023-12-23 01:45:52 -08:00
Enrico Ros 69098273bf TTI: return Markdown Image References.
Will be rendered neatly with or without markdown on.
2023-12-23 01:20:04 -08:00
Enrico Ros 5cd5702b83 Dalle: improve configuration 2023-12-23 01:11:42 -08:00
Enrico Ros 605d288da6 Dalle: improve typedefs 2023-12-23 01:10:24 -08:00
Enrico Ros 499840cae3 Parse the new markdown image blocks 2023-12-23 01:09:54 -08:00
Enrico Ros 4529fc325b RenderImage: vastly improve the Image Block, incl. the ALT Text 2023-12-23 01:09:35 -08:00
Enrico Ros 4769e9b900 T2I: move store 2023-12-22 23:22:09 -08:00
Enrico Ros 64d13a0d52 T2I: remove auto-set from OpenAI setup 2023-12-22 23:15:57 -08:00
Enrico Ros 7df1517b23 T2I: Settings (choose active) 2023-12-22 23:15:21 -08:00
Enrico Ros 56c372455d T2I: fix OpenAI DallE path 2023-12-22 23:13:13 -08:00
Enrico Ros 2e649ea12b Image Block: add Dalle 2023-12-22 23:12:03 -08:00
Enrico Ros 2a67315504 FormRadioControl: improve mobile, support undefined 2023-12-22 22:20:02 -08:00
Enrico Ros b53ceb70c4 T2I: improvements 2023-12-22 19:10:20 -08:00
Enrico Ros 3c9d06aac7 T2I: misc 2023-12-22 19:00:49 -08:00
Enrico Ros 77e7c1d467 T2I: move methods around 2023-12-22 19:00:35 -08:00
Enrico Ros eb38e119b8 misc: rename file 2023-12-22 18:48:00 -08:00
Enrico Ros 06402cc5c1 T2I: capability checks 2023-12-22 18:40:26 -08:00
Enrico Ros ddf631cdfc T2I: integrate with OpenAI Access credentials 2023-12-22 18:31:46 -08:00
Enrico Ros f7e89ae65c bits 2023-12-22 18:31:19 -08:00
Enrico Ros 07e1e1c580 T2I: client (capabilities, immediate generation) 2023-12-22 18:30:51 -08:00
Enrico Ros f6eb2aecee T2I: capabilities update 2023-12-22 18:29:38 -08:00
Enrico Ros f416b1df97 T2I: openAI generation 2023-12-22 18:29:17 -08:00
Enrico Ros 29d17795b8 T2I: cmd change 2023-12-22 18:28:30 -08:00
Enrico Ros 3b30f649c6 T2I: move Prodia in the Text2Image module 2023-12-22 18:27:33 -08:00
Enrico Ros ba9a9714a7 OpenAI: router: generate images 2023-12-22 16:34:06 -08:00
Enrico Ros c304ab5f3b Llms: Cleanup some type definitions 2023-12-22 15:43:49 -08:00
Enrico Ros cd4d5042e9 Roll packages 2023-12-22 15:22:01 -08:00
Enrico Ros 6c4d177bfc Metadata: update 2023-12-22 06:06:55 -08:00
Enrico Ros 5d1620b5c1 OpenRouter: limit free model calls to 1/5s. Closes #291 2023-12-22 03:42:42 -08:00
Enrico Ros bd78808950 Implement Rate limiting framework 2023-12-22 03:40:48 -08:00
Enrico Ros 6aee6aeac1 DLLM: add a 'Free' attribute (only on OpenRouter for now)
Shall have this on Local models as well?
2023-12-22 03:23:05 -08:00
Enrico Ros 5ae970a526 LLM Options: display Free models 2023-12-22 03:22:26 -08:00
Enrico Ros 87718d73d2 Models Loading progress 2023-12-22 03:04:42 -08:00
Enrico Ros 7c8498573e LLMOptions: improve display (add tooltips and advanced) 2023-12-22 02:58:01 -08:00
Enrico Ros f6e82d0c0c OpenRouter: drop the hardcoded list 2023-12-22 02:57:46 -08:00
Enrico Ros f7f827660d Merge pull request #290 from joriskalz/fix-reset-values-when-switching-mode
[BUG] Reset values when switching between text and youtube mode
2023-12-21 14:31:14 -08:00
Enrico Ros 664b221e67 Imagine: unified pipeline. Adds to #289 2023-12-21 14:29:11 -08:00
Enrico Ros f184a4bf97 Imagine: remove former 'mode' 2023-12-21 14:21:41 -08:00
Joris Kalz e442816c15 fix to reset state when switching between modes. 2023-12-21 18:37:31 +01:00
Enrico Ros aaa3b65cd8 Merge branch 'joriskalz-Persona-From-Text'. Fixes #282 2023-12-21 04:31:58 -08:00
Enrico Ros c6441662b0 Persona Creator: on by default, can be hidden like other tiles 2023-12-21 04:30:29 -08:00
Enrico Ros b902a7bce8 Persona Creator: consistent naming 2023-12-21 04:30:01 -08:00
Enrico Ros 87a916ba09 Persona Creator: remove the 'Labs' flag 2023-12-21 04:29:31 -08:00
Enrico Ros 35a85ed2fa Persona Creator: final fix I swear 2023-12-21 03:32:01 -08:00
Enrico Ros 75d56bfb56 Persona Creator: change the 'copy' location and improve paddings 2023-12-21 03:15:03 -08:00
Enrico Ros d0a125fad5 Persona Creator: rename model selector label 2023-12-21 02:51:53 -08:00
Enrico Ros 2af8437f6d Persona Creator: reorder blocks, and show the LLM name 2023-12-21 02:49:30 -08:00
Enrico Ros 0c3e65575c Persona Creator: remove YT -> renamed to PersonaCreator.tsx 2023-12-21 02:12:25 -08:00
Enrico Ros 1c15057fca Persona Creator: style: update TextArea and margins 2023-12-21 02:11:33 -08:00
Joris Kalz 44da928489 Optimzed text to cover both use cases 2023-12-20 21:54:46 +01:00
Joris Kalz 85027d3e3a Added Persona from Text 2023-12-20 21:46:19 +01:00
Enrico Ros 0fc83cf6f5 Merge branch 'release-1.8.0' 2023-12-20 02:38:51 -08:00
Enrico Ros 2949feccd5 Maintainers Release 2023-12-20 02:32:47 -08:00
Enrico Ros d6f1c2da81 1.8.0: Readme and Changelog 2023-12-20 02:11:13 -08:00
Enrico Ros fabb433fde 1.8.0: news.data.tsx 2023-12-20 01:54:23 -08:00
Enrico Ros b57445eb14 1.8.0: Version 2023-12-20 01:11:08 -08:00
Enrico Ros 5f8f4aba78 Ollama: update models 2023-12-20 00:59:14 -08:00
Enrico Ros d693cdaeba Ollama: update admin panel 2023-12-20 00:59:03 -08:00
Enrico Ros 39fbcfd97b OpenRouter: update models 2023-12-20 00:55:27 -08:00
Enrico Ros 7694bc3d52 OpenRouter: update models 2023-12-20 00:53:16 -08:00
Enrico Ros 7f21b2ac3d Merge branch 'feature-gemini'
Fixes #275
2023-12-20 00:16:44 -08:00
Enrico Ros fdb66da1a7 Gemini: choose a content filtering threshold 2023-12-20 00:14:53 -08:00
Enrico Ros 6b62a6733b Gemini: show block reason 2023-12-20 00:14:53 -08:00
Enrico Ros 5d62056807 Streaming: muxing format 2023-12-20 00:14:53 -08:00
Enrico Ros efff7126af Gemini: final touches 2023-12-20 00:14:53 -08:00
Enrico Ros 45046c70ed Gemini: stream on 2023-12-20 00:14:53 -08:00
Enrico Ros 7b5b852793 Gemini: trim key 2023-12-20 00:14:53 -08:00
Enrico Ros 9952b757b8 Gemini: client version 2023-12-20 00:14:53 -08:00
Enrico Ros b08ecc9012 Models Modal: improve caps 2023-12-20 00:14:53 -08:00
Enrico Ros bc5a38fa89 Models List: show a helpful message 2023-12-20 00:14:53 -08:00
Enrico Ros bee49a4b1c Llms: streaming as a vendor function (then all directed to the unified) 2023-12-20 00:14:53 -08:00
Enrico Ros 0ece1ce58c Llms: vendor-specific RPC to ChatGenerate 2023-12-20 00:14:53 -08:00
Enrico Ros fd897b55b2 Llms: improve list generics 2023-12-20 00:14:53 -08:00
Enrico Ros dd41a402d0 Llms: move models modal 2023-12-20 00:14:53 -08:00
Enrico Ros 3f9defd18c Llms: restructure 2023-12-20 00:14:53 -08:00
Enrico Ros 49c77f5a10 Llms: cleanup model lists (bits) 2023-12-20 00:14:52 -08:00
Enrico Ros 6b2bfa6060 Llms: cleanup model lists 2023-12-20 00:14:52 -08:00
Enrico Ros 8e3f247bfb Gemini: cleaner 2023-12-20 00:14:52 -08:00
Enrico Ros 201e3a7252 Streaming: cleanup 2023-12-20 00:14:52 -08:00
Enrico Ros 044ed4df79 Bits for the future 2023-12-20 00:14:52 -08:00
Enrico Ros 0df7297cca Gemini: configuration, list models, and immediate generation 2023-12-20 00:14:52 -08:00
Enrico Ros 453a3e5751 LLM Vendors: auto IDs 2023-12-20 00:14:52 -08:00
Enrico Ros 34c1c425b9 Gemini: backend env var 2023-12-20 00:14:52 -08:00
Enrico Ros e0a010189f LLMOptions Modal: fix display 2023-12-20 00:14:52 -08:00
Enrico Ros 7a07f10ed1 Move ModelVendor enum 2023-12-20 00:14:52 -08:00
Enrico Ros 33cb2b84b2 Anthropic: allow for 39 chars sks 2023-12-20 00:13:58 -08:00
Enrico Ros 3adec85e1f Fix shortcuts on Mac. 2023-12-18 19:59:03 -08:00
Enrico Ros 18cfe5e296 DB: drop URL validation for POSTGRES_PRISMA_URL. #277 2023-12-18 15:16:02 -08:00
Enrico Ros 566ba366b4 Merge pull request #280
[Visualize] Add custom instruction #218
2023-12-18 12:19:03 -08:00
Enrico Ros 7ed653b315 Fix. 2023-12-18 04:54:04 -08:00
Enrico Ros cb333c33d7 Better 1-click deployment, fixes #279 2023-12-18 03:22:18 -08:00
Joris Kalz 22ba37074b [Visualize] Add custom instruction #218 2023-12-16 23:22:47 +01:00
Enrico Ros 84d7b7644a Ollama: update models 2023-12-15 15:48:41 -08:00
Enrico Ros 71445dafc8 Ollama: improved diagram 2023-12-15 15:29:56 -08:00
Enrico Ros 66a5ad7f00 Ollama: update md 2023-12-15 15:27:11 -08:00
Enrico Ros 09f80adfaa Ollama: update md 2023-12-15 15:26:38 -08:00
Enrico Ros 9febd97065 Ollama: update md 2023-12-15 15:24:48 -08:00
Enrico Ros 5219f9928d Ollama: update md 2023-12-15 15:24:13 -08:00
Enrico Ros aec9f4665f Update config-ollama.md 2023-12-15 15:23:48 -08:00
Enrico Ros db48465204 Ollama: document network issue resolution. #276 2023-12-15 15:20:33 -08:00
Enrico Ros c2c858730a Bite the bullet with Zustand 2023-12-13 14:57:06 -08:00
Enrico Ros 402bde9a81 Newpad 2023-12-13 02:06:19 -08:00
Enrico Ros ba1c0ba0d9 Enforce a Single instance (Tab) of the app. Closes #268 2023-12-13 00:09:56 -08:00
Enrico Ros 084d77cd78 Linting 2023-12-12 18:24:59 -08:00
Enrico Ros 30c17a9b73 Roll Joy 2023-12-12 18:10:46 -08:00
Enrico Ros 2442463da3 deploy-docker.md: update Official guide 2023-12-12 17:52:28 -08:00
Enrico Ros 84a3e8cfdb Fix docker-compose to point to the 'latest' (stable) version, instead of the no more existing 'main' 2023-12-12 17:17:30 -08:00
Enrico Ros 6ae440d252 1.7.3: Patch release for Mistral support 2023-12-12 17:01:40 -08:00
Enrico Ros c0c724afc1 Mistral Platform: full support
Closes #273.
2023-12-12 16:39:06 -08:00
Enrico Ros a265112ce1 Mistral Platform: backend-configurable support (#273) 2023-12-12 16:39:06 -08:00
Enrico Ros 75605ed408 Dropdown: support model vendor icons 2023-12-12 16:39:06 -08:00
Enrico Ros ad38ff4157 LLMs: safer and smarter access 2023-12-12 16:39:06 -08:00
Enrico Ros 08c60e53b1 LLMs: reorder template params 2023-12-12 16:39:06 -08:00
Enrico Ros d0dcb2ac02 LLMs: getTransportAccess 2023-12-12 16:39:06 -08:00
Enrico Ros fbeb604b26 Update README.md 2023-12-12 03:42:05 -08:00
Enrico Ros c4f3b1df77 Update README.md 2023-12-12 03:40:44 -08:00
Enrico Ros 5a1f9caaac Roll rest 2023-12-12 03:16:35 -08:00
Enrico Ros 2fc70d5e95 Roll other dev deps 2023-12-12 03:12:43 -08:00
Enrico Ros 43adadef78 Roll Material/Joy/Next 2023-12-12 03:11:14 -08:00
Enrico Ros 96f6e7628b Roll Prisma 2023-12-12 03:08:10 -08:00
Enrico Ros 32ad82bcee Drag/Drop: do not remove the text from the source 2023-12-12 03:07:31 -08:00
Enrico Ros 3d72aec369 Roll pdfjs-dist 2023-12-12 02:58:06 -08:00
Enrico Ros d244ee2cca Update Docker image workflow.
Assume the vX.Y.Z is the latest (and will have the latest tag). Removing this to remove the 'stable' tag, as
latest is better.

The 'main' branch keeps the development tag.
2023-12-12 01:38:57 -08:00
Enrico Ros cc8a235ae3 Bits 2023-12-12 01:21:43 -08:00
Enrico Ros ae348812de OpenRouter: improve showing of discounted models 2023-12-12 01:14:33 -08:00
Enrico Ros 6053636f66 OpenRouter: OAuth login support 2023-12-11 22:35:40 -08:00
Enrico Ros f2e2aee672 1.7.2: Stable Patch Version 2023-12-11 21:22:31 -08:00
Enrico Ros 11cbb2bbf0 OpenRouter: update models 2023-12-11 21:21:22 -08:00
Enrico Ros 30bd19d6ce HTML Table to Markdown Table: improve reliability and ignore hidden data 2023-12-11 20:46:34 -08:00
Enrico Ros d0b5c02062 Improve how Stream errors are shown 2023-12-11 18:22:15 -08:00
Enrico Ros 771192e406 Ollama: support ollama errors via API 2023-12-11 18:19:38 -08:00
Enrico Ros 13f502bd76 1.7.1: Release (Ollama chat). #270 2023-12-10 22:17:35 -08:00
Enrico Ros 11055b12ca Ollama: use the new Chat endpoint. Closes #270 2023-12-10 22:12:51 -08:00
Enrico Ros d0ea96eec0 Ollama: Admin: optional sort by Pulls, and UI link to the Model page 2023-12-10 22:03:55 -08:00
Enrico Ros 02eafc03f1 Ollama: update models, and sort by Featured 2023-12-10 22:01:50 -08:00
Enrico Ros 33d07a0313 Ollama: update documentation 2023-12-10 21:30:30 -08:00
Enrico Ros 763b852148 Ollama: administration: external link 2023-12-10 20:24:20 -08:00
Enrico Ros d5b0617fd7 Comment for now 2023-12-10 06:14:49 -08:00
Enrico Ros e3ce83674c Update Ollama 2023-12-10 06:09:54 -08:00
Enrico Ros 5cc5df6909 1.7.0: Fix 2023-12-10 04:52:00 -08:00
Enrico Ros 11d8cf8996 Update GitHub docker action 2023-12-10 04:51:37 -08:00
Enrico Ros eae578970e 1.7.0: UpDate 2023-12-10 04:12:05 -08:00
Enrico Ros e076953c6a Merge branch 'release-1.7.0' 2023-12-10 04:08:29 -08:00
Enrico Ros 5c455591ea 1.7.0: Readme and Changelog 2023-12-10 04:06:50 -08:00
Enrico Ros 19b3dcd927 Update maintainers-release.md 2023-12-10 03:43:42 -08:00
Enrico Ros 702e27edbf Update deploy-authentication.md 2023-12-10 03:29:13 -08:00
Enrico Ros 7c872de9af Update deploy-authentication.md 2023-12-10 03:28:38 -08:00
Enrico Ros 53b18143e7 Update deploy-authentication.md 2023-12-10 03:27:49 -08:00
Enrico Ros d812813aac Update deploy-authentication.md 2023-12-10 03:27:09 -08:00
Enrico Ros 9505b7fd7f Update deploy-authentication.md 2023-12-10 03:26:27 -08:00
Enrico Ros 9e07822598 Update deploy-authentication.md 2023-12-10 03:26:02 -08:00
Enrico Ros 6d6604a043 Update maintainers-release.md 2023-12-10 03:10:29 -08:00
Enrico Ros 64d5071eb4 Update maintainers-release.md 2023-12-10 03:02:27 -08:00
Enrico Ros 4a29ff0b19 Update maintainers-release.md 2023-12-10 02:43:42 -08:00
Enrico Ros 6acab83ac5 1.7.0: Version 2023-12-10 02:28:54 -08:00
Enrico Ros a3391b46ec 1.7.0: News 2023-12-10 02:28:54 -08:00
Enrico Ros 9d021a0ea9 News: improve page 2023-12-10 01:58:15 -08:00
Enrico Ros 5b35435136 Removed stray page. #177 2023-12-10 01:56:48 -08:00
Enrico Ros 38b1cd1e4b Composer: premature optimizations 2023-12-10 01:47:37 -08:00
Enrico Ros 50e4bf30f2 Composer: more optimizations 2023-12-10 01:30:16 -08:00
Enrico Ros 6f8d6462b9 Composer: optimizations 2023-12-10 01:07:32 -08:00
Enrico Ros 596bb1ccc6 Readme: refer to http basic auth. #269 2023-12-10 00:20:21 -08:00
Enrico Ros 8023d4fd7e Improve HTTP Basic Auth docs. Improves #269 2023-12-10 00:17:34 -08:00
Enrico Ros 5808c5ae27 Merge branch 'LennardSchwarz-add-basic-auth' 2023-12-10 00:11:04 -08:00
Enrico Ros 0945bc1e74 Documented HTTP basic Auth. Fixes #269 2023-12-10 00:10:01 -08:00
Enrico Ros c82ea978da Improve Build/Deploy instructions 2023-12-09 23:05:56 -08:00
Enrico Ros 9184e28691 Merge branch 'add-basic-auth' of https://github.com/LennardSchwarz/lenn-big-agi into LennardSchwarz-add-basic-auth 2023-12-09 22:26:59 -08:00
Enrico Ros 59784af72c Browser: initial screenshot support 2023-12-08 04:45:43 -08:00
Enrico Ros 8feb1881b9 Merge branch 'feature-new-attachments'
Fixes #251
2023-12-08 04:45:26 -08:00
Enrico Ros 62747e07f1 Mic: greatly improve, with unmounting 2023-12-08 04:37:11 -08:00
Enrico Ros 934511a21f Mic: properly fix #221. The timeout was not reapplied. 2023-12-08 04:37:11 -08:00
Enrico Ros e36b71db9c Mic: Fix back on Desktop 2023-12-08 04:37:11 -08:00
Enrico Ros 924cd7018f Attachments: MultiPart-ready. Closes #251 for this stage. 2023-12-08 04:37:11 -08:00
Enrico Ros d5e91f9ce7 Optimize 2023-12-08 04:37:11 -08:00
Enrico Ros f1ad8cd55e Attachments: cleanups 2023-12-08 04:37:11 -08:00
Enrico Ros d177c73642 Attachments: Send! 2023-12-08 04:37:11 -08:00
Enrico Ros 011bcf8ccd Misc smaller improvements 2023-12-08 04:37:11 -08:00
Enrico Ros 7d0e5809e1 Misc cleanups 2023-12-08 04:37:11 -08:00
Enrico Ros b369148057 Attachments: Inlining: done. Use a hook that derives data from another hook. 2023-12-08 04:37:11 -08:00
Enrico Ros 2e0105b5ed Attachments: improvements and cleanups (still not attaching) 2023-12-08 04:37:11 -08:00
Enrico Ros 3f24ade8e6 Attachments: expire older parts 2023-12-08 04:37:11 -08:00
Enrico Ros 9cdaf26174 Attachments: remove Camera OCR (now common image OCR) 2023-12-08 04:37:11 -08:00
Enrico Ros 3b2c604615 Attachments: first inlining 2023-12-08 04:37:11 -08:00
Enrico Ros 223689316b Token Progress Bar: improve margins 2023-12-08 04:37:11 -08:00
Enrico Ros 6456a0de0c Token Progress Bar: disable Tooltip 2023-12-08 04:37:10 -08:00
Enrico Ros 57458fb32f Attachments: closer to ejection 2023-12-08 04:37:10 -08:00
Enrico Ros b2521060cc Attachments: cleanup Outputs 2023-12-08 04:37:10 -08:00
Enrico Ros 13b6a1ba7e Attachments: use ComposerOutputPart and cleanups 2023-12-08 04:37:10 -08:00
Enrico Ros ec81d802d5 Attachments: extract item menu 2023-12-08 04:37:10 -08:00
Enrico Ros f6eca257d6 Cleanup action group, slightly improves #258 2023-12-08 04:37:10 -08:00
Enrico Ros e744b1afcd Attachments: bits 2023-12-08 04:37:10 -08:00
Enrico Ros bfcae972f7 Attachments: cached token counting 2023-12-08 04:37:10 -08:00
Enrico Ros 360f886c37 Attachments: improve console log 2023-12-08 04:37:10 -08:00
Enrico Ros 305c278e1c Beauty: right align 2023-12-08 04:37:10 -08:00
Enrico Ros ccfcf6235f Beauty: by 2 pixels 2023-12-08 04:37:10 -08:00
Enrico Ros 62f7d92bb2 Beauty: token reporting 2023-12-08 04:37:10 -08:00
Enrico Ros f8915141c8 Attachments: major steps forward towards ejectability 2023-12-08 04:37:10 -08:00
Enrico Ros 7e1e4af19b Beauty: highlight user commands 2023-12-08 04:37:10 -08:00
Enrico Ros 439c462a9b Beauty: buttons 2023-12-08 04:37:10 -08:00
Enrico Ros 95aa71abd6 Beauty: mic buttons 2023-12-08 04:37:10 -08:00
Enrico Ros 3c829cbf97 Good Tooltip 2023-12-08 04:37:10 -08:00
Enrico Ros 29a31d5ca3 Beauty: main button 2023-12-08 04:37:10 -08:00
Enrico Ros 4a8bb24c0f Attachments: move withing composer 2023-12-08 04:37:10 -08:00
Enrico Ros 6b6c3afe0c Attachment: improve UX 2023-12-08 04:37:10 -08:00
Enrico Ros fd41388584 Attachment: outputsLoading for the spinners 2023-12-08 04:37:10 -08:00
Enrico Ros b418b69dc3 Attachment: improve Unsupported (without requiring user action to switch to the generic text-block) 2023-12-08 04:37:10 -08:00
Enrico Ros e1e2962a02 Attachment: bits 2023-12-08 04:37:10 -08:00
Enrico Ros f1662e174f Attachment: PDF to text, sync conversion, and debug 2023-12-08 04:37:10 -08:00
Enrico Ros a73c55fc1f Attachment: fixes 2023-12-08 04:37:10 -08:00
Enrico Ros 0aa923a99d Attachment: remove 2023-12-08 04:37:10 -08:00
Enrico Ros b75160bb2b Attachment: rename pipeline 2023-12-08 04:37:10 -08:00
Enrico Ros 3d515102a1 Attachment: initial image support 2023-12-08 04:37:10 -08:00
Enrico Ros b857cc18d8 Attachment: many cleanups 2023-12-08 04:37:10 -08:00
Enrico Ros 4737d962db Attachment: begin conversions 2023-12-08 04:37:10 -08:00
Enrico Ros 7ba71078a8 Attachment: conversion logic for text, finished popups 2023-12-08 04:37:10 -08:00
Enrico Ros bee0fa8751 Attachment: group Logic 2023-12-08 04:37:10 -08:00
Enrico Ros 5916dfb08d pdfUtils: move 2023-12-08 04:37:10 -08:00
Enrico Ros 9d13b03923 Enable Camera on desktop, #233 2023-12-08 04:37:10 -08:00
Enrico Ros 48e6385ac7 FormLabelStart: try with 'minWidth' 2023-12-08 04:37:10 -08:00
Enrico Ros cf664ff486 Attachment: improve auto-mime 2023-12-08 04:37:09 -08:00
Enrico Ros 5ccf8ba128 Attachment: push forward flow 2023-12-08 04:37:09 -08:00
Enrico Ros 3cd5917207 Attachment: set tooltip on button only 2023-12-08 04:37:09 -08:00
Enrico Ros e2dcca274f Browser: close incognito context 2023-12-08 04:37:09 -08:00
Enrico Ros 7369e898af Browser: make the wss endpoint always overridable 2023-12-08 04:37:09 -08:00
Enrico Ros 1e2c12fddb New Attach System: downloads almost ok 2023-12-08 04:37:09 -08:00
Enrico Ros 4f7369b940 Browser: improve behavior when loading non-pages (files) 2023-12-08 04:37:09 -08:00
Enrico Ros f566049890 Browser: further improve error handling 2023-12-08 04:37:09 -08:00
Enrico Ros fbc2da8b09 Browser: further improve error handling 2023-12-08 04:37:09 -08:00
Enrico Ros af70b39515 Browse: beginning to cleanup page load 2023-12-08 04:37:09 -08:00
Enrico Ros e080d72e8a New Attach System: Components 2023-12-08 04:37:09 -08:00
Enrico Ros fd24e3676a Confirmation Modals: prettier 2023-12-08 04:37:09 -08:00
Enrico Ros 942cd461f5 Drag & drop in Composer: exclude self-drags 2023-12-08 04:37:09 -08:00
Enrico Ros 9567e1cbaa New Attach System: renames 2023-12-08 04:37:09 -08:00
Enrico Ros 2d5d31268e New Attach System: transfer specialized functions to the hook 2023-12-08 04:37:09 -08:00
Enrico Ros b376608709 Fix on-demand clipboard item read.
Note: shall remove this and go for ctrl+v only?
2023-12-08 04:37:09 -08:00
Enrico Ros 551e502caf New Attach System: porting 2023-12-08 04:37:09 -08:00
Enrico Ros 9fb7fcd22f New Attach System: framework 2023-12-08 04:37:09 -08:00
Enrico Ros 1cda7d195b Revert "Browser: initial screenshot support"
This reverts commit 4a02923dda.
2023-12-08 04:36:17 -08:00
Enrico Ros 4a02923dda Browser: initial screenshot support 2023-12-08 04:13:44 -08:00
Enrico Ros a8a45631c2 Browser: update the documentation - large #247 improvement (@stevenlafl) 2023-12-08 03:40:51 -08:00
Enrico Ros eaa755d4ce Browser: update the documentation - integrates #247 2023-12-08 03:17:02 -08:00
Enrico Ros 872396a90e Browser: update Markdown, see #247 2023-12-08 02:08:00 -08:00
Enrico Ros 6b3a2772cc Bits 2023-12-08 01:41:56 -08:00
Enrico Ros f378733abe Oobabooga: document the changes 2023-12-07 22:18:06 -08:00
Enrico Ros 0cf8f0439d Oobabooga: fix with recent API changes 2023-12-07 22:09:28 -08:00
Enrico Ros ab53087b3a LLM Overheat: intuitive UX 2023-12-05 15:13:08 -08:00
Enrico Ros b50923a3b7 Denser menus: Message context & Selection 2023-12-05 14:59:45 -08:00
Enrico Ros 1b4a8da313 Backend: add support for analytics (log which host name responds) 2023-12-05 02:48:14 -08:00
Enrico Ros 31684c2fee [shortcuts] Ctrl+Shift+O: current Chat Model options (temperature, etc..) 2023-12-04 23:54:48 -08:00
Enrico Ros fedd4b1fda Fix setting reactivity on the new Voice Input Timeout. Closes #221 2023-12-04 23:36:18 -08:00
Enrico Ros a41667f427 Overheat LLMs
OpenAI LLMs can go up to 2 as far as temperature.
We don't enable >1 by default, but we have a new labs setting
to enable 'overheating' (max temperature raised
from 1 to 2) for Really Well Done LLMs.
2023-12-04 23:15:40 -08:00
Enrico Ros 021fa3b313 Update README.md 2023-12-02 01:50:49 -08:00
Lennard Schwarz b7ca69aa0e Update realm info 2023-12-01 18:31:04 +01:00
Lennard Schwarz 1efcadbf46 Update readme 2023-12-01 18:29:06 +01:00
Lennard Schwarz 598a6a8e0b Merge branch 'main' of github-ls:LennardSchwarz/lenn-big-agi into add-basic-auth 2023-12-01 18:25:58 +01:00
Enrico Ros 1cd441a2f5 Clipboard: intercept exception, e.g. when a jpeg/png file is copied to clipboard, chrome won't consider it valid on read (yes on ctrl+v) 2023-11-29 15:40:12 -08:00
Enrico Ros 783dc55d02 Ollama: pulling warning 2023-11-29 11:30:07 -08:00
Enrico Ros 88418d1ed0 Enable Toppy-M 2023-11-29 11:13:42 -08:00
Enrico Ros 6a74d1900f History truncation 2023-11-29 11:06:52 -08:00
Enrico Ros 5566e29bcc OpenRouter: update models 2023-11-29 10:43:10 -08:00
Enrico Ros 1f49195251 Ollama: update models, including a marker of the new models 2023-11-29 10:16:31 -08:00
Enrico Ros c5e15ece14 Composer: bits 2023-11-28 14:10:41 -08:00
Enrico Ros 7ceb176d70 Composer: cleanup overlays 2023-11-28 14:08:32 -08:00
Enrico Ros b93bd1bd0b move pdfToText 2023-11-28 12:35:38 -08:00
Enrico Ros 088133ec37 Configurable Voice Input timeout. #221 2023-11-28 03:46:23 -08:00
Enrico Ros 784766442d Extract FormRadioControl 2023-11-28 03:28:06 -08:00
Enrico Ros e014a7c828 Clarityx 2023-11-28 02:45:11 -08:00
Enrico Ros 224e745a71 Cosmetix 2023-11-28 02:35:06 -08:00
Enrico Ros 28ef74f1e9 Merge branch 'release-1.6.0' 2023-11-28 01:41:30 -08:00
Enrico Ros 70091ac39b 1.6.0 version 2023-11-28 01:40:29 -08:00
Enrico Ros cc1011659d 1.6.0 README and changelog 2023-11-28 01:39:03 -08:00
Enrico Ros 7eaa4a11bd 1.6.0 news 2023-11-28 01:32:14 -08:00
Enrico Ros 495f25e2d4 Update news hiding 2023-11-28 01:30:03 -08:00
Enrico Ros f2396000f2 Update template 2023-11-28 01:29:41 -08:00
Enrico Ros 77533aa385 Fix, thanks lint 2023-11-27 16:02:50 -08:00
Enrico Ros 01b2bf6fa3 Flattener: move to streaming, using a new helper 2023-11-27 16:01:25 -08:00
Enrico Ros 6d7843805e Small bits 2023-11-27 15:33:02 -08:00
Enrico Ros 0a593fb2c6 Fix focusing of imported chats. #233 2023-11-27 13:31:37 -08:00
Enrico Ros 57f277f269 ElevenLabs: improve config UX 2023-11-27 13:24:25 -08:00
Enrico Ros 6924e02a17 Link Import: fix Chat URL 2023-11-27 13:20:47 -08:00
Enrico Ros f4b645fd78 Update config-browse.md 2023-11-25 11:47:28 -08:00
Enrico Ros fdb46d3072 Browse: Improve errors reporting 2023-11-24 15:32:54 -08:00
Enrico Ros 858e9d3cb3 Browse: Local (ws://) in incognito 2023-11-24 15:32:46 -08:00
Enrico Ros 52a9dc7bec Browse: Documentation 2023-11-24 15:19:03 -08:00
Enrico Ros 16fbd3b6a3 Browse: cleanups2 2023-11-24 14:23:14 -08:00
Enrico Ros aa09e60f5f Browse: cleanups 2023-11-24 14:20:50 -08:00
Enrico Ros 3b2983831d Spell 2023-11-24 14:01:30 -08:00
Enrico Ros 16e69d0d0b commands: /help (primitive) 2023-11-24 13:55:47 -08:00
Enrico Ros 548f52c770 Browse: user configuration 2023-11-24 13:50:46 -08:00
Enrico Ros 8adac0d193 Browse: /browse -> loads as assistant response 2023-11-24 13:50:46 -08:00
Enrico Ros c0d3c6c982 Browse: /react support (as 'loadURL' tool) 2023-11-24 13:35:57 -08:00
Enrico Ros c1516e7be0 Browse: Share Target -> Composer attachment 2023-11-24 13:11:44 -08:00
Enrico Ros 8473894be2 Browse: CTRL+V (url) and 'Paste' (url) -> Composer attachment 2023-11-24 13:11:44 -08:00
Enrico Ros d5e2fbed0e Browse: page loading service, using remote Puppeteer
also: moved to tRPC (node)
2023-11-24 12:49:45 -08:00
Enrico Ros 2dfa78fbe0 Voice Calls - Labs option 2023-11-24 10:58:30 -08:00
Enrico Ros dff83c5ede Roll packages 2023-11-24 10:49:01 -08:00
Enrico Ros 483f483c4a Copy to clipboard snacks 2023-11-23 02:15:57 -08:00
Enrico Ros f780daf1b1 Anthropic Claude 2.1 support. Closes #245 2023-11-23 01:34:54 -08:00
Enrico Ros 5e6e5bf017 Improved Models Tooltip 2023-11-23 01:27:31 -08:00
Enrico Ros bfe2882ac3 Adding optional Pricing schema 2023-11-23 01:11:14 -08:00
Enrico Ros 0574be04f4 Update soft knowledge cutoff for 1106 models. 2023-11-22 23:36:04 -08:00
Enrico Ros 53b5da8cb8 OpenAI Shared Chats: import from Clipboard too, and copy json object 2023-11-22 22:32:45 -08:00
Enrico Ros 5387b17c36 Also show the branched title. 2023-11-22 13:03:03 -08:00
Enrico Ros 0e854b8772 Title: show the chat index (1: first, 2: second most recently created, etc) 2023-11-22 04:32:19 -08:00
Enrico Ros d23f247a8c Large Perf Boost on Messages 2023-11-22 04:06:27 -08:00
Enrico Ros ce13c04e96 Perf Boost - large gains on the Nav Drawer 2023-11-22 04:00:28 -08:00
Enrico Ros e55fbe9ad0 Fix missing hook dep 2023-11-22 03:14:16 -08:00
Enrico Ros e5a11af6d2 Rename 2023-11-22 02:32:24 -08:00
Enrico Ros 76f21f8c96 Rename 2023-11-22 02:22:20 -08:00
Enrico Ros ea4d9afff2 Ctrl + Shift + ?: show shortcuts 2023-11-22 01:52:13 -08:00
Enrico Ros d884970a02 Do not require confirmation for 'armed' deletions. 2023-11-22 01:39:23 -08:00
Enrico Ros ee11787dcc README.md - roadmap comment 2023-11-22 01:38:16 -08:00
Enrico Ros 13e1ba977f Update 1.5.0 release notes 2023-11-22 01:25:56 -08:00
Enrico Ros 7137ebdda2 Merge pull request #240 from g1ibby/fix-ollama-listModels
fix: ollama listModel endpoint when a model doesn't have TEMPLATE
2023-11-22 01:07:40 -08:00
Enrico Ros 9b71b08fe1 Chat Layout: push the chatmessagelist two levels down #233 2023-11-22 01:06:35 -08:00
Enrico Ros 45a18edac0 ChatMessageList: undo the Ephemeral move 2023-11-22 00:59:17 -08:00
Enrico Ros f1b1ca0a5f Window manager: split functions 2023-11-22 00:59:03 -08:00
Enrico Ros 0c1718bf9c Split-branch settings 2023-11-22 00:56:58 -08:00
Enrico Ros a934ca548e usePanesManager: optional debug 2023-11-21 22:52:24 -08:00
Enrico Ros 2896bd7287 Move Ephemerals Down 2023-11-21 22:41:55 -08:00
Enrico Ros 5ad103a8a2 Refer. 2023-11-21 22:21:22 -08:00
Enrico Ros 16916db247 Improve routing, and move the action pwa action receiver 2023-11-21 22:17:17 -08:00
g1ibby 669eb1414f fix: ollama listModel endpoint when a model doesn't have TEMPLATE or PARAMETER 2023-11-22 13:14:46 +07:00
Enrico Ros 6ed8529d6a Roll types 2023-11-21 22:06:18 -08:00
Enrico Ros bb36dbc4b9 Removed the Labs page, removed a store 2023-11-21 21:31:21 -08:00
Enrico Ros f9e38c7220 Ctrl + Alt + Left/Right: fast history navigation, closes #207 2023-11-21 19:27:22 -08:00
Enrico Ros 2b5a051a9e Ctrl + Alt + Left/Right: navigates in history 2023-11-21 18:45:19 -08:00
Enrico Ros 9793236941 Shortcuts: use fewer listeners 2023-11-21 18:04:33 -08:00
Enrico Ros 497d1c9559 Snackbar: chat title (disabled for now) 2023-11-21 17:41:00 -08:00
Enrico Ros 75c4fe5e67 Snackbars: useEffect compatible 2023-11-21 17:36:39 -08:00
Enrico Ros f4d3d3bd28 Snackbars: add the 'title' type 2023-11-21 17:36:12 -08:00
Enrico Ros 853aadaa0e Confirm branching. 2023-11-21 16:55:36 -08:00
Enrico Ros 8bf23e121c Snackbar Framework animations - Improves #206 2023-11-21 16:55:25 -08:00
Enrico Ros cbffc3f6d5 Snackbar Framework - Closes #206 2023-11-21 16:41:12 -08:00
Enrico Ros 52fc4ec5d8 Improve Restart messaging 2023-11-21 16:40:42 -08:00
Enrico Ros ab94579a30 Branching: duplication up to a message. Partial #235
This commit also largely cleanups the hierarchy tree of component callbacks/handlers
and sets a common nomenclature.
2023-11-21 15:16:58 -08:00
Enrico Ros 43ddc79939 Roll packages 2023-11-21 13:43:45 -08:00
Enrico Ros 6938c6b8d0 UI: Improve options location - Fixes #236 2023-11-21 13:41:27 -08:00
Enrico Ros ba5d835248 Improve spacing 2023-11-21 13:14:06 -08:00
Enrico Ros 510d58ba69 Cleanup News page - part of #236 2023-11-21 12:57:29 -08:00
Enrico Ros c23b0770bf tRPC: enforce more separation of the runtime
The build system was requiring (erroneously) some nodejs packages
when inside routers in the Edge route.
2023-11-21 02:29:05 -08:00
Enrico Ros cb4fdc56a5 Moved chat/commands 2023-11-21 00:28:59 -08:00
Enrico Ros 3b28767212 Renamed to ChatPane 2023-11-21 00:28:46 -08:00
Enrico Ros a1d6cb8cd0 Window management: separate stores again 2023-11-21 00:16:35 -08:00
Enrico Ros 0a094ef0b0 Improve Stores naming 2023-11-20 17:38:35 -08:00
Enrico Ros 17c349af94 Window management: framework
This includes moving the full responsibility for the active window
(and history) to the panes.
2023-11-20 16:19:04 -08:00
Enrico Ros 97f2a19227 Moved and renamed Trade, where it belongs 2023-11-20 16:07:08 -08:00
Enrico Ros 6fc2415e5d Chats store: removed the activeConversationId 2023-11-20 15:24:39 -08:00
Enrico Ros d68c131bbc Window management: ancillary nothingness 2 2023-11-20 15:18:04 -08:00
Enrico Ros 0b6c217da6 Window management: ancillary nothingness 2023-11-20 14:35:36 -08:00
Enrico Ros 432d78fc9d Window management: ancillary component cleanups 2023-11-20 14:32:06 -08:00
Enrico Ros 769ca1546a Window management: ancillary small changes 2023-11-20 14:20:19 -08:00
Enrico Ros 989684884c Window management: ancillary component changes 2023-11-20 14:19:09 -08:00
Enrico Ros a2b6554e73 ChatMessageList: do not collapse on null conversations, but show an helpful message 2023-11-20 02:16:25 -08:00
Enrico Ros 28555445c9 InlineError: allow 'info' 2023-11-20 02:14:24 -08:00
Enrico Ros 20bddfe6c6 Uniform sxprops 2023-11-20 02:14:06 -08:00
Enrico Ros 01243f7422 globalStoredList: begin abstracting stored lists 2023-11-19 19:02:43 -08:00
Enrico Ros 741edb499c Chat: begin moving window state up 2023-11-19 16:09:48 -08:00
Enrico Ros a3fd877a75 Default mobile corner button 2023-11-19 15:58:09 -08:00
Enrico Ros 0c19c4c8ac Clear for 1.6.0 2023-11-19 15:57:38 -08:00
Lennard Schwarz 89f3e6f955 Update readme 2023-10-30 14:57:51 +01:00
Lennard Schwarz e79b429c5e Update Readme 2023-10-30 14:57:45 +01:00
Lennard Schwarz c240f6bd5b Add deploy button 2023-10-30 14:55:53 +01:00
Lennard Schwarz 33312e0fd9 Add my middleware thing 2023-10-30 14:52:43 +01:00
471 changed files with 30040 additions and 8818 deletions
+5
View File
@@ -1,7 +1,12 @@
# big-AGI non-code files
/docs/
/dist/
README.md
# Ignore build and log files
Dockerfile
/.dockerignore
# Node build artifacts
/node_modules
/.pnp
-25
View File
@@ -1,25 +0,0 @@
---
name: Bug report
about: Omg what's happening?
title: "[BUG]"
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
Where is it happening?
- Which device [Mobile/Desktop, os version]:
- Which browser:
- Which website:
**To Reproduce**
Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots / context**
If applicable, please add screenshots or additional context
+32
View File
@@ -0,0 +1,32 @@
name: 🐞 Bug Report
description: Create a report to help us improve
title: '[BUG]'
labels: [ 'type: bug' ]
body:
- type: markdown
attributes:
value: Thank you for reporting a bug.
- type: textarea
attributes:
label: Description
description: (required) Please provide a clear description. Please also provide the steps to reproduce.
placeholder: 'Concise description + steps to reproduce.'
validations:
required: true
- type: textarea
attributes:
label: Device and browser
description: '(required) Please specify your Mobile/Desktop device, OS version, browser.'
placeholder: 'Device: (e.g., iPhone 16, Pixel 9, PC, Macbook...), OS: (e.g., iOS 17, Windows 12), Browser: (e.g., Chrome 119, Safari 18, Firefox..)'
validations:
required: true
- type: textarea
attributes:
label: Screenshots and more
placeholder: 'Attach screenshots, or add any additional context here.'
- type: checkboxes
attributes:
label: Willingness to Contribute
description: We appreciate contributions - would you be willing to submit a pull request?
options:
- label: '🙋‍♂️ Yes, I would like to contribute a fix.'
+86 -9
View File
@@ -7,27 +7,104 @@ assignees: enricoros
---
Release checklist:
## Release checklist:
- [x] Create a new [Release Issue](https://github.com/enricoros/big-AGI/issues/new?assignees=enricoros&projects=enricoros/4&template=maintainers-release.md&title=Release+1.2.3)
- [ ] Replace 1.1.0 with the _former_ release, and _1.2.3_ with THIS
- [ ] Update the [Roadmap](https://github.com/users/enricoros/projects/4/views/2) calling out shipped features
- [ ] Create and update a [Milestone](https://github.com/enricoros/big-agi/milestones) for the release
- [ ] Assign this task
- [ ] Assign all the shipped roadmap Issues
- [ ] Assign the relevant [recently closed Isssues](https://github.com/enricoros/big-agi/issues?q=is%3Aclosed+sort%3Aupdated-desc)
- Code changes:
- [ ] Create a release branch 'release-x.y.z', and commit:
- [ ] Create a release branch 'release-x.y.z': `git checkout -b release-1.2.3`
- [ ] Create a temporary tag `git tag v1.2.3 && git push opensource --tags`
- [ ] Create a [New Draft GitHub Release](https://github.com/enricoros/big-agi/releases/new), and generate the automated changelog (for new contributors)
- [ ] Update the release version in package.json, and `npm i`
- [ ] Update in-app News [src/apps/news/news.data.tsx](src/apps/news/news.data.tsx)
- [ ] Update in-app News [src/apps/news/news.data.tsx](/src/apps/news/news.data.tsx)
- [ ] Update the in-app News version number
- [ ] Update the readme with the new release
- [ ] Copy the highlights to the [changelog](docs/changelog.md)
- [ ] Update the README.md with the new release
- [ ] Copy the highlights to the [docs/changelog.md](/docs/changelog.md)
- Release:
- [ ] merge onto main
- [ ] merge onto main `git checkout main && git merge --no-ff release-1.2.3`
- [ ] re-tag `git tag -f v1.2.3 && git push opensource --tags -f`
- [ ] verify deployment on Vercel
- [ ] verify container on GitHub Packages
- create a GitHub release
- [ ] name it 'vX.Y.Z'
- [ ] copy the release notes and link appropriate artifacts
- [ ] update the GitHub release
- [ ] push as stable `git push opensource main:main-stable`
- Announce:
- [ ] Discord announcement
- [ ] Twitter announcement
### Links
- Milestone: https://github.com/enricoros/big-AGI/milestone/X
- GitHub release: https://github.com/enricoros/big-AGI/releases/tag/v1.2.3
- Former release task: #...
## Artifacts Generation
```markdown
You help me generate the following collateral for the new release of my opensource application called big-AGI. The new release is 1.2.3.
To familiarize yourself with the application, the following are the Website and the GitHub README.md.
```
- paste the URL: https://big-agi.com
- drag & drop: [README.md](https://raw.githubusercontent.com/enricoros/big-AGI/main/README.md)
```markdown
I am announcing a new version, 1.2.3.
For reference, the following was the collateral for 1.1.0 (Discord announcement, GitHub Release, in-app-news file news.data.tsx).
```
- paste the former: `discord announcement`,
- `GitHub release`,
- `news.data.tsx`,
- `changelog.md`
```markdown
The following are the new developments for 1.2.3:
- ...
- git log --pretty=format:"%h %an %B" v1.1.0..v1.2.3 | clip
```
- paste the link to the milestone (closed) and each individual issue (content will be downloaded)
- paste the output of the git log command
### news.data.tsx
```markdown
I need the following from you:
1. a table summarizing all the new features in 1.2.3 with the following columns: 4 words description (exactly what it is), short description, usefulness (what it does for the user), significance, link to the issue number (not the commit)), which will be used for the artifacts later
2. then double-check the git log to see if there are any features of significance that are not in the table
3. then score each feature in terms of importance for users (1-10), relative impact of the feature (1-10, where 10 applies to the broadest user base), and novelty and uniqueness (1-10, where 10 is truly unique and novel from what exists already)
4. then improve the table, in decreasing order of importance for features, fixing any detail that's missing, in particular check if there are commits of significance from a user or developer point of view, which are not contained in the table
5. then I want you then to update the news.data.tsx for the new release
```
### Readme (and Changelog)
```markdown
I need you to update the README.md and the with the new release.
Attaching the in-app news, with my language for you to improve on, but keep the tone.
```
### GitHub release
```markdown
Please create the 1.2.3 Release Notes for GitHub, following the format of the 1.1.0 GitHub release notes attached before.
Use a truthful and honest tone, understanding that people's time and attention span is short.
Today is 2024-XXXX-YYYY.
```
Now paste-attachment the former release notes (or 1.5.0 which was accurate and great), including the new contributors and
some stats (# of commits, etc.), and roll it for the new release.
### Discord announcement
```markdown
Can you generate my 1.2.3 big-AGI discord announcement from the GitHub Release announcement?
Please keep the formatting and stye of the discord announcement for 1.1.0, but with the new messaging above.
```
+5 -4
View File
@@ -8,10 +8,11 @@ assignees: ''
---
**Why**
The reason behind the request - we love it to be framed for "users will be able to do x" rather than quick-aging hype-tech-of-the-day requests
(replace this text with yours) The reason behind the request - we love it to be framed for "users will be able to do x" rather than quick-aging hype-tech-of-the-day requests
**Concise description**
A clear and concise description of what you want to happen.
**Description**
Clear and concise description of what you want to happen.
**Requirements**
If you can, please detail the changes you expect in UX, user workflows, technology, architecture (if not, the reviewers will do it for you)
If you can, Please break-down the changes use cases, UX, technology, architecture, etc.
- [ ] ...
+14 -3
View File
@@ -7,11 +7,15 @@
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.
name: Create and publish a Docker image
name: Create and publish Docker images
on:
push:
branches: ['main']
branches:
- main
#- main-stable # Disabled as the v* tag is used for stable releases
tags:
- 'v*' # Trigger on version tags (e.g., v1.7.0)
env:
REGISTRY: ghcr.io
@@ -26,7 +30,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
@@ -40,11 +44,18 @@ jobs:
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=development,enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=stable,enable=${{ github.ref == 'refs/heads/main-stable' }}
type=ref,event=tag # Use the tag name as a tag for tag builds
type=semver,pattern={{version}} # Generate semantic versioning tags for tag builds
- name: Build and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
file: Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: NEXT_PUBLIC_GA4_MEASUREMENT_ID=${{ secrets.GA4_MEASUREMENT_ID }}
+4
View File
@@ -1,5 +1,8 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Frontend Build: ignore API files disabled for this build
/app/**/*.backup
# dependencies
/node_modules
/.pnp
@@ -10,6 +13,7 @@
# next.js
/.next/
/dist/
/out/
# production
+12 -4
View File
@@ -2,22 +2,28 @@
FROM node:18-alpine AS base
ENV NEXT_TELEMETRY_DISABLED 1
# Dependencies
FROM base AS deps
WORKDIR /app
# Dependency files
COPY package*.json ./
COPY prisma ./prisma
COPY src/server/prisma ./src/server/prisma
# Install dependencies, including dev (release builds should use npm ci)
ENV NODE_ENV development
RUN npm ci
# Builder
FROM base AS builder
WORKDIR /app
# Optional argument to configure GA4 at build time (see: docs/deploy-analytics.md)
ARG NEXT_PUBLIC_GA4_MEASUREMENT_ID
ENV NEXT_PUBLIC_GA4_MEASUREMENT_ID=${NEXT_PUBLIC_GA4_MEASUREMENT_ID}
# Copy development deps and source
COPY --from=deps /app/node_modules ./node_modules
COPY . .
@@ -29,6 +35,7 @@ RUN npm run build
# Reduce installed packages to production-only
RUN npm prune --production
# Runner
FROM base AS runner
WORKDIR /app
@@ -38,9 +45,10 @@ RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy Built app
COPY --from=builder --chown=nextjs:nodejs /app/public public
COPY --from=builder --chown=nextjs:nodejs /app/.next .next
COPY --from=builder --chown=nextjs:nodejs /app/node_modules node_modules
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/src/server/prisma ./src/server/prisma
# Minimal ENV for production
ENV NODE_ENV production
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023 Enrico Ros
Copyright (c) 2023-2024 Enrico Ros
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+151 -62
View File
@@ -1,8 +1,8 @@
# BIG-AGI 🧠✨
Welcome to big-AGI 👋, the GPT application for Pro users that combines utility,
simplicity, and speed. Powered by the latest models from 7 vendors, including
open-source, `big-AGI` offers best-in-class Voice and Chat with AI Personas,
Welcome to big-AGI 👋, the GPT application for professionals that need function, form,
simplicity, and speed. Powered by the latest models from 12 vendors and
open-source model servers, `big-AGI` offers best-in-class Voice and Chat with AI Personas,
visualizations, coding, drawing, calling, and quite more -- all in a polished UX.
Pros use big-AGI. 🚀 Developers love big-AGI. 🤖
@@ -11,85 +11,176 @@ Pros use big-AGI. 🚀 Developers love big-AGI. 🤖
Or fork & run on Vercel
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fenricoros%2Fbig-agi&env=OPENAI_API_KEY,OPENAI_API_HOST&envDescription=OpenAI%20KEY%20for%20your%20deployment.%20Set%20HOST%20only%20if%20non-default.)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fenricoros%2Fbig-AGI&env=OPENAI_API_KEY&envDescription=Backend%20API%20keys%2C%20optional%20and%20may%20be%20overridden%20by%20the%20UI.&envLink=https%3A%2F%2Fgithub.com%2Fenricoros%2Fbig-AGI%2Fblob%2Fmain%2Fdocs%2Fenvironment-variables.md&project-name=big-AGI)
## 🗺️ Explore the Roadmap
## 👉 [roadmap](https://github.com/users/enricoros/projects/4/views/2) 👉 [documentation](docs/README.md)
The development of big-AGI is an open book. Our **[public roadmap](https://github.com/users/enricoros/projects/4/views/2)** is
live, providing a detailed look at the current and future development of the application.
big-AGI is an open book; see the **[ready-to-ship and future ideas](https://github.com/users/enricoros/projects/4/views/2)** in our open roadmap
- Got a suggestion? [_Add your roadmap ideas_](https://github.com/enricoros/big-agi/issues/new?&template=roadmap-request.md)
- Want to contribute? [_Pick up a task!_](https://github.com/users/enricoros/projects/4/views/4) - _easy_ to _pro_
### What's New in 1.13.0 · Feb 8, 2024 · Multi + Mind
### What's New in 1.5.0 🌟
https://github.com/enricoros/big-AGI/assets/32999/01732528-730e-41dc-adc7-511385686b13
- **Continued Voice**: Engage with hands-free interaction for a seamless experience
- **Visualization Tool**: Create data representations with our new visualization capabilities
- **Ollama Local Models**: Leverage local models support with our comprehensive guide
- **Text Tools**: Enjoy tools including highlight differences to refine your content
- **Mermaid Diagramming**: Render complex diagrams with our Mermaid language support
- **OpenAI 1106 Chat Models**: Experience the cutting-edge capabilities of the latest OpenAI models
- **SDXL Support**: Enhance your image generation with SDXL support for Prodia
- **Cloudflare OpenAI API Gateway**: Integrate with Cloudflare for a robust API gateway
- **Helicone for Anthropic**: Utilize Helicone's tools for Anthropic models
- **Side-by-Side Split Windows**: multitask with parallel conversations. [#208](https://github.com/enricoros/big-AGI/issues/208)
- **Multi-Chat Mode**: message everyone, all at once. [#388](https://github.com/enricoros/big-AGI/issues/388)
- **Export tables as CSV**: big thanks to @aj47. [#392](https://github.com/enricoros/big-AGI/pull/392)
- Adjustable text size: customize density. [#399](https://github.com/enricoros/big-AGI/issues/399)
- Dev2 Persona Technology Preview
- Better looking chats with improved spacing, fonts, and menus
- More: new video player, [LM Studio tutorial](https://github.com/enricoros/big-AGI/blob/main/docs/config-local-lmstudio.md) (thanks @aj47), [MongoDB support](https://github.com/enricoros/big-AGI/blob/main/docs/deploy-database.md) (thanks @ranfysvalle02), and speedups
Check out the [big-AGI open roadmap](https://github.com/users/enricoros/projects/4/views/2), or
the [past releases changelog](docs/changelog.md).
<details>
<summary>What's New in 1.12.0 · Jan 26, 2024 · AGI Hotline</summary>
https://github.com/enricoros/big-AGI/assets/32999/95ceb03c-945d-4fdd-9a9f-3317beb54f3f
- **Voice Calls**: real-time voice call your personas out of the blue or in relation to a chat [#354](https://github.com/enricoros/big-AGI/issues/354)
- Support **OpenAI 0125** Models. [#364](https://github.com/enricoros/big-AGI/issues/364)
- Rename or Auto-Rename chats. [#222](https://github.com/enricoros/big-AGI/issues/222), [#360](https://github.com/enricoros/big-AGI/issues/360)
- More control over **Link Sharing** [#356](https://github.com/enricoros/big-AGI/issues/356)
- **Accessibility** to screen readers [#358](https://github.com/enricoros/big-AGI/issues/358)
- Export chats to Markdown [#337](https://github.com/enricoros/big-AGI/issues/337)
- Paste tables from Excel [#286](https://github.com/enricoros/big-AGI/issues/286)
- Ollama model updates and context window detection fixes [#309](https://github.com/enricoros/big-AGI/issues/309)
</details>
<details>
<summary>What's New in 1.11.0 · Jan 16, 2024 · Singularity</summary>
https://github.com/enricoros/big-AGI/assets/1590910/a6b8e172-0726-4b03-a5e5-10cfcb110c68
- **Find chats**: search in titles and content, with frequency ranking. [#329](https://github.com/enricoros/big-AGI/issues/329)
- **Commands**: command auto-completion (type '/'). [#327](https://github.com/enricoros/big-AGI/issues/327)
- **[Together AI](https://www.together.ai/products#inference)** inference platform support (good speed and newer models). [#346](https://github.com/enricoros/big-AGI/issues/346)
- Persona Creator history, deletion, custom creation, fix llm API timeouts
- Enable adding up to five custom OpenAI-compatible endpoints
- Developer enhancements: new 'Actiles' framework
</details>
<details>
<summary>What's New in 1.10.0 · Jan 6, 2024 · The Year of AGI</summary>
- **New UI**: for both desktop and mobile, sets the stage for future scale. [#201](https://github.com/enricoros/big-AGI/issues/201)
- **Conversation Folders**: enhanced conversation organization. [#321](https://github.com/enricoros/big-AGI/issues/321)
- **[LM Studio](https://lmstudio.ai/)** support and improved token management
- Resizable panes in split-screen conversations.
- Large performance optimizations
- Developer enhancements: new UI framework, updated documentation for proxy settings on browserless/docker
</details>
For full details and former releases, check out the [changelog](docs/changelog.md).
## ✨ Key Features 👊
![Ask away, paste a ton, copy the gems](docs/pixels/big-AGI-compo1.png)
[More](docs/pixels/big-AGI-compo2b.png), [screenshots](docs/pixels).
| ![Advanced AI](https://img.shields.io/badge/Advanced%20AI-32383e?style=for-the-badge&logo=ai&logoColor=white) | ![100+ AI Models](https://img.shields.io/badge/100%2B%20AI%20Models-32383e?style=for-the-badge&logo=ai&logoColor=white) | ![Flow-state UX](https://img.shields.io/badge/Flow--state%20UX-32383e?style=for-the-badge&logo=flow&logoColor=white) | ![Privacy First](https://img.shields.io/badge/Privacy%20First-32383e?style=for-the-badge&logo=privacy&logoColor=white) | ![Advanced Tools](https://img.shields.io/badge/Fun%20To%20Use-f22a85?style=for-the-badge&logo=tools&logoColor=white) |
|---------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|
| **Chat**<br/>**Call** AGI<br/>**Draw** images<br/>**Agents**, ... | Local & Cloud<br/>Open & Closed<br/>Cheap & Heavy<br/>Google, Mistral, ... | Attachments<br/>Diagrams<br/>Multi-Chat<br/>Mobile-first UI | Stored Locally<br/>Easy self-Host<br/>Local actions<br/>Data = Gold | AI Personas<br/>Voice Modes<br/>Screen Capture<br/>Camera + OCR |
- **AI Personas**: Tailor your AI interactions with customizable personas
- **Sleek UI/UX**: A smooth, intuitive, and mobile-responsive interface
- **Efficient Interaction**: Voice commands, OCR, and drag-and-drop file uploads
- **Multiple AI Models**: Choose from a variety of leading AI providers
- **Privacy First**: Self-host and use your own API keys for full control
- **Advanced Tools**: Execute code, import PDFs, and summarize documents
- **Seamless Integrations**: Enhance functionality with various third-party services
- **Open Roadmap**: Contribute to the progress of big-AGI
![big-AGI screenshot](docs/pixels/big-AGI-compo-20240201_small.png)
## 💖 Support
You can easily configure 100s of AI models in big-AGI:
| **AI models** | _supported vendors_ |
|:--------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Opensource Servers | [LocalAI](https://localai.com) (multimodal) · [Ollama](https://ollama.com/) · [Oobabooga](https://github.com/oobabooga/text-generation-webui) |
| Local Servers | [LM Studio](https://lmstudio.ai/) |
| Multimodal services | [Azure](https://azure.microsoft.com/en-us/products/ai-services/openai-service) · [Google Gemini](https://ai.google.dev/) · [OpenAI](https://platform.openai.com/docs/overview) |
| Language services | [Anthropic](https://anthropic.com) · [Groq](https://wow.groq.com/) · [Mistral](https://mistral.ai/) · [OpenRouter](https://openrouter.ai/) · [Perplexity](https://www.perplexity.ai/) · [Together AI](https://www.together.ai/) |
| Image services | [Prodia](https://prodia.com/) (SDXL) |
| Speech services | [ElevenLabs](https://elevenlabs.io) (Voice synthesis / cloning) |
Add extra functionality with these integrations:
| **More** | _integrations_ |
|:-------------|:---------------------------------------------------------------------------------------------------------------|
| Web Browse | [Browserless](https://www.browserless.io/) · [Puppeteer](https://pptr.dev/)-based |
| Web Search | [Google CSE](https://programmablesearchengine.google.com/) |
| Code Editors | [CodePen](https://codepen.io/pen/) · [StackBlitz](https://stackblitz.com/) · [JSFiddle](https://jsfiddle.net/) |
| Sharing | [Paste.gg](https://paste.gg/) (Paste chats) |
| Tracking | [Helicone](https://www.helicone.ai) (LLM Observability) |
[//]: # (- [x] **Flow-state UX** for uncompromised productivity)
[//]: # (- [x] **AI Personas**: Tailor your AI interactions with customizable personas)
[//]: # (- [x] **Sleek UI/UX**: A smooth, intuitive, and mobile-responsive interface)
[//]: # (- [x] **Efficient Interaction**: Voice commands, OCR, and drag-and-drop file uploads)
[//]: # (- [x] **Privacy First**: Self-host and use your own API keys for full control)
[//]: # (- [x] **Advanced Tools**: Execute code, import PDFs, and summarize documents)
[//]: # (- [x] **Seamless Integrations**: Enhance functionality with various third-party services)
[//]: # (- [x] **Open Roadmap**: Contribute to the progress of big-AGI)
<br/>
# 🌟 Get Involved!
[//]: # ([![Official Discord]&#40;https://img.shields.io/discord/1098796266906980422?label=discord&logo=discord&logoColor=%23fff&style=for-the-badge&#41;]&#40;https://discord.gg/MkH4qj2Jp9&#41;)
[![Official Discord](https://discordapp.com/api/guilds/1098796266906980422/widget.png?style=banner2)](https://discord.gg/MkH4qj2Jp9)
* Enjoy the hosted open-source app on [big-AGI.com](https://big-agi.com)
* [Chat with us](https://discord.gg/MkH4qj2Jp9)
* Deploy your [fork](https://github.com/enricoros/big-agi/fork) for your friends and family
* send PRs! ...
🎭[Editing Personas](https://github.com/enricoros/big-agi/issues/35),
🧩[Reasoning Systems](https://github.com/enricoros/big-agi/issues/36),
🌐[Community Templates](https://github.com/enricoros/big-agi/issues/35),
and [your big-IDEAs](https://github.com/enricoros/big-agi/issues/new?labels=RFC&body=Describe+the+idea)
- [ ] 📢️ [**Chat with us** on Discord](https://discord.gg/MkH4qj2Jp9)
- [ ]**Give us a star** on GitHub 👆
- [ ] 🚀 **Do you like code**? You'll love this gem of a project! [_Pick up a task!_](https://github.com/users/enricoros/projects/4/views/4) - _easy_ to _pro_
- [ ] 💡 Got a feature suggestion? [_Add your roadmap ideas_](https://github.com/enricoros/big-agi/issues/new?&template=roadmap-request.md)
- [ ] ✨ Deploy your [fork](docs/customizations.md) for your friends and family, or [customize it for work](docs/customizations.md)
- [ ] Check out some of the big-AGI [**community projects**](docs/customizations.md)
| Project | Features | GitHub |
|---------|----------------------------------------------------|-------------------------------------------------------------------------------------|
| CoolAGI | Code Interpreter, Vision, Mind maps, and much more | [nextgen-user/CoolAGI](https://github.com/nextgen-user/CoolAGI) |
| HL-GPT | Fully remodeled UI | [harlanlewis/nextjs-chatgpt-app](https://github.com/harlanlewis/nextjs-chatgpt-app) |
<br/>
## 🧩 Develop
# 🧩 Develop
![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=&logo=typescript&logoColor=white)
![React](https://img.shields.io/badge/React-61DAFB?style=&logo=react&logoColor=black)
![Next.js](https://img.shields.io/badge/Next.js-000000?style=&logo=vercel&logoColor=white)
[//]: # (![TypeScript]&#40;https://img.shields.io/badge/TypeScript-007ACC?style=&logo=typescript&logoColor=white&#41;)
Clone this repo, install the dependencies, and run the development server:
[//]: # (![React]&#40;https://img.shields.io/badge/React-61DAFB?style=&logo=react&logoColor=black&#41;)
[//]: # (![Next.js]&#40;https://img.shields.io/badge/Next.js-000000?style=&logo=vercel&logoColor=white&#41;)
To download and run this Typescript/React/Next.js project locally, the only prerequisite is Node.js with the `npm` package manager.
Clone this repo, install the dependencies (all local), and run the development server (which auto-watches the
files for changes):
```bash
git clone https://github.com/enricoros/big-agi.git
cd big-agi
npm install
npm run dev
# You will see something like:
#
# ▲ Next.js 14.1.0
# - Local: http://localhost:3000
# ✓ Ready in 2.6s
```
The app will be running on `http://localhost:3000`
The development app will be running on `http://localhost:3000`. Development builds have the advantage of not requiring
a build step, but can be slower than production builds. Also, development builds won't have timeout on edge functions.
Integrations:
## 🛠️ Deploy from source
* Local models: Ollama, Oobabooga, LocalAi, etc.
* [ElevenLabs](https://elevenlabs.io/) Voice Synthesis (bring your own voice too) - Settings > Text To Speech
* [Helicone](https://www.helicone.ai/) LLM Observability Platform - Models > OpenAI > Advanced > API Host: 'oai.hconeai.com'
* [Paste.gg](https://paste.gg/) Paste Sharing - Chat Menu > Share via paste.gg
* [Prodia](https://prodia.com/) Image Generation - Settings > Image Generation > Api Key & Model
The _production_ build of the application is optimized for performance and is performed by the `npm run build` command,
after installing the required dependencies.
```bash
# .. repeat the steps above up to `npm install`, then:
npm run build
next start --port 3000
```
The app will be running on the specified port, e.g. `http://localhost:3000`.
Want to deploy with username/password? See the [Authentication](docs/deploy-authentication.md) guide.
## 🐳 Deploy with Docker
@@ -105,7 +196,7 @@ docker run -d -p 3000:3000 big-agi
Or run the official container:
- manually: `docker run -d -p 3000:3000 ghcr.io/enricoros/big-agi`
- or, with docker-compose: `docker-compose up`
- or, with docker-compose: `docker-compose up` or see [the documentation](docs/deploy-docker.md) for a composer file with integrated browsing
## ☁️ Deploy on Cloudflare Pages
@@ -115,19 +206,17 @@ Please refer to the [Cloudflare deployment documentation](docs/deploy-cloudflare
Create your GitHub fork, create a Vercel project over that fork, and deploy it. Or press the button below for convenience.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fenricoros%2Fbig-agi&env=OPENAI_API_KEY,OPENAI_API_HOST&envDescription=OpenAI%20KEY%20for%20your%20deployment.%20Set%20HOST%20only%20if%20non-default.)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fenricoros%2Fbig-AGI&env=OPENAI_API_KEY&envDescription=Backend%20API%20keys%2C%20optional%20and%20may%20be%20overridden%20by%20the%20UI.&envLink=https%3A%2F%2Fgithub.com%2Fenricoros%2Fbig-AGI%2Fblob%2Fmain%2Fdocs%2Fenvironment-variables.md&project-name=big-AGI)
[//]: # ([![GitHub stars]&#40;https://img.shields.io/github/stars/enricoros/big-agi&#41;]&#40;https://github.com/enricoros/big-agi/stargazers&#41;)
<br/>
[//]: # ([![GitHub forks]&#40;https://img.shields.io/github/forks/enricoros/big-agi&#41;]&#40;https://github.com/enricoros/big-agi/network&#41;)
This project is licensed under the MIT License.
[//]: # ([![GitHub pull requests]&#40;https://img.shields.io/github/issues-pr/enricoros/big-agi&#41;]&#40;https://github.com/enricoros/big-agi/pulls&#41;)
[![GitHub stars](https://img.shields.io/github/stars/enricoros/big-agi)](https://github.com/enricoros/big-agi/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/enricoros/big-agi)](https://github.com/enricoros/big-agi/network)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/enricoros/big-agi)](https://github.com/enricoros/big-agi/pulls)
[![License](https://img.shields.io/github/license/enricoros/big-agi)](https://github.com/enricoros/big-agi/LICENSE)
[//]: # ([![License]&#40;https://img.shields.io/github/license/enricoros/big-agi&#41;]&#40;https://github.com/enricoros/big-agi/LICENSE&#41;)
[//]: # ([![GitHub issues]&#40;https://img.shields.io/github/issues/enricoros/big-agi&#41;]&#40;https://github.com/enricoros/big-agi/issues&#41;)
---
Made with 💙
2023-2024 · Enrico Ros x [big-AGI](https://big-agi.com) · License: [MIT](LICENSE) · Made with 💙
+1 -51
View File
@@ -1,52 +1,2 @@
import { createEmptyReadableStream, safeErrorString, serverFetchOrThrow } from '~/server/wire';
import { elevenlabsAccess, elevenlabsVoiceId, ElevenlabsWire, speechInputSchema } from '~/modules/elevenlabs/elevenlabs.router';
/* NOTE: Why does this file even exist?
This file is a workaround for a limitation in tRPC; it does not support ArrayBuffer responses,
and that would force us to use base64 encoding for the audio data, which would be a waste of
bandwidth. So instead, we use this file to make the request to ElevenLabs, and then return the
response as an ArrayBuffer. Unfortunately this means duplicating the code in the server-side
and client-side vs. the tRPC implementation. So at lease we recycle the input structures.
*/
const handler = async (req: Request) => {
try {
// construct the upstream request
const {
elevenKey, text, voiceId, nonEnglish,
streaming, streamOptimization,
} = speechInputSchema.parse(await req.json());
const path = `/v1/text-to-speech/${elevenlabsVoiceId(voiceId)}` + (streaming ? `/stream?optimize_streaming_latency=${streamOptimization || 1}` : '');
const { headers, url } = elevenlabsAccess(elevenKey, path);
const body: ElevenlabsWire.TTSRequest = {
text: text,
...(nonEnglish && { model_id: 'eleven_multilingual_v1' }),
};
// elevenlabs POST
const upstreamResponse: Response = await serverFetchOrThrow(url, 'POST', headers, body);
// NOTE: this is disabled, as we pass-through what we get upstream for speed, as it is not worthy
// to wait for the entire audio to be downloaded before we send it to the client
// if (!streaming) {
// const audioArrayBuffer = await upstreamResponse.arrayBuffer();
// return new NextResponse(audioArrayBuffer, { status: 200, headers: { 'Content-Type': 'audio/mpeg' } });
// }
// stream the data to the client
const audioReadableStream = upstreamResponse.body || createEmptyReadableStream();
return new Response(audioReadableStream, { status: 200, headers: { 'Content-Type': 'audio/mpeg' } });
} catch (error: any) {
const fetchOrVendorError = safeErrorString(error) + (error?.cause ? ' · ' + error.cause : '');
console.log(`api/elevenlabs/speech: fetch issue: ${fetchOrVendorError}`);
return new Response(`[Issue] elevenlabs: ${fetchOrVendorError}`, { status: 500 });
}
};
export const runtime = 'edge';
export { handler as POST };
export { elevenLabsHandler as POST } from '~/modules/elevenlabs/elevenlabs.server';
+1 -1
View File
@@ -1,2 +1,2 @@
export const runtime = 'edge';
export { openaiStreamingRelayHandler as POST } from '~/modules/llms/transports/server/openai/openai.streaming';
export { llmStreamingRelayHandler as POST } from '~/modules/llms/server/llm.server.streaming';
+2 -2
View File
@@ -1,6 +1,6 @@
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouterEdge } from '~/server/api/trpc.router';
import { appRouterEdge } from '~/server/api/trpc.router-edge';
import { createTRPCFetchContext } from '~/server/api/trpc.server';
const handlerEdgeRoutes = (req: Request) =>
@@ -11,7 +11,7 @@ const handlerEdgeRoutes = (req: Request) =>
createContext: createTRPCFetchContext,
onError:
process.env.NODE_ENV === 'development'
? ({ path, error }) => console.error(`❌ tRPC-edge failed on ${path ?? '<no-path>'}:`, error)
? ({ path, error }) => console.error(`❌ tRPC-edge failed on ${path ?? "<no-path>"}: ${error.message}`)
: undefined,
});
+2 -2
View File
@@ -1,6 +1,6 @@
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouterNode } from '~/server/api/trpc.router';
import { appRouterNode } from '~/server/api/trpc.router-node';
import { createTRPCFetchContext } from '~/server/api/trpc.server';
const handlerNodeRoutes = (req: Request) =>
@@ -11,7 +11,7 @@ const handlerNodeRoutes = (req: Request) =>
createContext: createTRPCFetchContext,
onError:
process.env.NODE_ENV === 'development'
? ({ path, error }) => console.error(`❌ tRPC-node failed on ${path ?? '<no-path>'}:`, error)
? ({ path, error }) => console.error(`❌ tRPC-node failed on ${path ?? '<no-path>'}: ${error.message}`)
: undefined,
});
+5 -1
View File
@@ -1,8 +1,12 @@
# Very simple docker-compose file to run the app on http://localhost:3000 (or http://127.0.0.1:3000).
#
# For more examples, such runnin big-AGI alongside a web browsing service, see the `docs/docker` folder.
version: '3.9'
services:
big-agi:
image: ghcr.io/enricoros/big-agi:main
image: ghcr.io/enricoros/big-agi:latest
ports:
- "3000:3000"
env_file:
+65
View File
@@ -0,0 +1,65 @@
# big-AGI Documentation
Find all the information you need to get started, configure, and effectively use big-AGI.
[//]: # (## Quick Start)
[//]: # (- **[Introduction]&#40;big-agi.md&#41;**: Overview of big-AGI's features.)
## Configuration Guides
Detailed guides to configure your big-AGI interface and models.
👉 The following applies to the users of big-AGI.com, as the public instance is empty and to be configured by the user.
- **Cloud Model Services**:
- **[Azure OpenAI](config-azure-openai.md)**
- **[OpenRouter](config-openrouter.md)**
- easy API key: **Anthropic**, **Google AI**, **Groq**, **Mistral**, **OpenAI**, **Perplexity**, **TogetherAI**
- **Local Model Servers**:
- **[LocalAI](config-local-localai.md)**
- **[LM Studio](config-local-lmstudio.md)**
- **[Ollama](config-local-ollama.md)**
- **[Oobabooga](config-local-oobabooga.md)**
- **Advanced Feature Configuration**:
- **[Browse](config-feature-browse.md)**: Enable web page download through third-party services or your own cloud (advanced)
- **ElevenLabs API**: Voice and cutom voice generation, only requires their API key
- **Google Search API**: guide not yet available, see the Google options in 'Environment Variables'
- **Prodia API**: Stable Diffusion XL image generation, only requires their API key, alternative to DALL·E
## Deployment
System integrators, administrators, whitelabelers: instead of using the public big-AGI instance on get.big-agi.com, you can deploy your own instance.
Step-by-step deployment and system configuration instructions.
- **Deploy Your Own**
- straightforward: **Local development**, **Vercel 1-Click**
- **[Cloudflare Deployment](deploy-cloudflare.md)**
- **[Docker Deployment](deploy-docker.md)**: Containers for Local or Cloud deployments
- **Deployment Server Features**
- **[Database Setup](deploy-database.md)**: Optional, only required to enable "Chat Link Sharing"
- **[Environment Variables](environment-variables.md)**: 📌 Set server-side API keys and special features in your deployments
- **[HTTP Basic Authentication](deploy-authentication.md)**: Optional, Secure your big-AGI instance with a username and password
## Customization & Derivative UIs
👏 Customize big-AGI to fit your needs.
- **[Customizing big-AGI](customizations.md)**: how to alter source code and server-side configuration
## Support and Community
Join our community or get support:
- Visit our [GitHub repository](https://github.com/enricoros/big-AGI) for source code and issue tracking
- Check the latest updates and features on [Changelog](changelog.md) or the in-app [News](https://get.big-agi.com/news)
- Connect with us and other users on [Discord](https://discord.gg/MkH4qj2Jp9) for discussions, help, and sharing your experiences with big-AGI
Thank you for choosing big-AGI. We're excited to see what you'll build.
+108 -4
View File
@@ -2,10 +2,103 @@
This is a high-level changelog. Calls out some of the high level features batched
by release.
- For the live roadmap, please see [the GitHub project](https://github.com/users/enricoros/projects/4/views/2)
- For the live roadmap, please see [the GitHub project](https://github.com/users/enricoros/projects/4/views/2)
### ✨ What's New in 1.5.0 👊 - Nov 19, 2023
### 1.13.0 - Feb 2024
- milestone: [1.13.0](https://github.com/enricoros/big-agi/milestone/13)
- work in progress: [big-AGI open roadmap](https://github.com/users/enricoros/projects/4/views/2), [help here](https://github.com/users/enricoros/projects/4/views/4)
## What's New in 1.13.0 · Feb 8, 2024 · Multi + Mind
https://github.com/enricoros/big-AGI/assets/32999/01732528-730e-41dc-adc7-511385686b13
- **Side-by-Side Split Windows**: multitask with parallel conversations. [#208](https://github.com/enricoros/big-AGI/issues/208)
- **Multi-Chat Mode**: message everyone, all at once. [#388](https://github.com/enricoros/big-AGI/issues/388)
- **Export tables as CSV**: big thanks to @aj47. [#392](https://github.com/enricoros/big-AGI/pull/392)
- Adjustable text size: customize density. [#399](https://github.com/enricoros/big-AGI/issues/399)
- Dev2 Persona Technology Preview
- Better looking chats with improved spacing, fonts, and menus
- More: new video player, [LM Studio tutorial](https://github.com/enricoros/big-AGI/blob/main/docs/config-local-lmstudio.md) (thanks @aj47), [MongoDB support](https://github.com/enricoros/big-AGI/blob/main/docs/deploy-database.md) (thanks @ranfysvalle02), and speedups
## What's New in 1.12.0 · Jan 26, 2024 · AGI Hotline
https://github.com/enricoros/big-AGI/assets/32999/95ceb03c-945d-4fdd-9a9f-3317beb54f3f
- **Voice Calls**: real-time voice call your personas out of the blue or in relation to a chat [#354](https://github.com/enricoros/big-AGI/issues/354)
- Support **OpenAI 0125** Models. [#364](https://github.com/enricoros/big-AGI/issues/364)
- Rename or Auto-Rename chats. [#222](https://github.com/enricoros/big-AGI/issues/222), [#360](https://github.com/enricoros/big-AGI/issues/360)
- More control over **Link Sharing** [#356](https://github.com/enricoros/big-AGI/issues/356)
- **Accessibility** to screen readers [#358](https://github.com/enricoros/big-AGI/issues/358)
- Export chats to Markdown [#337](https://github.com/enricoros/big-AGI/issues/337)
- Paste tables from Excel [#286](https://github.com/enricoros/big-AGI/issues/286)
- Ollama model updates and context window detection fixes [#309](https://github.com/enricoros/big-AGI/issues/309)
### What's New in 1.11.0 · Jan 16, 2024 · Singularity
https://github.com/enricoros/big-AGI/assets/1590910/a6b8e172-0726-4b03-a5e5-10cfcb110c68
- **Find chats**: search in titles and content, with frequency ranking. [#329](https://github.com/enricoros/big-AGI/issues/329)
- **Commands**: command auto-completion (type '/'). [#327](https://github.com/enricoros/big-AGI/issues/327)
- **[Together AI](https://www.together.ai/products#inference)** inference platform support (good speed and newer models). [#346](https://github.com/enricoros/big-AGI/issues/346)
- Persona Creator history, deletion, custom creation, fix llm API timeouts
- Enable adding up to five custom OpenAI-compatible endpoints
- Developer enhancements: new 'Actiles' framework
### What's New in 1.10.0 · Jan 6, 2024 · The Year of AGI
- **New UI**: for both desktop and mobile, sets the stage for future scale. [#201](https://github.com/enricoros/big-AGI/issues/201)
- **Conversation Folders**: enhanced conversation organization. [#321](https://github.com/enricoros/big-AGI/issues/321)
- **[LM Studio](https://lmstudio.ai/)** support and improved token management
- Resizable panes in split-screen conversations.
- Large performance optimizations
- Developer enhancements: new UI framework, updated documentation for proxy settings on browserless/docker
### What's New in 1.9.0 · Dec 28, 2023 · Creative Horizons
- **DALL·E 3 integration** for enhanced image generation. [#212](https://github.com/enricoros/big-AGI/issues/212)
- **Perfect scrolling mechanics** across devices. [#304](https://github.com/enricoros/big-AGI/issues/304)
- Persona creation now supports **text input**. [#287](https://github.com/enricoros/big-AGI/pull/287)
- Openrouter updates for better model management and rate limit handling
- Image drawing UX improvements
- Layout fix for Firefox users
- Developer enhancements: Text2Image subsystem, Optima layout, ScrollToBottom library, Panes library, and Llms subsystem updates.
### What's New in 1.8.0 · Dec 20, 2023 · To The Moon And Back
- **Google Gemini Support**: Use the newest Google models. [#275](https://github.com/enricoros/big-agi/issues/275)
- **Mistral Platform**: Mixtral and future models support. [#273](https://github.com/enricoros/big-agi/issues/273)
- **Diagram Instructions**. Thanks to @joriskalz! [#280](https://github.com/enricoros/big-agi/pull/280)
- Ollama Chats: Enhanced chatting experience. [#270](https://github.com/enricoros/big-agi/issues/270)
- Mac Shortcuts Fix: Improved UX on Mac
- **Single-Tab Mode**: Data integrity with single window. [#268](https://github.com/enricoros/big-agi/issues/268)
- **Updated Models**: Latest Ollama (v0.1.17) and OpenRouter models
- Official Downloads: Easy access to the latest big-AGI on [big-AGI.com](https://big-agi.com)
- For developers: [troubleshot networking](https://github.com/enricoros/big-AGI/issues/276#issuecomment-1858591483), fixed Vercel deployment, cleaned up the LLMs/Streaming framework
### What's New in 1.7.0 · Dec 11, 2023 · Attachment Theory
- **Attachments System Overhaul**: Drag, paste, link, snap, text, images, PDFs and more. [#251](https://github.com/enricoros/big-agi/issues/251)
- **Desktop Webcam Capture**: Image capture now available as Labs feature. [#253](https://github.com/enricoros/big-agi/issues/253)
- **Independent Browsing**: Full browsing support with Browserless. [Learn More](https://github.com/enricoros/big-agi/blob/main/docs/config-feature-browse.md)
- **Overheat LLMs**: Push the creativity with higher LLM temperatures. [#256](https://github.com/enricoros/big-agi/issues/256)
- **Model Options Shortcut**: Quick adjust with `Ctrl+Shift+O`
- Optimized Voice Input and Performance
- Latest Ollama and Oobabooga models
- For developers: **Password Protection**: HTTP Basic Auth. [Learn How](https://github.com/enricoros/big-agi/blob/main/docs/deploy-authentication.md)
### What's New in 1.6.0 - Nov 28, 2023 · Surf's Up
- **Web Browsing**: Download web pages within chats - [browsing guide](https://github.com/enricoros/big-agi/blob/main/docs/config-feature-browse.md)
- **Branching Discussions**: Create new conversations from any message
- **Keyboard Navigation**: Swift chat navigation with new shortcuts (e.g. ctrl+alt+left/right)
- **Performance Boost**: Faster rendering for a smoother experience
- **UI Enhancements**: Refined interface based on user feedback
- **New Features**: Anthropic Claude 2.1, `/help` command, and Flattener tool
- **For Developers**: Code quality upgrades and snackbar notifications
### What's New in 1.5.0 - Nov 19, 2023 · Loaded
- **Continued Voice**: Engage with hands-free interaction for a seamless experience
- **Visualization Tool**: Create data representations with our new visualization capabilities
@@ -17,6 +110,17 @@ by release.
- **Cloudflare OpenAI API Gateway**: Integrate with Cloudflare for a robust API gateway
- **Helicone for Anthropic**: Utilize Helicone's tools for Anthropic models
For Developers:
- Runtime Server-Side configuration: https://github.com/enricoros/big-agi/issues/189. Env vars are
not required to be set at build time anymore. The frontend will roundtrip to the backend at the
first request to get the configuration. See
https://github.com/enricoros/big-agi/blob/main/src/modules/backend/backend.router.ts.
- CloudFlare developers: please change the deployment command to
`rm app/api/trpc-node/[trpc]/route.ts && npx @cloudflare/next-on-pages@1`,
as we transitioned to the App router in NextJS 14. The documentation in
[docs/deploy-cloudflare.md](../docs/deploy-cloudflare.md) is updated
### 1.4.0: Sept/Oct: scale OUT
- **Expanded Model Support**: Azure and [OpenRouter](https://openrouter.ai/docs#models) models, including gpt-4-32k
@@ -32,7 +136,7 @@ by release.
- **Backup/Restore** - save chats, and restore them later
- **[Local model support with Oobabooga server](../docs/config-local-oobabooga)** - run your own LLMs!
- **Flatten conversations** - conversations summarizer with 4 modes
- **Fork conversations** - create a new chat, to experiment with different endings
- **Fork conversations** - create a new chat, to try with different endings
- New commands: /s to add a System message, and /a for an Assistant message
- New Chat modes: Write-only - just appends the message, without assistant response
- Fix STOP generation - in sync with the Vercel team to fix a long-standing NextJS issue
@@ -60,7 +164,7 @@ by release.
- **[Install Mobile APP](../docs/pixels/feature_pwa.png)** 📲 looks like native (@harlanlewis)
- **[UI language](../docs/pixels/feature_language.png)** with auto-detect, and future app language! (@tbodyston)
- **PDF Summarization** 🧩🤯 - ask questions to a PDF! (@fredliubojin)
- **Code Execution: [Codepen](https://codepen.io/)/[Replit](https://replit.com/)** 💻 (@harlanlewis)
- **Code Execution: [Codepen](https://codepen.io/)** 💻 (@harlanlewis)
- **[SVG Drawing](../docs/pixels/feature_svg_drawing.png)** - draw with AI 🎨
- Chats: multiple chats, AI titles, Import/Export, Selection mode
- Rendering: Markdown, SVG, improved Code blocks
+118
View File
@@ -0,0 +1,118 @@
# Browse Functionality in big-AGI 🌐
Allows users to load web pages across various components of `big-AGI`. This feature is supported by Puppeteer-based
browsing services, which are the most common way to render web pages in a headless environment.
Once configured, the Browsing service provides the following functionality:
-**Paste a URL**: Simply paste/drag a URL into the chat, and `big-AGI` will load and attach the page (very effective)
-**Use /browse**: Type `/browse [URL]` in the chat to command `big-AGI` to load the specified web page
-**ReAct**: ReAct will automatically use the `loadURL()` function whenever a URL is encountered
It does not yet support the following functionality:
- ✖️ **Auto-browsing by LLMs**: if an LLM encounters a URL, it will NOT load the page and will likely respond
that it cannot browse the web - No technical limitation, just haven't gotten to implement this yet outside of `/react` yet
First of all, you need to procure a Puppteer web browsing service endpoint. `big-AGI` supports services like:
| Service | Working | Type | Location | Special Features |
|--------------------------------------------------------------------------------------|---------|-------------|----------------|---------------------------------------------|
| [BrightData Scraping Browser](https://brightdata.com/products/scraping-browser) | Yes | Proprietary | Cloud | Advanced scraping tools, global IP pool |
| [Cloudflare Browser Rendering](https://developers.cloudflare.com/browser-rendering/) | ? | Proprietary | Cloud | Integrated CDN, optimized browser rendering |
| ⬇️ [Browserless 2.0](#-browserless-20) | Okay | OpenSource | Local (Docker) | Parallelism, debug viewer, advanced APIs |
| ⬇️ [Your Chrome Browser (ALPHA)](#-your-own-chrome-browser) | Alpha | Proprietary | Local (Chrome) | Personal, experimental use (ALPHA!) |
| other Puppeteer-based WSS Services | ? | Varied | Cloud/Local | Service-specific features |
## Configuration
1. **Procure an Endpoint**
- Ensure that your browsing service is running (remote or local) and has a WebSocket endpoint available
- Write down the address: `wss://${auth}@{some host}:{port}`, or ws:// for local services on your machine
2. **Configure `big-AGI`**
- navigate to **Preferences** > **Tools** > **Browse**
- Enter the 'wss://...' connection string provided by your browsing service
3. **Enable Features**: Choose which browse-related features you want to enable:
- **Attach URLs**: Automatically load and attach a page when pasting a URL into the composer
- **/browse Command**: Use the `/browse` command in the chat to load a web page
- **ReAct**: Enable the `loadURL()` function in ReAct for advanced interactions
### 🌐 Browserless 2.0
[Browserless 2.0](https://github.com/browserless/browserless) is a Docker-based service that provides a headless
browsing experience compatible with `big-AGI`. An open-source solution that simplifies web automation tasks,
in a scalable manner.
Launch Browserless with:
```bash
docker run -p 9222:3000 browserless/chrome:latest
```
Now you can use the following connection string in `big-AGI`: `ws://127.0.0.1:9222`.
You can also browse to [http://127.0.0.1:9222](http://127.0.0.1:9222) to see the Browserless debug viewer
and configure some options.
The chat agent won't be able to access the web sites if the browserless container does not have direct Internet access. You can resolve the issue by defining internet proxy for the running container. You can then use the evironment file in the a `docker-compose.yaml
```
browserless:
image: browserless/chrome:latest
env_file:
- .env
ports:
- "9222:3000" # Map host's port 9222 to container's port 3000
environment:
- MAX_CONCURRENT_SESSIONS=10
```
You can then add the proyy lines to your `.env` file.
```
https_proxy=http://PROXY-IP:PROXY-PORT
http_proxy=http://PROXY-IP:PROXY-PORT
```
This is how you can define it in a one liner docker
`docker run --env https_proxy=http://PROXY-IP:PROXY-PORT --env http_proxy=http://PROXY-IP:PROXY-PORT -p 9222:3000 browserless/chrome:latest `
Note: if you are using `docker-compose`, please see the
[docker/docker-compose-browserless.yaml](docker/docker-compose-browserless.yaml) file for an example
on how to run `big-AGI` and Browserless simultaneously in a single application.
### 🌐 Your own Chrome browser
***EXPERIMENTAL - UNTESTED*** - You can use your own Chrome browser as a browsing service, by configuring it to expose
a WebSocket endpoint.
- close all the Chrome instances (on Windows, check the Task Manager if still running)
- start Chrome with the following command line options (on Windows, you can edit the shortcut properties):
- `--remote-debugging-port=9222`
- go to http://localhost:9222/json/version and copy the `webSocketDebuggerUrl` value
- it should be something like: `ws://localhost:9222/...`
- paste the value into the Endpoint configuration (see point 2 in the configuration)
### Server-Side Configuration
You can set the Puppeteer WebSocket endpoint (`PUPPETEER_WSS_ENDPOINT`) in the deployment before running it.
This is useful for self-hosted instances or when you want to pre-configure the endpoint for all users, and will
allow your to skip points 2 and 3 above.
Always deploy your own user authentication, authorization and security solution. For this feature, the tRPC
route that provides browsing service, shall be secured with a user authentication and authorization solution,
to prevent unauthorized access to the browsing service.
## Support
If you encounter any issues or have questions about configuring the browse functionality, join our community on Discord for support and discussions.
[![Official Discord](https://discordapp.com/api/guilds/1098796266906980422/widget.png?style=banner2)](https://discord.gg/MkH4qj2Jp9)
---
Enjoy the enhanced browsing experience within `big-AGI` and explore the web without ever leaving your chat!
Last updated on Feb 27, 2024 ([edit on GitHub](https://github.com/enricoros/big-AGI/edit/main/docs/config-feature-browse.md))
+51
View File
@@ -0,0 +1,51 @@
# Integrating LM Studio with big-AGI
Quickly set up LM Studio with big-AGI to run local and open LLMs on your computer for enhanced privacy and control over AI interactions.
## Video Tutorial
For a visual step-by-step guide, watch our [YouTube tutorial](https://www.youtube.com/watch?v=MqXzxVokMDk).
[![Running big-AGI locally with LM Studio YouTube Tutorial](http://img.youtube.com/vi/MqXzxVokMDk/0.jpg)](http://www.youtube.com/watch?v=MqXzxVokMDk "Running big-AGI locally with LM Studio")
## Quick Setup Guide
### Installing big-AGI
Clone and set up big-AGI:
```bash
git clone https://github.com/enricoros/big-agi.git && cd big-agi
npm install # Or: yarn install
npm run dev # Or: yarn dev
# If missing dependencies:
npm install @mui/material # Or: yarn add @mui/material
```
### Configuring LM Studio
Ensure LM Studio is running (default: [http://localhost:1234](http://localhost:1234)).
Check the URL and modify if different.
1. Download local models in LM Studio
2. Start the LM Studio server
3. Optionally. Check the logs
### Integration in big-AGI
1. In big-AGI, navigate to **Models** > **Add** > **LM Studio**
2. Enter the API URL: `http://localhost:1234` (modify if different)
3. Refresh by clicking on the `Models` button to load models from LM Studio
## Troubleshooting
- **Missing @mui/material**: Execute `npm install @mui/material` or `yarn add @mui/material`
- **Connection Issues**: Check LM Studio's URL and ensure it's operational
## Further Assistance
Advanced configurations and more:
- big-AGI Community: [Discord](https://discord.gg/MkH4qj2Jp9)
- LM Studio: [LM Studio home page](https://lmstudio.ai/)
+49 -22
View File
@@ -1,34 +1,61 @@
# Local LLM integration with `localai`
# Run your models with `LocalAI` x `big-AGI`
Integrate local Large Language Models (LLMs) with [LocalAI](https://localai.io).
[LocalAI](https://localai.io) lets you run your AI models locally, or in the cloud. It supports text, image, asr, speech, and more models.
_Last updated Nov 7, 2023_
We are deepening the integration between the two products. As of the time of writing, we integrate the following features:
## Instructions
- ✅ [Text generation](https://localai.io/features/text-generation/) with GPTs
- ✅ [Function calling](https://localai.io/features/openai-functions/) by GPTs 🆕
- ✅ [Model Gallery](https://localai.io/models/) to list and install models
- ✖️ [Vision API](https://localai.io/features/gpt-vision/) for image chats
- ✖️ [Image generation](https://localai.io/features/image-generation) with stable diffusion
- ✖️ [Audio to Text](https://localai.io/features/audio-to-text/)
- ✖️ [Text to Audio](https://localai.io/features/text-to-audio/)
- ✖️ [Embeddings generation](https://localai.io/features/embeddings/)
- ✖️ [Constrained grammars](https://localai.io/features/constrained_grammars/) (JSON output)
- ✖️ Voice cloning 🆕
_Last updated Feb 21, 2024_
## Guide
### LocalAI installation and configuration
Follow the guide at: https://localai.io/basics/getting_started/
For instance with [Use luna-ai-llama2 with docker compose](https://localai.io/basics/getting_started/#example-use-luna-ai-llama2-model-with-docker-compose):
- verify it works by browsing to [http://localhost:8080/v1/models](http://localhost:8080/v1/models)
(or the IP:Port of the machine, if running remotely) and seeing listed the model(s) you downloaded
listed in the JSON response.
- clone LocalAI
- get the model
- copy the prompt template
- start docker
- -> the server will be listening on `localhost:8080`
- verify it works by going to [http://localhost:8080/v1/models](http://localhost:8080/v1/models) on
your browser and seeing listed the model you downloaded
### Integrating LocalAI with big-AGI
### Integration: chat with LocalAI
- Go to Models > Add a model source of type: **LocalAI**
- Enter the address: `http://localhost:8080` (default)
- If running remotely, replace localhost with the IP of the machine. Make sure to use the **IP:Port** format
- Load the models
- Select model & Chat
- Enter the default address: `http://localhost:8080`, or the address of your localAI cloud instance
![configure models](pixels/config-localai-1-models.png)
- If running remotely, replace localhost with the IP of the machine. Make sure to use the **IP:Port** format
- Load the models (click on `Models 🔄`)
- Select the model and chat
> NOTE: LocalAI does not list details about the mdoels. Every model is assumed to be
> capable of chatting, and with a context window of 4096 tokens.
> Please update the [src/modules/llms/transports/server/openai/models.data.ts](../src/modules/llms/transports/server/openai/models.data.ts)
> file with the mapping information between LocalAI model IDs and names/descriptions/tokens, etc.
### Integration: Models Gallery
If the running LocalAI instance is configured with a [Model Gallery](https://localai.io/models/):
- Go to Models > LocalAI
- Click on `Gallery Admin`
- Select the models to install, and view installation progress
![img.png](pixels/config-localai-2-gallery.png)
## Troubleshooting
##### Unknown Context Window Size
At the time of writing, LocalAI does not publish the model `context window size`.
Every model is assumed to be capable of chatting, and with a context window of 4096 tokens.
Please update the [src/modules/llms/transports/server/openai/models.data.ts](../src/modules/llms/server/openai/models.data.ts)
file with the mapping information between LocalAI model IDs and names/descriptions/tokens, etc.
# 🤝 Support
- Hop into the [LocalAI Discord](https://discord.gg/uJAeKSAGDy) for support and questions
- Hop into the [big-AGI Discord](https://discord.gg/MkH4qj2Jp9) for questions
- For big-AGI support, please open an issue in our [big-AGI issue tracker](https://bit.ly/agi-request)
@@ -5,31 +5,46 @@ This guide helps you connect [Ollama](https://ollama.ai) [models](https://ollama
experience. The integration brings the popular big-AGI features to Ollama, including: voice chats,
editing tools, models switching, personas, and more.
_Last updated Dec 16, 2023_
![config-local-ollama-0-example.png](pixels/config-ollama-0-example.png)
## Quick Integration Guide
1. **Ensure Ollama API Server is Running**: Before starting, make sure your Ollama API server is up and running.
2. **Add Ollama as a Model Source**: In `big-AGI`, navigate to the **Models** section, select **Add a model source**, and choose **Ollama**.
3. **Enter Ollama Host URL**: Provide the Ollama Host URL where the API server is accessible (e.g., `http://localhost:11434`).
4. **Refresh Model List**: Once connected, refresh the list of available models to include the Ollama models.
5. **Start Using AI Personas**: Select an Ollama model and begin interacting with AI personas tailored to your needs.
1. **Ensure Ollama API Server is Running**: Follow the official instructions to get Ollama up and running on your machine
- For detailed instructions on setting up the Ollama API server, please refer to the
[Ollama download page](https://ollama.ai/download) and [instructions for linux](https://github.com/jmorganca/ollama/blob/main/docs/linux.md).
2. **Add Ollama as a Model Source**: In `big-AGI`, navigate to the **Models** section, select **Add a model source**, and choose **Ollama**
3. **Enter Ollama Host URL**: Provide the Ollama Host URL where the API server is accessible (e.g., `http://localhost:11434`)
4. **Refresh Model List**: Once connected, refresh the list of available models to include the Ollama models
> Optional: use the Ollama Admin interface to see which models are available and 'Pull' them in your local machine. Note
that this operation will likely timeout due to Edge Functions timeout on the big-AGI server while pulling, and
you'll have to press the 'Pull' button again, until a green message appears.
5. **Chat with Ollama models**: select an Ollama model and begin chatting with AI personas
### Ollama: installation and Setup
**Visual Configuration Guide**:
For detailed instructions on setting up the Ollama API server, please refer to the
[Ollama download page](https://ollama.ai/download) and [instructions for linux](https://github.com/jmorganca/ollama/blob/main/docs/linux.md).
* After adding the `Ollama` model vendor, entering the IP address of an Ollama server, and refreshing models:<br/>
<img src="pixels/config-ollama-1-models.png" alt="config-local-ollama-1-models.png" width="320">
### Visual Guide
* The `Ollama` admin panel, with the `Pull` button highlighted, after pulling the "Yi" model:<br/>
<img src="pixels/config-ollama-2-admin-pull.png" alt="config-local-ollama-2-admin-pull.png" width="320">
* After adding the `Ollama` model vendor, entering the IP address of an Ollama server, and refreshing models:
<img src="pixels/config-ollama-1-models.png" alt="config-local-ollama-1-models.png" style="max-width: 320px;">
* You can now switch model/persona dynamically and text/voice chat with the models:<br/>
<img src="pixels/config-ollama-3-chat.png" alt="config-local-ollama-3-chat.png" width="320">
* The `Ollama` admin panel, with the `Pull` button highlighted, after pulling the "Yi" model:
<img src="pixels/config-ollama-2-admin-pull.png" alt="config-local-ollama-2-admin-pull.png" style="max-width: 320px;">
<br/>
* You can now switch model/persona dynamically and text/voice chat with the models:
<img src="pixels/config-ollama-3-chat.png" alt="config-local-ollama-3-chat.png" style="max-width: 320px;">
### ⚠️ Network Troubleshooting
If you get errors about the server having trouble connecting with Ollama, please see
[this message](https://github.com/enricoros/big-AGI/issues/276#issuecomment-1858591483) on Issue #276.
And in brief, make sure the Ollama endpoint is accessible from the servers where you run big-AGI (which could
be localhost or cloud servers).
![Ollama Networking Chart](pixels/config-ollama-network.png)
<br/>
### Advanced: Model parameters
@@ -68,6 +83,8 @@ Then, edit the nginx configuration file `/etc/nginx/sites-enabled/default` and a
Reach out to our community if you need help with this.
<br/>
### Community and Support
Join our community to share your experiences, get help, and discuss best practices:
@@ -78,4 +95,4 @@ Join our community to share your experiences, get help, and discuss best practic
---
`big-AGI` is committed to providing a powerful, intuitive, and privacy-respecting AI experience.
We are excited for you to explore the possibilities with Ollama models. Happy creating!
We are excited for you to explore the possibilities with Ollama models. Happy creating!
+17 -10
View File
@@ -4,7 +4,7 @@ Integrate local Large Language Models (LLMs) with
[oobabooga/text-generation-webui](https://github.com/oobabooga/text-generation-webui),
a specialized interface that includes a custom variant of the OpenAI API for a smooth integration process.
_Last updated on Nov 7, 2023_
_Last updated on Dec 7, 2023_
### Components
@@ -20,26 +20,31 @@ This guide assumes that **big-AGI** is already installed on your system. Note th
### Text-web-ui Installation & Configuration:
1. Install [text-generation-webui](https://github.com/oobabooga/text-generation-webui#Installation).
- Download the one-click installer, extract it, and double-click on "start" - ~10 minutes
- Close it afterwards as we need to modify the startup flags
1. Install [text-generation-webui](https://github.com/oobabooga/text-generation-webui#Installation):
- Follow the instructions in the official page (basicall clone the repo and run a script) [~10 minutes]
- Stop the Web UI as we need to modify the startup flags to enable the OpenAI API
2. Enable the **openai extension**
- Edit `CMD_FLAGS.txt`
- Make sure that `--listen --extensions openai` is present and uncommented
- Make sure that `--listen --api` is present and uncommented
3. Restart text-generation-webui
- Double-click on "start"
- You should see something like:
```
2023-11-07 21:24:26 INFO:Loading the extension "openai"...
2023-11-07 21:24:27 INFO:OpenAI compatible API URL:
2023-12-07 21:51:21 INFO:Loading the extension "openai"...
2023-12-07 21:51:21 INFO:OpenAI-compatible API URL:
http://0.0.0.0:5000/v1
http://0.0.0.0:5000
...
INFO: Uvicorn running on http://0.0.0.0:5000 (Press CTRL+C to quit)
Running on local URL: http://0.0.0.0:7860
```
- The OpenAI API is now running on port 5000, on both localhost (127.0.0.1) and your network IP address
- This shows that:
- The Web UI is running on port 7860: http://127.0.0.1:7860
- **The OpenAI API is running on port 5000: http://127.0.0.1:5000**
4. Load your first model
- Open the text-generation-webui at [127.0.0.1:7860](http://127.0.0.1:7860/)
- Switch to the **Model** tab
- Download, for instance, `TheBloke/Llama-2-7b-Chat-GPTQ:gptq-4bit-32g-actorder_True` - 4.3 GB
- Download, for instance, `TheBloke/Llama-2-7B-Chat-GPTQ`
- Select the model once it's loaded
### Integrating text-web-ui with big-AGI:
@@ -51,4 +56,6 @@ This guide assumes that **big-AGI** is already installed on your system. Note th
- The active model must be selected and LOADED on the text-generation-webui as it doesn't support model switching or parallel requests.
- Select model & Chat
![config-oobabooga-0.png](pixels/config-oobabooga-0.png)
Enjoy the privacy and flexibility of local LLMs with `big-AGI` and `text-generation-webui`!
+67
View File
@@ -0,0 +1,67 @@
# Customizing and Creating Derivative Applications
This document outlines how to develop applications derived from big-AGI.
## Manual Customization
Application customization _requires manual code modifications or the use of environment variables_. Currently, **there is no admin panel to "managed" deployment customization** for enterprise use cases.
| Required Code Alteration | Not Required |
|---------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|
| - Persona changes<br>- UI theme customization<br>- Feature additions or modifications | - Setting API keys in [environment variables](environment-variables.md)<br>- Toggling features with environment variables |
| Apply these to the source code before building the application | Set these post-build on local machines or cloud deployment, before application launch |
<br/>
## Code Alterations
Start by creating a fork of the [big-AGI repository](https://github.com/enricoros/big-AGI) on GitHub for a personal development space.
Understand the Architecture: big-AGI uses Next.js, React for the front end, and Node.js (Next.js edge functions) for the back end.
### Add Authentication
This necessitates a code change (file renaming) before build initiation, detailed in [deploy-authentication.md](deploy-authentication.md).
### Change the Personas
Edit the `src/data.ts` file to customize personas. This file houses the default personas. You can add, remove, or modify these to meet your project's needs.
- [ ] Modify `src/data.ts` to alter default personas
### Change the UI
Adapt the UI to match your project's aesthetic, incorporate new features, or exclude unnecessary ones.
- [ ] Adjust `src/common/app.theme.ts` for theme changes: colors, spacing, button appearance, animations, etc
- [ ] Modify `src/common/app.config.tsx` to alter the application's name
- [ ] Update `src/common/app.nav.tsx` to revise the navigation bar
## Testing & Deployment
Test your application thoroughly using local development (refer to README.md for local build instructions). Deploy using your preferred hosting service. big-AGI supports deployment on platforms like Vercel, Docker, or any Node.js-compatible service, especially those supporting NextJS's "Edge Runtime."
- [deploy-cloudflare.md](deploy-cloudflare.md): for Cloudflare Workers deployment
- [deploy-docker.md](deploy-docker.md): for Docker deployment instructions and examples
<br/>
## Community Projects - Share Your Project
After deployment, share your project with the community. We will link to your project to help others discover and learn from your work.
| Project | Features | GitHub |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|
| 🚀 CoolAGI: Where AI meets Imagination<br/>![CoolAGI Logo](https://github.com/nextgen-user/freegpt4plus/assets/150797204/9b0e1232-4791-4d61-b949-16f9eb284c22) | Code Interpreter, Vision, Mind maps, Web Searches, Advanced Data Analytics, Large Data Handling and more! | [nextgen-user/CoolAGI](https://github.com/nextgen-user/CoolAGI) |
| HL-GPT | Fully remodeled UI | [harlanlewis/nextjs-chatgpt-app](https://github.com/harlanlewis/nextjs-chatgpt-app) |
For public projects, update your README.md with your modifications and submit a pull request to add your project to our list, aiding in its discovery.
<br/>
## Best Practices
- **Stay Updated**: Frequently merge updates from the main big-AGI repository to incorporate bug fixes and new features.
- **Keep It Open Source**: Consider maintaining your derivative as open source to foster community contributions.
- **Engage with the Community**: Leverage platforms like GitHub, Discord, or Reddit for feedback, collaboration, and project promotion.
Developing a derivative application is an opportunity to explore new possibilities with AI and share your innovations with the global community. We look forward to seeing your contributions.
+63
View File
@@ -0,0 +1,63 @@
# big-AGI Analytics
The open-source big-AGI project provides support for the following analytics services:
- **Vercel Analytics**: automatic when deployed to Vercel
- **Google Analytics 4**: manual setup required
The following is a quick overview of the Analytics options for the deployers of this open-source project.
big-AGI is deployed to many large-scale and enterprise though various ways (custom builds, Docker, Vercel, Cloudflare, etc.),
and this guide is for its customization.
## Service Configuration
### Vercel Analytics
- Why: understand coarse traction, and identify deployment issues - all without tracking individual users
- What: top pages, top referrers, country of origin, operating system, browser, and page speed metrics
Vercel Analytics and Speed Insights are local API endpoints deployed to your domain, so everything stays within your
domain. Furthermore, the Vercel Analytics service is privacy-friendly, and does not track individual users.
This service is avaialble to system administrators when deploying to Vercel. It is automatically enabled when deploying to Vercel.
The code that activates Vercel Analytics is located in the `src/pages/_app.tsx` file:
```tsx
const MyApp = ({ Component, emotionCache, pageProps }: MyAppProps) => <>
...
{isVercelFromFrontend && <VercelAnalytics debug={false} />}
{isVercelFromFrontend && <VercelSpeedInsights debug={false} sampleRate={1 / 2} />}
...
</>;
```
When big-AGI is served on Vercel hosts, the ```process.env.NEXT_PUBLIC_VERCEL_URL``` environment variable is trueish, and
analytics will be sent by default to the Vercel Analytics service which is deployed by Vercel IF configured from the
Vercel project dashboard.
In summary: to turn it on: activate the `Analytics` service in the Vercel project dashboard.
### Google Analytics 4
- Why: user engagement and retention, performance insights, personalization, content optimization
- What: https://support.google.com/analytics/answer/11593727
Google Analytics 4 (GA4) is a powerful tool for understanding user behavior and engagement.
This can help optimize big-AGI, understanding which features are needed/users and which aren't.
To enable Google Analytics 4, you need to set the `NEXT_PUBLIC_GA4_MEASUREMENT_ID` environment variable
before starting the local build or the docker build (i.e. at build time), at which point the
server/container will be able to report analytics to your Google Analytics 4 property.
As of Feb 27, 2024, this feature is in development.
## Configurations
| Scope | Default | Description / Instructions |
|-----------------------------------------------------------------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------|
| Your source builds of big-AGI | None | **Vercel**: enable Vercel Analytics from the dashboard. · **Google Analytics**: set environment variable at build time. |
| Your docker builds of big-AGI | None | **Vercel**: n/a. · **Google Analytics**: set environment variable at `docker build` time. |
| [big-agi.com](https://big-agi.com) | Vercel + Google | The main website ([privacy policy](https://big-agi.com/privacy)) hosted for free for anyone. |
| [official Docker packages](https://github.com/enricoros/big-AGI/pkgs/container/big-agi) | Google Analytics | **Vercel**: n/a · **Google Analytics**: set to the big-agi.com Google Analytics for analytics and improvements. |
Note: this information is updated as of Feb 27, 2024 and can change at any time.
+45
View File
@@ -0,0 +1,45 @@
# Authentication
`big-AGI` does not come with built-in authentication. To secure your deployment, you can implement authentication
in one of the following ways:
1. Build `big-AGI` with support for ⬇️ [HTTP Authentication](#http-authentication)
2. Utilize user authentication features provided by your ⬇️ [cloud deployment platform](#cloud-deployments-authentication)
3. Develop a custom authentication solution
<br/>
### HTTP Authentication
[HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) is a simple method
to secure your application.
To enable it in `big-AGI`, you **must manually build the application**:
- Build `big-AGI` with HTTP authentication enabled:
- Clone the repository
- Rename `middleware_BASIC_AUTH.ts` to `middleware.ts`
- Build: usual simple build procedure (e.g. [Deploy manually](../README.md#-deploy-manually) or [Deploying with Docker](deploy-docker.md))
- Configure the following [environment variables](environment-variables.md) before launching `big-AGI`:
```dotenv
HTTP_BASIC_AUTH_USERNAME=<your username>
HTTP_BASIC_AUTH_PASSWORD=<your password>
```
- Start the application 🔒
<br/>
### Cloud Deployments Authentication
> This approach allows you to enable authentication without rebuilding the application by using the features
> provided by your cloud platform to manage user accounts and access.
Many cloud deployment platforms offer built-in authentication mechanisms. Refer to the platform's documentation
for setup instructions:
1. [CloudFlare Access / Zero Trust](https://www.cloudflare.com/zero-trust/products/access/)
2. [Vercel Authentication](https://vercel.com/docs/security/deployment-protection/methods-to-protect-deployments/vercel-authentication)
3. [Vercel Password Protection](https://vercel.com/docs/security/deployment-protection/methods-to-protect-deployments/password-protection)
4. Let us know when you test more solutions (Heroku, AWS IAM, Google IAP, etc.)
+66
View File
@@ -0,0 +1,66 @@
**Connecting Your Database for Enhanced Features:**
This guide outlines the database options and setup steps for enabling features like Chat Link Sharing in your application.
### Choose Your Database:
**1. Serverless Postgres (default):**
- Available on Vercel, Neon, and other platforms.
- Less feature-rich but a suitable option depending on your needs.
- **Connection String:** Replace placeholders with your Postgres credentials.
- `postgres://USER:PASS@SOMEHOST.postgres.vercel-storage.com/SOMEDB?pgbouncer=true&connect_timeout=15`
**2. MongoDB Atlas (alternative):**
- **Highly Recommended:** More than a database, it's a data platform. MongoDB Atlas is a robust cloud-based platform that offers scalability, security, and a suite of developer tools. No need for a separate vector database, you can query your vector embeddings right within your operational database!
- **Additional Features:** MongoDB Atlas is packed with unique features designed to streamline the development process such as: Atlas App Services, Atlas search (with vector search), Atlas charts, Data Federation, and more.
- **Connection String:** Replace placeholders with your Atlas credentials.
- `mongodb://USER:PASS@CLUSTER-NAME.mongodb.net/DATABASE-NAME?retryWrites=true&w=majority`
### Environment Variables:
#### Postgres:
| Variable | |
|---------------------------------------|------------------------------------------------------------------------------------------------------|
| `POSTGRES_PRISMA_URL` | `postgres://USER:PASS@SOMEHOST.postgres.vercel-storage.com/SOMEDB?pgbouncer=true&connect_timeout=15` |
| `POSTGRES_URL_NON_POOLING` (optional) | URL for the Postgres database without pooling (specific use cases) |
#### MongoDB:
| Variable | |
|-----------|------------------------------------------------------------------------------------------|
| `MDB_URI` | `mongodb://USER:PASS@CLUSTER-NAME.mongodb.net/DATABASE-NAME?retryWrites=true&w=majority` |
### MongoDB Atlas + Prisma
When using MongoDB Atlas, you'll need to make the below changes to the file [`src/server/prisma/schema.prisma`](../src/server/prisma/schema.prisma).
```
...
datasource db {
provider = "mongodb"
url = env("MDB_URI")
}
//
// Storage of Linked Data
//
model LinkStorage {
id String @id @default(uuid()) @map("_id")
// ...rest of file
```
### Initial Setup Steps:
1. **Run `npx prisma db push`:** Create or update the database schema (run once after connecting).
### Additional Resources:
- Prisma documentation: [https://www.prisma.io/docs/](https://www.prisma.io/docs/)
- MongoDB Atlas: [https://www.mongodb.com/atlas/database](https://www.mongodb.com/atlas/database)
- Atlas App Services: [https://www.mongodb.com/docs/atlas/app-services/](https://www.mongodb.com/docs/atlas/app-services/)
- Atlas vector search: [https://www.mongodb.com/products/platform/atlas-vector-search/](https://www.mongodb.com/products/platform/atlas-vector-search)
- Atlas Data Federation: [https://www.mongodb.com/products/platform/atlas-data-federation](https://www.mongodb.com/products/platform/atlas-data-federation)
+37 -20
View File
@@ -21,33 +21,23 @@ Docker ensures faster development cycles, easier collaboration, and seamless env
```
4. Browse to [http://localhost:3000](http://localhost:3000)
## Documentation
<br/>
The big-AGI repository includes a Dockerfile and a GitHub Actions workflow for building and publishing a
Docker image of the application.
## Run Official Containers 📦
### Dockerfile
`big-AGI` is pre-built from source code and published as a Docker image on the GitHub Container Registry (ghcr).
The build process is transparent, and happens via GitHub Actions, as described in the
file.
The [`Dockerfile`](../Dockerfile) describes how to create a Docker image. It establishes a Node.js environment,
installs dependencies, and creates a production-ready version of the application as a local container.
### Official Images: [ghcr.io/enricoros/big-agi](https://github.com/enricoros/big-agi/pkgs/container/big-agi)
### Official container images
#### Run using *docker* 🚀
The [`.github/workflows/docker-image.yml`](../.github/workflows/docker-image.yml) file automates the
building and publishing of the Docker images to the GitHub Container Registry (ghcr) when changes are
pushed to the `main` branch.
Official pre-built containers: [ghcr.io/enricoros/big-agi](https://github.com/enricoros/big-agi/pkgs/container/big-agi)
Run official pre-built containers:
```bash
docker run -d -p 3000:3000 ghcr.io/enricoros/big-agi
docker run -d -p 3000:3000 ghcr.io/enricoros/big-agi:latest
```
### Run official containers
In addition, the repository also includes a `docker-compose.yaml` file, configured to run the pre-built
'ghcr image'. This file is used to define the `big-agi` service, the ports to expose, and the command to run.
#### Run using *docker-compose* 🚀
If you have Docker Compose installed, you can run the Docker container with `docker-compose up`
to pull the Docker image (if it hasn't been pulled already) and start a Docker container. If you want to
@@ -57,4 +47,31 @@ update the image to the latest version, you can run `docker-compose pull` before
docker-compose up -d
```
Leverage Docker's capabilities for a reliable and efficient big-AGI deployment.
### Make Local Services Visible to Docker 🌐
To make local services running on your host machine accessible to a Docker container, such as a
[Browseless](./config-feature-browse.md) service or a local API, you can follow this simplified guide:
| Operating System | Steps to Make Local Services Visible to Docker |
|:------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Windows and macOS | Use the special DNS name `host.docker.internal` to refer to the host machine from within the Docker container. No additional network configuration is required. Access local services using `host.docker.internal:<PORT>`. |
| Linux | Two options: *A*. Use <ins>--network="host"</ins> (`docker run --network="host" -d big-agi`) when running the Docker container to merge the container within the host network stack; however, this reduces container isolation. Alternatively: *B*. Connect to local services <ins>using the host's IP address</ins> directly, as host.docker.internal is not available by default on Linux. |
<br/>
### More Information
The [`Dockerfile`](../Dockerfile) describes how to create a Docker image. It establishes a Node.js environment,
installs dependencies, and creates a production-ready version of the application as a local container.
The [`docker-compose.yaml`](../docker-compose.yaml) file is configured to run the
official image (big-agi:latest). This file is used to define the `big-agi` service, to expose
port 3000 on the host, and launch big-AGI within the container (startup command).
The [`.github/workflows/docker-image.yml`](../.github/workflows/docker-image.yml) file is used
to build the Official Docker images and publish them to the GitHub Container Registry (ghcr).
The build process is transparent and happens via GitHub Actions.
<br/>
Leverage Docker's capabilities for a reliable and efficient big-AGI deployment!
@@ -0,0 +1,31 @@
# This file is used to run `big-AGI` and `browserless` with Docker Compose.
#
# The two containers are linked together and `big-AGI` is configured to use `browserless`
# as its Puppeteer endpoint (from the containers intranet, it is available browserless:3000).
#
# From your host, you can access big-AGI on http://127.0.0.1:3000 and browserless on http://127.0.0.1:9222.
#
# To start the containers, run:
# docker-compose -f docs/docker/docker-compose-browserless.yaml up
version: '3.9'
services:
big-agi:
image: ghcr.io/enricoros/big-agi:latest
ports:
- "3000:3000"
env_file:
- .env
environment:
- PUPPETEER_WSS_ENDPOINT=ws://browserless:3000
command: [ "next", "start", "-p", "3000" ]
depends_on:
- browserless
browserless:
image: browserless/chrome:latest
ports:
- "9222:3000" # Map host's port 9222 to container's port 3000
environment:
- MAX_CONCURRENT_SESSIONS=10
+14
View File
@@ -0,0 +1,14 @@
# Why big-AGI?
Placeholder for a document that demonstrates the productivity and unique features of Big-AGI.
## Exclusive features
- [x] Call AGI
- [x] Continuous Voice mode
- [x] Diagram generation
- [ ] ...
## Productivity Features
- [x] Multi-window to never wait
- [x] Multi-Chat to explore different solutions
- [x] Rendering of graphs, charts, mindmaps
- [ ] ...
+71 -31
View File
@@ -9,17 +9,16 @@ which take place over _defaults_. This file is kept in sync with [`../src/server
Environment variables can be set by creating a `.env` file in the root directory of the project.
> For Docker deployment, ensure all necessary environment variables are set **both during build and run**.
> If the Docker container is built without setting environment variables, the frontend UI will be unaware
> of them, despite the backend being able to use them at runtime.
The following is an example `.env` for copy-paste convenience:
```bash
# Database
# Database (Postgres)
POSTGRES_PRISMA_URL=
POSTGRES_URL_NON_POOLING=
# Database (MongoDB)
MDB_URI=
# LLMs
OPENAI_API_KEY=
OPENAI_API_HOST=
@@ -28,8 +27,15 @@ AZURE_OPENAI_API_ENDPOINT=
AZURE_OPENAI_API_KEY=
ANTHROPIC_API_KEY=
ANTHROPIC_API_HOST=
GEMINI_API_KEY=
GROQ_API_KEY=
LOCALAI_API_HOST=
LOCALAI_API_KEY=
MISTRAL_API_KEY=
OLLAMA_API_HOST=
OPENROUTER_API_KEY=
PERPLEXITY_API_KEY=
TOGETHERAI_API_KEY=
# Model Observability: Helicone
HELICONE_API_KEY=
@@ -43,22 +49,31 @@ PRODIA_API_KEY=
# Google Custom Search
GOOGLE_CLOUD_API_KEY=
GOOGLE_CSE_ID=
# Browse
PUPPETEER_WSS_ENDPOINT=
# Backend Analytics
BACKEND_ANALYTICS=
# Backend HTTP Basic Authentication (see `deploy-authentication.md` for turning on authentication)
HTTP_BASIC_AUTH_USERNAME=
HTTP_BASIC_AUTH_PASSWORD=
# Frontend variables
NEXT_PUBLIC_GA4_MEASUREMENT_ID=
NEXT_PUBLIC_PLANTUML_SERVER_URL=
```
## Variables Documentation
## Backend Variables
These variables are used only by the server-side code, at runtime. Define them before running the nextjs local server (in development or
cloud deployment), or pass them to Docker (--env-file or -e) when starting the container.
### Database
To enable features such as Chat Link Shring, you need to connect the backend to a database. We require
serverless Postgres, which is available on Vercel, Neon and more.
To enable Chat Link Sharing, you need to connect the backend to a database. We currently support Postgres and MongoDB.
Also make sure that you run `npx prisma db:push` to create the initial schema on the database for the
first time (or update it on a later stage).
| Variable | Description |
|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `POSTGRES_PRISMA_URL` | The URL of the Postgres database used by Prisma - example: `postgres://USER:PASS@SOMEHOST.postgres.vercel-storage.com/SOMEDB?pgbouncer=true&connect_timeout=15` |
| `POSTGRES_URL_NON_POOLING` | The URL of the Postgres database without pooling |
For Database configuration see [deploy-database.md](deploy-database.md).
### LLMs
@@ -74,10 +89,17 @@ requiring the user to enter an API key
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API key, see [config-azure-openai.md](config-azure-openai.md) | Optional, but if set `AZURE_OPENAI_API_ENDPOINT` must also be set |
| `ANTHROPIC_API_KEY` | The API key for Anthropic | Optional |
| `ANTHROPIC_API_HOST` | Changes the backend host for the Anthropic vendor, to enable platforms such as [config-aws-bedrock.md](config-aws-bedrock.md) | Optional |
| `OLLAMA_API_HOST` | Changes the backend host for the Ollama vendor. See [config-ollama.md](config-ollama.md) | |
| `GEMINI_API_KEY` | The API key for Google AI's Gemini | Optional |
| `GROQ_API_KEY` | The API key for Groq Cloud | Optional |
| `LOCALAI_API_HOST` | Sets the URL of the LocalAI server, or defaults to http://127.0.0.1:8080 | Optional |
| `LOCALAI_API_KEY` | The (Optional) API key for LocalAI | Optional |
| `MISTRAL_API_KEY` | The API key for Mistral | Optional |
| `OLLAMA_API_HOST` | Changes the backend host for the Ollama vendor. See [config-local-ollama.md](config-local-ollama) | |
| `OPENROUTER_API_KEY` | The API key for OpenRouter | Optional |
| `PERPLEXITY_API_KEY` | The API key for Perplexity | Optional |
| `TOGETHERAI_API_KEY` | The API key for Together AI | Optional |
### Model Observability: Helicone
### LLM Observability: Helicone
Helicone provides observability to your LLM calls. It is a paid service, with a generous free tier.
It is currently supported for:
@@ -89,23 +111,41 @@ It is currently supported for:
|--------------------|--------------------------|
| `HELICONE_API_KEY` | The API key for Helicone |
### Specials
### Features
Enable the app to Talk, Draw, and Google things up.
| Variable | Description |
|:-------------------------|:------------------------------------------------------------------------------------------------------------------------|
| **Text-To-Speech** | [ElevenLabs](https://elevenlabs.io/) is a high quality speech synthesis service |
| `ELEVENLABS_API_KEY` | ElevenLabs API Key - used for calls, etc. |
| `ELEVENLABS_API_HOST` | Custom host for ElevenLabs |
| `ELEVENLABS_VOICE_ID` | Default voice ID for ElevenLabs |
| **Google Custom Search** | [Google Programmable Search Engine](https://programmablesearchengine.google.com/about/) produces links to pages |
| `GOOGLE_CLOUD_API_KEY` | Google Cloud API Key, used with the '/react' command - [Link to GCP](https://console.cloud.google.com/apis/credentials) |
| `GOOGLE_CSE_ID` | Google Custom/Programmable Search Engine ID - [Link to PSE](https://programmablesearchengine.google.com/) |
| **Text-To-Image** | [Prodia](https://prodia.com/) is a reliable image generation service |
| `PRODIA_API_KEY` | Prodia API Key - used with '/imagine ...' |
| Variable | Description |
|:---------------------------|:------------------------------------------------------------------------------------------------------------------------|
| **Text-To-Speech** | [ElevenLabs](https://elevenlabs.io/) is a high quality speech synthesis service |
| `ELEVENLABS_API_KEY` | ElevenLabs API Key - used for calls, etc. |
| `ELEVENLABS_API_HOST` | Custom host for ElevenLabs |
| `ELEVENLABS_VOICE_ID` | Default voice ID for ElevenLabs |
| **Text-To-Image** | [Prodia](https://prodia.com/) is a reliable image generation service |
| `PRODIA_API_KEY` | Prodia API Key - used with '/imagine ...' |
| **Google Custom Search** | [Google Programmable Search Engine](https://programmablesearchengine.google.com/about/) produces links to pages |
| `GOOGLE_CLOUD_API_KEY` | Google Cloud API Key, used with the '/react' command - [Link to GCP](https://console.cloud.google.com/apis/credentials) |
| `GOOGLE_CSE_ID` | Google Custom/Programmable Search Engine ID - [Link to PSE](https://programmablesearchengine.google.com/) |
| **Browse** | |
| `PUPPETEER_WSS_ENDPOINT` | Puppeteer WebSocket endpoint - used for browsing (pade downloadeing), etc. |
| **Backend** | |
| `BACKEND_ANALYTICS` | Semicolon-separated list of analytics flags (see backend.analytics.ts). Flags: `domain` logs the responding domain. |
| `HTTP_BASIC_AUTH_USERNAME` | See the [Authentication](deploy-authentication.md) guide. Username for HTTP Basic Authentication. |
| `HTTP_BASIC_AUTH_PASSWORD` | Password for HTTP Basic Authentication. |
### Frontend Variables
The value of these variables are passed to the frontend (Web UI) - make sure they do not contain secrets.
| Variable | Description |
|:----------------------------------|:-----------------------------------------------------------------------------------------|
| `NEXT_PUBLIC_GA4_MEASUREMENT_ID` | The measurement ID for Google Analytics 4. (see [deploy-analytics](deploy-analytics.md)) |
| `NEXT_PUBLIC_PLANTUML_SERVER_URL` | The URL of the PlantUML server, used for rendering UML diagrams. (code in RederCode.tsx) |
> Important: these variables must be set at build time, which is required by Next.js to pass them to the frontend.
> This is in contrast to the backend variables, which can be set when starting the local server/container.
---
For a higher level overview of backend code and environemnt customization,
see the [big-AGI Customization](customizations.md) guide.
Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 KiB

+59
View File
@@ -0,0 +1,59 @@
/**
* Middleware to protect `big-AGI` with HTTP Basic Authentication
*
* For more information on how to deploy with HTTP Basic Authentication, see:
* - [deploy-authentication.md](docs/deploy-authentication.md)
*/
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
// noinspection JSUnusedGlobalSymbols
export function middleware(request: NextRequest) {
// Validate deployment configuration
if (!process.env.HTTP_BASIC_AUTH_USERNAME || !process.env.HTTP_BASIC_AUTH_PASSWORD) {
console.warn('HTTP Basic Authentication is enabled but not configured');
return new Response('Unauthorized/Unconfigured', unauthResponse);
}
// Request client authentication if no credentials are provided
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Basic '))
return new Response('Unauthorized', unauthResponse);
// Request authentication if credentials are invalid
const base64Credentials = authHeader.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
const [username, password] = credentials.split(':');
if (
!username || !password ||
username !== process.env.HTTP_BASIC_AUTH_USERNAME ||
password !== process.env.HTTP_BASIC_AUTH_PASSWORD
)
return new Response('Unauthorized', unauthResponse);
return NextResponse.next();
}
// Response to send when authentication is required
const unauthResponse: ResponseInit = {
status: 401,
headers: {
'WWW-Authenticate': 'Basic realm="Secure big-AGI"',
},
};
export const config = {
matcher: [
// Include root
'/',
// Include pages
'/(call|index|news|personas|link)(.*)',
// Include API routes
'/api(.*)',
// Note: this excludes _next, /images etc..
],
};
+40 -6
View File
@@ -1,13 +1,31 @@
// Non-default build types
const buildType =
process.env.BIG_AGI_BUILD === 'standalone' ? 'standalone'
: process.env.BIG_AGI_BUILD === 'static' ? 'export'
: undefined;
buildType && console.log(` 🧠 big-AGI: building for ${buildType}...\n`);
/** @type {import('next').NextConfig} */
let nextConfig = {
reactStrictMode: true,
// Note: disabled to chech whether the project becomes slower with this
// modularizeImports: {
// '@mui/icons-material': {
// transform: '@mui/icons-material/{{member}}',
// },
// },
// [exports] https://nextjs.org/docs/advanced-features/static-html-export
...buildType && {
output: buildType,
distDir: 'dist',
// disable image optimization for exports
images: { unoptimized: true },
// Optional: Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html`
// trailingSlash: true,
},
// [puppeteer] https://github.com/puppeteer/puppeteer/issues/11052
experimental: {
serverComponentsExternalPackages: ['puppeteer-core'],
},
webpack: (config, _options) => {
// @mui/joy: anything material gets redirected to Joy
@@ -19,8 +37,24 @@ let nextConfig = {
layers: true,
};
// prevent too many small chunks (40kb min) on 'client' packs (not 'server' or 'edge-server')
if (typeof config.optimization.splitChunks === 'object' && config.optimization.splitChunks.minSize)
config.optimization.splitChunks.minSize = 40 * 1024;
return config;
},
// Note: disabled to check whether the project becomes slower with this
// modularizeImports: {
// '@mui/icons-material': {
// transform: '@mui/icons-material/{{member}}',
// },
// },
// Uncomment the following leave console messages in production
// compiler: {
// removeConsole: false,
// },
};
// Validate environment variables, if set at build time. Will be actually read and used at runtime.
+2341 -710
View File
File diff suppressed because it is too large Load Diff
+50 -33
View File
@@ -1,68 +1,85 @@
{
"name": "big-agi",
"version": "1.5.0",
"version": "1.13.0",
"private": true,
"author": "Enrico Ros <enrico.ros@gmail.com>",
"repository": "https://github.com/enricoros/big-agi",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"env:pull": "npx vercel env pull .env.development.local",
"postinstall": "prisma generate",
"db:push": "prisma db push",
"db:studio": "prisma studio"
"db:studio": "prisma studio",
"vercel:env:pull": "npx vercel env pull .env.development.local"
},
"prisma": {
"schema": "src/server/prisma/schema.prisma"
},
"dependencies": {
"@dqbd/tiktoken": "^1.0.7",
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.1",
"@emotion/react": "^11.11.3",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.16",
"@mui/joy": "^5.0.0-beta.15",
"@next/bundle-analyzer": "^14.0.3",
"@prisma/client": "^5.6.0",
"@mui/icons-material": "^5.15.11",
"@mui/joy": "^5.0.0-beta.29",
"@next/bundle-analyzer": "^14.1.0",
"@next/third-parties": "^14.1.0",
"@prisma/client": "^5.10.2",
"@sanity/diff-match-patch": "^3.1.1",
"@t3-oss/env-nextjs": "^0.7.1",
"@tanstack/react-query": "^4.36.1",
"@trpc/client": "^10.43.3",
"@trpc/next": "^10.43.3",
"@trpc/react-query": "^10.43.3",
"@trpc/server": "^10.43.3",
"@vercel/analytics": "^1.1.1",
"@t3-oss/env-nextjs": "^0.9.2",
"@tanstack/react-query": "~4.36.1",
"@trpc/client": "10.44.1",
"@trpc/next": "10.44.1",
"@trpc/react-query": "10.44.1",
"@trpc/server": "10.44.1",
"@vercel/analytics": "^1.2.2",
"@vercel/speed-insights": "^1.0.10",
"browser-fs-access": "^0.35.0",
"eventsource-parser": "^1.1.1",
"eventsource-parser": "^1.1.2",
"idb-keyval": "^6.2.1",
"next": "^14.0.3",
"pdfjs-dist": "4.0.189",
"next": "^14.1.0",
"nprogress": "^0.2.0",
"pdfjs-dist": "4.0.379",
"plantuml-encoder": "^1.4.0",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-csv": "^2.2.2",
"react-dom": "^18.2.0",
"react-katex": "^3.0.1",
"react-markdown": "^9.0.0",
"react-markdown": "^9.0.1",
"react-player": "^2.14.1",
"react-resizable-panels": "^2.0.11",
"react-timeago": "^7.2.0",
"remark-gfm": "^4.0.0",
"sharp": "^0.33.2",
"superjson": "^2.2.1",
"tesseract.js": "^5.0.3",
"tesseract.js": "^5.0.5",
"tiktoken": "^1.0.13",
"uuid": "^9.0.1",
"zod": "^3.22.4",
"zustand": "~4.3.9"
"zustand": "^4.5.1"
},
"devDependencies": {
"@types/node": "^20.9.2",
"@cloudflare/puppeteer": "^0.0.5",
"@types/node": "^20.11.20",
"@types/nprogress": "^0.2.3",
"@types/plantuml-encoder": "^1.4.2",
"@types/prismjs": "^1.26.3",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/react-katex": "^3.0.3",
"@types/react-timeago": "^4.1.6",
"@types/uuid": "^9.0.7",
"eslint": "^8.54.0",
"eslint-config-next": "^14.0.3",
"prettier": "^3.1.0",
"prisma": "^5.6.0",
"typescript": "^5.2.2"
"@types/react": "^18.2.59",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-csv": "^1.1.10",
"@types/react-dom": "^18.2.19",
"@types/react-katex": "^3.0.4",
"@types/react-timeago": "^4.1.7",
"@types/uuid": "^9.0.8",
"eslint": "^8.57.0",
"eslint-config-next": "^14.1.0",
"prettier": "^3.2.5",
"prisma": "^5.10.2",
"typescript": "^5.3.3"
},
"engines": {
"node": "^20.0.0 || ^18.0.0"
+26 -10
View File
@@ -1,7 +1,8 @@
import * as React from 'react';
import Head from 'next/head';
import { MyAppProps } from 'next/app';
import { Analytics as VercelAnalytics } from '@vercel/analytics/react';
import { Analytics as VercelAnalytics } from '@vercel/analytics/next';
import { SpeedInsights as VercelSpeedInsights } from '@vercel/speed-insights/next';
import { Brand } from '~/common/app.config';
import { apiQuery } from '~/common/util/trpc.client';
@@ -9,10 +10,17 @@ import { apiQuery } from '~/common/util/trpc.client';
import 'katex/dist/katex.min.css';
import '~/common/styles/CodePrism.css';
import '~/common/styles/GithubMarkdown.css';
import '~/common/styles/NProgress.css';
import '~/common/styles/app.styles.css';
import { ProviderBackend } from '~/common/state/ProviderBackend';
import { ProviderTRPCQueryClient } from '~/common/state/ProviderTRPCQueryClient';
import { ProviderTheming } from '~/common/state/ProviderTheming';
import { ProviderBackendAndNoSSR } from '~/common/providers/ProviderBackendAndNoSSR';
import { ProviderBootstrapLogic } from '~/common/providers/ProviderBootstrapLogic';
import { ProviderSingleTab } from '~/common/providers/ProviderSingleTab';
import { ProviderSnacks } from '~/common/providers/ProviderSnacks';
import { ProviderTRPCQueryClient } from '~/common/providers/ProviderTRPCQueryClient';
import { ProviderTheming } from '~/common/providers/ProviderTheming';
import { hasGoogleAnalytics, OptionalGoogleAnalytics } from '~/common/components/GoogleAnalytics';
import { isVercelFromFrontend } from '~/common/util/pwaUtils';
const MyApp = ({ Component, emotionCache, pageProps }: MyAppProps) =>
@@ -24,14 +32,22 @@ const MyApp = ({ Component, emotionCache, pageProps }: MyAppProps) =>
</Head>
<ProviderTheming emotionCache={emotionCache}>
<ProviderTRPCQueryClient>
<ProviderBackend>
<Component {...pageProps} />
</ProviderBackend>
</ProviderTRPCQueryClient>
<ProviderSingleTab>
<ProviderBootstrapLogic>
<ProviderTRPCQueryClient>
<ProviderSnacks>
<ProviderBackendAndNoSSR>
<Component {...pageProps} />
</ProviderBackendAndNoSSR>
</ProviderSnacks>
</ProviderTRPCQueryClient>
</ProviderBootstrapLogic>
</ProviderSingleTab>
</ProviderTheming>
<VercelAnalytics debug={false} />
{isVercelFromFrontend && <VercelAnalytics debug={false} />}
{isVercelFromFrontend && <VercelSpeedInsights debug={false} sampleRate={1 / 2} />}
{hasGoogleAnalytics && <OptionalGoogleAnalytics />}
</>;
+6 -6
View File
@@ -5,7 +5,7 @@ import createEmotionServer from '@emotion/server/create-instance';
import { getInitColorSchemeScript } from '@mui/joy/styles';
import { Brand } from '~/common/app.config';
import { bodyFontClassName, createEmotionCache } from '~/common/app.theme';
import { createEmotionCache } from '~/common/app.theme';
interface MyDocumentProps extends DocumentProps {
@@ -14,7 +14,7 @@ interface MyDocumentProps extends DocumentProps {
export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
return (
<Html lang='en' className={bodyFontClassName}>
<Html lang='en'>
<Head>
{/* Meta (missing Title, set by the App or Page) */}
<meta name='description' content={Brand.Meta.Description} />
@@ -24,7 +24,7 @@ export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
<link rel='shortcut icon' href='/favicon.ico' />
<link rel='icon' type='image/png' sizes='32x32' href='/icons/favicon-32x32.png' />
<link rel='icon' type='image/png' sizes='16x16' href='/icons/favicon-16x16.png' />
<link rel='apple-touch-icon' sizes='180x180' href='/icons/apple-touch-icon.png' />
<link rel='apple-touch-icon' sizes='180x180' href='/apple-touch-icon.png' />
<link rel='manifest' href='/manifest.json' />
<meta name='apple-mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-status-bar-style' content='black' />
@@ -51,9 +51,9 @@ export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
{emotionStyleTags}
</Head>
<body>
{getInitColorSchemeScript()}
<Main />
<NextScript />
{getInitColorSchemeScript()}
<Main />
<NextScript />
</body>
</Html>
);
+2 -6
View File
@@ -2,13 +2,9 @@ import * as React from 'react';
import { AppCall } from '../src/apps/call/AppCall';
import { AppLayout } from '~/common/layout/AppLayout';
import { withLayout } from '~/common/layout/withLayout';
export default function CallPage() {
return (
<AppLayout>
<AppCall />
</AppLayout>
);
return withLayout({ type: 'optima' }, <AppCall />);
}
+10
View File
@@ -0,0 +1,10 @@
import * as React from 'react';
import { AppDraw } from '../src/apps/draw/AppDraw';
import { withLayout } from '~/common/layout/withLayout';
export default function DrawPage() {
return withLayout({ type: 'optima' }, <AppDraw />);
}
+6 -10
View File
@@ -1,18 +1,14 @@
import * as React from 'react';
import { AppChat } from '../src/apps/chat/AppChat';
import { useShowNewsOnUpdate } from '../src/apps/news/news.hooks';
import { AppLayout } from '~/common/layout/AppLayout';
import { withLayout } from '~/common/layout/withLayout';
export default function ChatPage() {
// show the News page on updates
useShowNewsOnUpdate();
export default function IndexPage() {
return (
<AppLayout>
<AppChat />
</AppLayout>
);
// TODO: This Index page will point to the Dashboard (or a landing page)
// For now it offers the chat experience, but this will change. #299
return withLayout({ type: 'optima' }, <AppChat />);
}
+167
View File
@@ -0,0 +1,167 @@
import * as React from 'react';
import { fileSave } from 'browser-fs-access';
import { Box, Button, Card, CardContent, Typography } from '@mui/joy';
import DownloadIcon from '@mui/icons-material/Download';
import { AppPlaceholder } from '../../src/apps/AppPlaceholder';
import { backendCaps } from '~/modules/backend/state-backend';
import { getPlantUmlServerUrl } from '~/modules/blocks/code/RenderCode';
import { withLayout } from '~/common/layout/withLayout';
// app config
import { Brand } from '~/common/app.config';
import { ROUTE_APP_CHAT, ROUTE_INDEX } from '~/common/app.routes';
// apps access
import { incrementalNewsVersion } from '../../src/apps/news/news.version';
// capabilities access
import { useCapabilityBrowserSpeechRecognition, useCapabilityElevenLabs, useCapabilityTextToImage } from '~/common/components/useCapabilities';
// stores access
import { getLLMsDebugInfo } from '~/modules/llms/store-llms';
import { useAppStateStore } from '~/common/state/store-appstate';
import { useChatStore } from '~/common/state/store-chats';
import { useFolderStore } from '~/common/state/store-folders';
import { useUXLabsStore } from '~/common/state/store-ux-labs';
// utils access
import { clientHostName, isChromeDesktop, isFirefox, isIPhoneUser, isMacUser, isPwa, isVercelFromFrontend } from '~/common/util/pwaUtils';
import { getGA4MeasurementId } from '~/common/components/GoogleAnalytics';
import { supportsClipboardRead } from '~/common/util/clipboardUtils';
import { supportsScreenCapture } from '~/common/util/screenCaptureUtils';
function DebugCard(props: { title: string, children: React.ReactNode }) {
return (
<Box>
<Typography level='title-lg'>
{props.title}
</Typography>
{props.children}
</Box>
);
}
function prettifyJsonString(jsonString: string, deleteChars: number, removeDoubleQuotes: boolean, removeTrailComma: boolean): string {
return jsonString.split('\n').map(l => {
if (deleteChars > 0)
l = l.substring(deleteChars);
if (removeDoubleQuotes)
l = l.replaceAll('\"', '');
if (removeTrailComma && l.endsWith(','))
l = l.substring(0, l.length - 1);
return l;
}).join('\n').trim();
}
function DebugJsonCard(props: { title: string, data: any }) {
return (
<DebugCard title={props.title}>
<Typography level='body-sm' sx={{ whiteSpace: 'break-spaces', fontFamily: 'code', fontSize: { xs: 'xs' } }}>
{prettifyJsonString(JSON.stringify(props.data, null, 2), 2, true, true)}
</Typography>
</DebugCard>
);
}
function AppDebug() {
// state
const [saved, setSaved] = React.useState(false);
// external state
const backendCapabilities = backendCaps();
const chatsCount = useChatStore.getState().conversations?.length;
const uxLabsExperiments = Object.entries(useUXLabsStore.getState()).filter(([_k, v]) => v === true).map(([k, _]) => k).join(', ');
const { folders, enableFolders } = useFolderStore.getState();
const { lastSeenNewsVersion, usageCount } = useAppStateStore.getState();
// derived state
const cClient = {
// isBrowser,
isChromeDesktop,
isFirefox,
isIPhone: isIPhoneUser,
isMac: isMacUser,
isPWA: isPwa(),
supportsClipboardPaste: supportsClipboardRead,
supportsScreenCapture,
};
const cProduct = {
capabilities: {
mic: useCapabilityBrowserSpeechRecognition(),
elevenLabs: useCapabilityElevenLabs(),
textToImage: useCapabilityTextToImage(),
},
models: getLLMsDebugInfo(),
state: {
chatsCount,
foldersCount: folders?.length,
foldersEnabled: enableFolders,
newsCurrent: incrementalNewsVersion,
newsSeen: lastSeenNewsVersion,
labsActive: uxLabsExperiments,
reloads: usageCount,
},
};
const cBackend = {
configuration: backendCapabilities,
deployment: {
home: Brand.URIs.Home,
hostName: clientHostName(),
isVercelFromFrontend,
measurementId: getGA4MeasurementId(),
plantUmlServerUrl: getPlantUmlServerUrl(),
routeIndex: ROUTE_INDEX,
routeChat: ROUTE_APP_CHAT,
},
};
const handleDownload = async () => {
fileSave(
new Blob([JSON.stringify({ client: cClient, agi: cProduct, backend: cBackend }, null, 2)], { type: 'application/json' }),
{ fileName: `big-agi-debug-${new Date().toISOString().replace(/:/g, '-')}.json`, extensions: ['.json'] },
)
.then(() => setSaved(true))
.catch(e => console.error('Error saving debug.json', e));
};
return (
<AppPlaceholder title={`${Brand.Title.Common} Debug`}>
<Box sx={{ display: 'grid', gap: 3, my: 3 }}>
<Button
variant={saved ? 'soft' : 'outlined'} color={saved ? 'success' : 'neutral'}
onClick={handleDownload}
endDecorator={<DownloadIcon />}
sx={{
backgroundColor: saved ? undefined : 'background.surface',
boxShadow: 'sm',
placeSelf: 'start',
minWidth: 260,
}}
>
Download debug JSON
</Button>
<Card>
<CardContent sx={{ display: 'grid', gap: 3 }}>
<DebugJsonCard title='Client' data={cClient} />
<DebugJsonCard title='AGI' data={cProduct} />
<DebugJsonCard title='Backend' data={cBackend} />
</CardContent>
</Card>
</Box>
</AppPlaceholder>
);
}
export default function DebugPage() {
return withLayout({ type: 'plain' }, <AppDebug />);
};
-14
View File
@@ -1,14 +0,0 @@
import * as React from 'react';
import { AppLabs } from '../src/apps/labs/AppLabs';
import { AppLayout } from '~/common/layout/AppLayout';
export default function LabsPage() {
return (
<AppLayout suspendAutoModelsSetup>
<AppLabs />
</AppLayout>
);
}
+90
View File
@@ -0,0 +1,90 @@
import * as React from 'react';
import { Box, Typography } from '@mui/joy';
import { useModelsStore } from '~/modules/llms/store-llms';
import { InlineError } from '~/common/components/InlineError';
import { apiQuery } from '~/common/util/trpc.client';
import { navigateToIndex, useRouterQuery } from '~/common/app.routes';
import { withLayout } from '~/common/layout/withLayout';
function CallbackOpenRouterPage(props: { openRouterCode: string | undefined }) {
// external state
const { data, isError, error, isLoading } = apiQuery.backend.exchangeOpenRouterKey.useQuery({ code: props.openRouterCode || '' }, {
enabled: !!props.openRouterCode,
refetchOnWindowFocus: false,
staleTime: Infinity,
});
// derived state
const isErrorInput = !props.openRouterCode;
const openRouterKey = data?.key ?? undefined;
const isSuccess = !!openRouterKey;
// Success: save the key and redirect to the chat app
React.useEffect(() => {
if (!isSuccess)
return;
// 1. Save the key as the client key
useModelsStore.getState().setOpenRoutersKey(openRouterKey);
// 2. Navigate to the chat app
void navigateToIndex(true); //.then(openModelsSetup);
}, [isSuccess, openRouterKey]);
return (
<Box sx={{
flexGrow: 1,
overflowY: 'auto',
display: 'flex', justifyContent: 'center',
p: { xs: 3, md: 6 },
}}>
<Box sx={{
// my: 'auto',
display: 'flex', flexDirection: 'column', alignItems: 'center',
gap: 4,
}}>
<Typography level='title-lg'>
Welcome Back
</Typography>
{isLoading && <Typography level='body-sm'>Loading...</Typography>}
{isErrorInput && <InlineError error='There was an issue retrieving the code from OpenRouter.' />}
{isError && <InlineError error={error} />}
{data && (
<Typography level='body-md'>
Success! You can now close this window.
</Typography>
)}
</Box>
</Box>
);
}
/**
* This page will be invoked by OpenRouter as a Callback
*
* Docs: https://openrouter.ai/docs#oauth
* Example URL: https://localhost:3000/link/callback_openrouter?code=SomeCode
*/
export default function CallbackPage() {
// external state - get the 'code=...' from the URL
const { code } = useRouterQuery<{ code: string | undefined }>();
return withLayout({ type: 'plain' }, <CallbackOpenRouterPage openRouterCode={code} />);
}
+7 -10
View File
@@ -1,18 +1,15 @@
import * as React from 'react';
import { useRouter } from 'next/router';
import { AppChatLink } from '../../../src/apps/link/AppChatLink';
import { AppLinkChat } from '../../../src/apps/link-chat/AppLinkChat';
import { AppLayout } from '~/common/layout/AppLayout';
import { useRouterQuery } from '~/common/app.routes';
import { withLayout } from '~/common/layout/withLayout';
export default function ChatLinkPage() {
const { query } = useRouter();
const chatLinkId = query?.chatLinkId as string ?? '';
return (
<AppLayout suspendAutoModelsSetup>
<AppChatLink linkId={chatLinkId} />
</AppLayout>
);
// external state
const { chatLinkId } = useRouterQuery<{ chatLinkId: string | undefined }>();
return withLayout({ type: 'optima', suspendAutoModelsSetup: true }, <AppLinkChat chatLinkId={chatLinkId || null} />);
}
@@ -1,14 +1,16 @@
import * as React from 'react';
import { useRouter } from 'next/router';
import { Alert, Box, Button, Typography } from '@mui/joy';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { setComposerStartupText } from '../src/apps/chat/components/composer/store-composer';
import { setComposerStartupText } from '../../src/apps/chat/components/composer/store-composer';
import { callBrowseFetchPage } from '~/modules/browse/browse.client';
import { AppLayout } from '~/common/layout/AppLayout';
import { LogoProgress } from '~/common/components/LogoProgress';
import { asValidURL } from '~/common/util/urlUtils';
import { navigateToIndex, useRouterQuery } from '~/common/app.routes';
import { withLayout } from '~/common/layout/withLayout';
/**
@@ -28,23 +30,25 @@ function AppShareTarget() {
const [isDownloading, setIsDownloading] = React.useState(false);
// external state
const { query, push: routerPush, replace: routerReplace } = useRouter();
const { url: queryUrl, text: queryText } = useRouterQuery<{
url: string | string[] | undefined,
text: string | string[] | undefined,
}>();
const queueComposerTextAndLaunchApp = React.useCallback((text: string) => {
setComposerStartupText(text);
void routerReplace('/');
}, [routerReplace]);
void navigateToIndex(true);
}, []);
// Detect the share Intent from the query
React.useEffect(() => {
// skip when query is not parsed yet
if (!Object.keys(query).length)
let queryTextItem = queryUrl || queryText || null;
if (!queryTextItem)
return;
// single item from the query
let queryTextItem: string[] | string | null = query.url || query.text || null;
if (Array.isArray(queryTextItem))
queryTextItem = queryTextItem[0];
@@ -55,9 +59,9 @@ function AppShareTarget() {
else if (queryTextItem)
setIntentText(queryTextItem);
else
setErrorMessage('No text or url. Received: ' + JSON.stringify(query));
setErrorMessage('No text or url. Received: ' + JSON.stringify({ queryText, queryUrl }));
}, [query.url, query.text, query]);
}, [queryText, queryUrl]);
// Text -> Composer
@@ -71,18 +75,15 @@ function AppShareTarget() {
React.useEffect(() => {
if (intentURL) {
setIsDownloading(true);
// TEMP: until the Browse module is ready, just use the URL, verbatim
queueComposerTextAndLaunchApp(intentURL);
setIsDownloading(false);
/*callBrowseFetchSinglePage(intentURL)
.then(pageContent => {
if (pageContent)
queueComposerTextAndLaunchApp('\n\n```' + intentURL + '\n' + pageContent + '\n```\n');
callBrowseFetchPage(intentURL)
.then(page => {
if (page.stopReason !== 'error')
queueComposerTextAndLaunchApp('\n\n```' + intentURL + '\n' + page.content + '\n```\n');
else
setErrorMessage('Could not read any data');
setErrorMessage('Could not read any data' + page.error ? ': ' + page.error : '');
})
.catch(error => setErrorMessage(error?.message || error || 'Unknown error'))
.finally(() => setIsDownloading(false));*/
.finally(() => setIsDownloading(false));
}
}, [intentURL, queueComposerTextAndLaunchApp]);
@@ -90,7 +91,6 @@ function AppShareTarget() {
return (
<Box sx={{
backgroundColor: 'background.level2',
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
flexGrow: 1,
}}>
@@ -110,7 +110,7 @@ function AppShareTarget() {
</Alert>
<Button
variant='solid' color='danger'
onClick={() => routerPush('/')}
onClick={() => navigateToIndex()}
endDecorator={<ArrowBackIcon />}
sx={{ mt: 2 }}
>
@@ -130,12 +130,8 @@ function AppShareTarget() {
/**
* This page will be invoked on mobile when sharing Text/URLs/Files from other APPs
* Example URL: https://get.big-agi.com/launch?title=This+Title&text=https%3A%2F%2Fexample.com%2Fapp%2Fpath
* Example URL: https://localhost:3000/link/share_target?title=This+Title&text=https%3A%2F%2Fexample.com%2Fapp%2Fpath
*/
export default function LaunchPage() {
return (
<AppLayout>
<AppShareTarget />
</AppLayout>
);
export default function ShareTargetPage() {
return withLayout({ type: 'plain' }, <AppShareTarget />);
}
+5 -9
View File
@@ -1,18 +1,14 @@
import * as React from 'react';
import { AppNews } from '../src/apps/news/AppNews';
import { useMarkNewsAsSeen } from '../src/apps/news/news.hooks';
import { markNewsAsSeen } from '../src/apps/news/news.version';
import { AppLayout } from '~/common/layout/AppLayout';
import { withLayout } from '~/common/layout/withLayout';
export default function NewsPage() {
// update the last seen news version
useMarkNewsAsSeen();
// 'touch' the last seen news version
React.useEffect(() => markNewsAsSeen(), []);
return (
<AppLayout suspendAutoModelsSetup>
<AppNews />
</AppLayout>
);
return withLayout({ type: 'optima', suspendAutoModelsSetup: true }, <AppNews />);
}
+2 -6
View File
@@ -2,13 +2,9 @@ import * as React from 'react';
import { AppPersonas } from '../src/apps/personas/AppPersonas';
import { AppLayout } from '~/common/layout/AppLayout';
import { withLayout } from '~/common/layout/withLayout';
export default function PersonasPage() {
return (
<AppLayout>
<AppPersonas />
</AppLayout>
);
return withLayout({ type: 'optima' }, <AppPersonas />);
}
+12
View File
@@ -0,0 +1,12 @@
import * as React from 'react';
import { Box } from '@mui/joy';
// import { AppWorkspace } from '../src/apps/personas/AppWorkspace';
import { withLayout } from '~/common/layout/withLayout';
export default function PersonasPage() {
return withLayout({ type: 'optima' }, <Box />);
}

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+1 -1
View File
@@ -25,7 +25,7 @@
}
],
"share_target": {
"action": "/launch",
"action": "/link/share_target",
"method": "GET",
"enctype": "application/x-www-form-urlencoded",
"params": {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+54
View File
@@ -0,0 +1,54 @@
import * as React from 'react';
import { Box, Typography } from '@mui/joy';
import { capitalizeFirstLetter } from '~/common/util/textUtils';
import { useRouterRoute } from '~/common/app.routes';
/**
* https://github.com/enricoros/big-AGI/issues/299
*/
export function AppPlaceholder(props: {
title?: string,
text?: React.ReactNode,
children?: React.ReactNode,
}) {
// external state
const route = useRouterRoute();
// derived state
const placeholderAppName = props.title || capitalizeFirstLetter(route.replace('/', '') || 'Home');
return (
<Box sx={{
flexGrow: 1,
overflowY: 'auto',
p: { xs: 3, md: 6 },
border: '1px solid blue',
}}>
<Box sx={{
my: 'auto',
display: 'flex', flexDirection: 'column', alignItems: 'center',
gap: 4,
border: '1px solid red',
}}>
<Typography level='h1'>
{placeholderAppName}
</Typography>
{!!props.text && (
<Typography>
{props.text}
</Typography>
)}
</Box>
{props.children}
</Box>
);
}
+58 -25
View File
@@ -1,46 +1,79 @@
import * as React from 'react';
import { useRouter } from 'next/router';
import { Container, Sheet } from '@mui/joy';
import { AppCallQueryParams } from '~/common/app.routes';
import { InlineError } from '~/common/components/InlineError';
import type { DConversationId } from '~/common/state/store-chats';
import { useRouterQuery } from '~/common/app.routes';
import { CallUI } from './CallUI';
import { CallWizard } from './CallWizard';
import { Contacts } from './Contacts';
import { Telephone } from './Telephone';
import { useAppCallStore } from './state/store-app-call';
export const APP_CALL_ENABLED = false;
/**
* Used to define the intent of the call from other apps (via query params) or
* from the contacts list (via the 'call' button).
*/
export interface AppCallIntent {
conversationId: DConversationId | null;
personaId: string;
backTo: 'app-chat' | 'app-call-contacts';
}
export function AppCall() {
// external state
const { query } = useRouter();
// derived state
const { conversationId, personaId } = query as any as AppCallQueryParams;
const validInput = !!conversationId && !!personaId;
// state
const [callIntent, setCallIntent] = React.useState<AppCallIntent | null>(null);
// external state
const grayUI = useAppCallStore(state => state.grayUI);
const query = useRouterQuery<Partial<AppCallIntent>>();
// [effect] set intent from the query parameters
React.useEffect(() => {
if (query.personaId) {
setCallIntent({
conversationId: query.conversationId ?? null,
personaId: query.personaId,
backTo: query.backTo || 'app-chat',
});
}
}, [query.backTo, query.conversationId, query.personaId]);
const hasIntent = !!callIntent && !!callIntent.personaId;
return (
<Sheet variant='solid' color='neutral' invertedColors sx={{
display: 'flex', flexDirection: 'column', justifyContent: 'center',
flexGrow: 1,
overflowY: 'auto',
minHeight: 96,
}}>
<Sheet
variant={grayUI ? 'solid' : 'soft'}
invertedColors={grayUI ? true : undefined}
sx={{
// take the full V-area (we're inside PageWrapper) and scroll as needed
flexGrow: 1,
overflowY: 'auto',
<Container maxWidth='sm' sx={{
display: 'flex', flexDirection: 'column',
alignItems: 'center',
minHeight: '80dvh', justifyContent: 'space-evenly',
gap: { xs: 2, md: 4 },
// container will take the full v-area
display: 'grid',
}}>
{!validInput && <InlineError error={`Something went wrong. ${JSON.stringify(query)}`} />}
<Container
maxWidth={hasIntent ? 'sm' : 'md'}
sx={{
display: 'flex', flexDirection: 'column', alignItems: 'center',
justifyContent: hasIntent ? 'space-evenly' : undefined,
gap: hasIntent ? 1 : undefined,
// shall force the contacts or telephone to stay within the container
overflowY: hasIntent ? 'hidden' : undefined,
}}>
{validInput && (
<CallWizard conversationId={conversationId}>
<CallUI conversationId={conversationId} personaId={personaId} />
{!hasIntent ? (
<Contacts setCallIntent={setCallIntent} />
) : (
<CallWizard conversationId={callIntent.conversationId}>
<Telephone callIntent={callIntent} backToContacts={() => setCallIntent(null)} />
</CallWizard>
)}
+30 -23
View File
@@ -1,23 +1,23 @@
import * as React from 'react';
import { keyframes } from '@emotion/react';
import { Box, Button, Card, CardContent, IconButton, ListItemDecorator, Typography } from '@mui/joy';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import ChatIcon from '@mui/icons-material/Chat';
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import MicIcon from '@mui/icons-material/Mic';
import RecordVoiceOverIcon from '@mui/icons-material/RecordVoiceOver';
import WarningIcon from '@mui/icons-material/Warning';
import WarningRoundedIcon from '@mui/icons-material/WarningRounded';
import { PreferencesTab, useOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
import { cssRainbowColorKeyframes } from '~/common/app.theme';
import { navigateBack } from '~/common/app.routes';
import { openLayoutPreferences } from '~/common/layout/store-applayout';
import { useCapabilityBrowserSpeechRecognition, useCapabilityElevenLabs } from '~/common/components/useCapabilities';
import { useChatStore } from '~/common/state/store-chats';
import { useUICounter } from '~/common/state/store-ui';
const cssRainbowBackgroundKeyframes = keyframes`
/*export const cssRainbowBackgroundKeyframes = keyframes`
100%, 0% {
background-color: rgb(128, 0, 0);
}
@@ -53,7 +53,7 @@ const cssRainbowBackgroundKeyframes = keyframes`
}
91% {
background-color: rgb(102, 0, 51);
}`;
}`;*/
function StatusCard(props: { icon: React.JSX.Element, hasIssue: boolean, text: string, button?: React.JSX.Element }) {
return (
@@ -67,7 +67,7 @@ function StatusCard(props: { icon: React.JSX.Element, hasIssue: boolean, text: s
{props.button}
</Typography>
<ListItemDecorator>
{props.hasIssue ? <WarningIcon color='warning' /> : <CheckIcon color='success' />}
{props.hasIssue ? <WarningRoundedIcon color='warning' /> : <CheckIcon color='success' />}
</ListItemDecorator>
</CardContent>
</Card>
@@ -75,21 +75,26 @@ function StatusCard(props: { icon: React.JSX.Element, hasIssue: boolean, text: s
}
export function CallWizard(props: { strict?: boolean, conversationId: string, children: React.ReactNode }) {
export function CallWizard(props: { strict?: boolean, conversationId: string | null, children: React.ReactNode }) {
// state
const [chatEmptyOverride, setChatEmptyOverride] = React.useState(false);
const [recognitionOverride, setRecognitionOverride] = React.useState(false);
// external state
const { openPreferencesTab } = useOptimaLayout();
const recognition = useCapabilityBrowserSpeechRecognition();
const synthesis = useCapabilityElevenLabs();
const chatIsEmpty = useChatStore(state => {
if (!props.conversationId)
return false;
const conversation = state.conversations.find(conversation => conversation.id === props.conversationId);
return !(conversation?.messages?.length);
});
const { novel, touch } = useUICounter('call-wizard');
// derived state
const outOfTheBlue = !props.conversationId;
const overriddenEmptyChat = chatEmptyOverride || !chatIsEmpty;
const overriddenRecognition = recognitionOverride || recognition.mayWork;
const allGood = overriddenEmptyChat && overriddenRecognition && synthesis.mayWork;
@@ -103,7 +108,7 @@ export function CallWizard(props: { strict?: boolean, conversationId: string, ch
const handleOverrideRecognition = () => setRecognitionOverride(true);
const handleConfigureElevenLabs = () => {
openLayoutPreferences(3);
openPreferencesTab(PreferencesTab.Voice);
};
const handleFinishButton = () => {
@@ -117,16 +122,11 @@ export function CallWizard(props: { strict?: boolean, conversationId: string, ch
<Box sx={{ flexGrow: 0.5 }} />
<Typography level='title-lg' sx={{ fontSize: '3rem', fontWeight: 200, lineHeight: '1.5em', textAlign: 'center' }}>
<Typography level='title-lg' sx={{ fontSize: '3rem', fontWeight: 'sm', textAlign: 'center' }}>
Welcome to<br />
<Typography
component='span'
sx={{
backgroundColor: 'primary.solidActiveBg', mx: -0.5, px: 0.5,
animation: `${cssRainbowBackgroundKeyframes} 15s linear infinite`,
}}>
<Box component='span' sx={{ animation: `${cssRainbowColorKeyframes} 15s linear infinite` }}>
your first call
</Typography>
</Box>
</Typography>
<Box sx={{ flexGrow: 0.5 }} />
@@ -137,7 +137,7 @@ export function CallWizard(props: { strict?: boolean, conversationId: string, ch
</Typography>
{/* Chat Empty status */}
<StatusCard
{!outOfTheBlue && <StatusCard
icon={<ChatIcon />}
hasIssue={!overriddenEmptyChat}
text={overriddenEmptyChat ? 'Great! Your chat has messages.' : 'The chat is empty. Calls are effective when the caller has context.'}
@@ -146,7 +146,7 @@ export function CallWizard(props: { strict?: boolean, conversationId: string, ch
Ignore
</Button>
)}
/>
/>}
{/* Add the speech to text feature status */}
<StatusCard
@@ -198,14 +198,21 @@ export function CallWizard(props: { strict?: boolean, conversationId: string, ch
</Typography>
<IconButton
size='lg' variant={allGood ? 'soft' : 'solid'} color={allGood ? 'success' : 'danger'}
onClick={handleFinishButton} sx={{ borderRadius: '50px' }}
size='lg'
variant='solid' color={allGood ? 'success' : 'danger'}
onClick={handleFinishButton}
sx={{
borderRadius: '50px',
mr: 0.5,
// animation: `${cssRainbowBackgroundKeyframes} 15s linear infinite`,
// boxShadow: allGood ? 'md' : 'none',
}}
>
{allGood ? <ArrowForwardIcon sx={{ fontSize: '1.5em' }} /> : <CloseIcon sx={{ fontSize: '1.5em' }} />}
{allGood ? <ArrowForwardIcon sx={{ fontSize: '1.5em' }} /> : <CloseRoundedIcon sx={{ fontSize: '1.5em' }} />}
</IconButton>
</Box>
<Box sx={{ flexGrow: 0.5 }} />
<Box sx={{ flexGrow: 2 }} />
</>;
}
+345
View File
@@ -0,0 +1,345 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { keyframes } from '@emotion/react';
import type { SxProps } from '@mui/joy/styles/types';
import { Avatar, Box, Card, CardContent, Chip, IconButton, Link as MuiLink, ListDivider, MenuItem, Sheet, Switch, Typography } from '@mui/joy';
import CallIcon from '@mui/icons-material/Call';
import { GitHubProjectIssueCard } from '~/common/components/GitHubProjectIssueCard';
import { conversationTitle, DConversation, DConversationId, useChatStore } from '~/common/state/store-chats';
import { usePluggableOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
import type { AppCallIntent } from './AppCall';
import { MockPersona, useMockPersonas } from './state/useMockPersonas';
import { useAppCallStore } from './state/store-app-call';
// number of conversations to show before collapsing
const COLLAPSED_COUNT = 2;
export const niceShadowKeyframes = keyframes`
100%, 0% {
//background-color: rgb(102, 0, 51);
box-shadow: 1px 1px 0 white, 2px 2px 12px rgb(183, 255, 0);
}
25% {
//background-color: rgb(76, 0, 76);
box-shadow: 1px 1px 0 white, 2px 2px 12px rgb(255, 251, 0);
//scale: 1.2;
}
50% {
//background-color: rgb(63, 0, 128);
box-shadow: 1px 1px 0 white, 2px 2px 12px rgba(0, 255, 81);
//scale: 0.8;
}
75% {
//background-color: rgb(0, 0, 128);
box-shadow: 1px 1px 0 white, 2px 2px 12px rgb(255, 153, 0);
}`;
const ContactCardAvatar = (props: { size: string, symbol?: string, imageUrl?: string, onClick?: () => void, sx?: SxProps }) =>
<Avatar
// variant='outlined'
onClick={props.onClick}
src={props.imageUrl}
sx={{
'--Avatar-size': props.size,
fontSize: props.size,
backgroundColor: 'background.popup',
boxShadow: !props.imageUrl ? 'sm' : null,
...props.sx,
}}
>
{/* As fallback, show the large Persona Symbol */}
{!props.imageUrl && <Box>{props.symbol}</Box>}
</Avatar>;
const ContactCardConversationCall = (props: { conversation: DConversation, onConversationClicked: (conversationId: DConversationId) => void, }) =>
<Chip
variant='plain' color='primary' size='sm'
endDecorator={<CallIcon />}
onClick={() => props.onConversationClicked(props.conversation.id)}
slotProps={{
root: {
sx: {
maxWidth: 'unset',
mx: -1,
px: 1,
py: 0.25,
},
},
}}
>
{conversationTitle(props.conversation, 'Chat')}
</Chip>;
function CallContactCard(props: {
persona: MockPersona,
callGrayUI: boolean,
conversations: DConversation[],
setCallIntent: (intent: AppCallIntent) => void,
}) {
// state
const [conversationsExpanded, setConversationsExpanded] = React.useState(false);
// derived state
const { persona, setCallIntent } = props;
const conversations = props.conversations.slice(0, conversationsExpanded ? undefined : COLLAPSED_COUNT);
const hasConversations = !!conversations.length;
const showExpander = props.conversations.length > COLLAPSED_COUNT && !conversationsExpanded;
const handleCallPersona = React.useCallback(() => setCallIntent({
conversationId: null,
personaId: persona.personaId,
backTo: 'app-call-contacts',
}), [persona.personaId, setCallIntent]);
const handleCallPersonaRe = React.useCallback((conversationId: DConversationId | null) => setCallIntent({
conversationId: conversationId,
personaId: persona.personaId,
backTo: 'app-call-contacts',
}), [persona.personaId, setCallIntent]);
return (
<Box sx={{ mt: 3.5 }}>
<Card sx={{
// boxShadow: 'lg',
height: '100%',
gap: 0,
}}>
{/* Persona Symbol - Overlapping */}
<ContactCardAvatar
size='6rem'
symbol={persona.symbol}
imageUrl={persona?.imageUri}
sx={{
mx: 'auto',
mt: '-2.5rem',
zIndex: 1,
}}
/>
<CardContent sx={{ my: 2, display: 'flex' }}>
{/* Persona Description */}
<Typography level='body-xs' sx={{ minHeight: '3em', mb: hasConversations ? 1.5 : undefined }}>
{typeof persona.description === 'string' ? persona.description : 'Custom persona'}
</Typography>
{/*{hasConversations && <Divider>*/}
{/*<Typography level='body-xs'>call about</Typography>*/}
{/*</Divider>}*/}
{/* Persona Recent Converstions */}
{conversations.map(conversation =>
<ContactCardConversationCall
key={conversation.id}
conversation={conversation}
onConversationClicked={handleCallPersonaRe}
/>,
)}
{showExpander && <Chip
variant='plain' color='primary' size='sm'
onClick={() => setConversationsExpanded(true)}
slotProps={{
root: {
sx: {
maxWidth: 'unset',
mx: -1,
px: 1,
py: 0.25,
},
},
}}
>
{`+${props.conversations.length - COLLAPSED_COUNT} more`}
</Chip>}
</CardContent>
{/*<Divider />*/}
{/* Bottom Name and "Call" Button */}
<Sheet
variant='soft' color='primary'
invertedColors={props.callGrayUI ? undefined : true}
sx={{
// emulate CardOverflow, because CardOverflow doesn't work well with Sheet/Inverted
// (there's also a potential top-level inversion)
'--variant-borderWidth': '1px',
'--CardOverflow-offset': 'calc(-1 * var(--Card-padding))',
'--CardOverflow-radius': 'calc(var(--Card-radius) - var(--variant-borderWidth, 0px))',
margin: '0 var(--CardOverflow-offset) var(--CardOverflow-offset)',
borderRadius: '0 0 var(--CardOverflow-radius) var(--CardOverflow-radius)',
padding: '0.5rem var(--Card-padding)',
// contents
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
gap: 1,
}}
>
<Typography level='title-md'>
{persona.title}
</Typography>
<MuiLink overlay onClick={handleCallPersona}>
<IconButton size='md' variant='soft' sx={{
// borderRadius: '50%',
ml: 'auto',
mr: -1,
}}>
<CallIcon />
</IconButton>
</MuiLink>
</Sheet>
</Card>
</Box>
);
}
function useConversationsByPersona() {
const conversations = useChatStore(state => state.conversations, shallow);
return React.useMemo(() => {
// group by personaId
const groupedConversations: { [personaId: string]: DConversation[] } = conversations.reduce((acc, conversation) => {
const personaId = conversation.systemPurposeId;
acc[personaId] = [...acc[personaId] || [], conversation];
return acc;
}, {} as { [personaId: string]: DConversation[] });
// sort conversations by time and limit to 3
Object.values(groupedConversations).forEach(conversations =>
conversations.sort((a, b) => (b.updated || b.created) - (a.updated || a.created)),
);
return groupedConversations;
}, [conversations]);
}
export function Contacts(props: { setCallIntent: (intent: AppCallIntent) => void }) {
// external state
const {
grayUI, toggleGrayUI,
showConversations, toggleShowConversations,
showSupport, toggleShowSupport,
} = useAppCallStore();
const { personas } = useMockPersonas();
const conversationsByPersona = useConversationsByPersona();
// pluggable UI
const menuItems = React.useMemo(() => <>
<MenuItem onClick={toggleShowConversations}>
Conversations
<Switch checked={showConversations} sx={{ ml: 'auto' }} />
</MenuItem>
<MenuItem onClick={toggleShowSupport}>
Support
<Switch checked={showSupport} sx={{ ml: 'auto' }} />
</MenuItem>
<MenuItem onClick={toggleGrayUI}>
Grayed UI
<Switch checked={grayUI} sx={{ ml: 'auto' }} />
</MenuItem>
</>, [grayUI, showConversations, showSupport, toggleGrayUI, toggleShowConversations, toggleShowSupport]);
usePluggableOptimaLayout(null, null, menuItems, 'CallUI');
return <>
{/* Header "Call AGI" */}
<Box sx={{
my: 6,
display: 'flex', alignItems: 'center',
gap: 3,
}}>
<IconButton
variant='soft' color='success'
sx={{
'--IconButton-size': { xs: '4.2rem', md: '5rem' },
borderRadius: '50%',
pointerEvents: 'none',
backgroundColor: 'background.popup',
animation: `${niceShadowKeyframes} 5s infinite`,
}}>
<CallIcon />
</IconButton>
<Box>
<Typography level='title-lg'>
Call AGI
</Typography>
<Typography level='title-sm' sx={{ mt: 1 }}>
Explore ideas and ignite creativity
</Typography>
<Chip variant='outlined' size='sm' sx={{ px: 1, py: 0.5, mt: 0.25, ml: -1, textWrap: 'wrap' }}>
Out-of-the-blue, or within a conversation
</Chip>
</Box>
</Box>
<ListDivider>
Personas
</ListDivider>
{/* Personas Cards */}
<Box
sx={{
width: '100%',
my: 5,
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))',
gap: { xs: 1, md: 2 },
}}
>
{personas.map((persona) =>
<CallContactCard
key={persona.personaId}
persona={persona}
callGrayUI={grayUI}
conversations={!showConversations ? [] : conversationsByPersona[persona.personaId] || []}
setCallIntent={props.setCallIntent}
/>,
)}
</Box>
{showSupport && <ListDivider sx={{ my: 1 }} />}
{showSupport && <GitHubProjectIssueCard
issue={354}
text='Call App: Support thread and compatibility matrix'
note={<>
Voice input uses the HTML Web Speech API, and speech output requires an ElevenLabs API Key.
</>}
// note2='Please report any issues you encounter'
sx={{
width: '100%',
mb: 2,
mt: 5,
}}
/>}
</>;
}
@@ -1,34 +1,36 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { useRouter } from 'next/router';
import { Box, Card, ListItemDecorator, MenuItem, Switch, Typography } from '@mui/joy';
import { Box, Card, ListDivider, ListItemDecorator, MenuItem, Switch, Typography } from '@mui/joy';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import CallEndIcon from '@mui/icons-material/CallEnd';
import CallIcon from '@mui/icons-material/Call';
import ChatOutlinedIcon from '@mui/icons-material/ChatOutlined';
import MicIcon from '@mui/icons-material/Mic';
import MicNoneIcon from '@mui/icons-material/MicNone';
import RecordVoiceOverIcon from '@mui/icons-material/RecordVoiceOver';
import { useChatLLMDropdown } from '../chat/components/applayout/useLLMDropdown';
import { ScrollToBottom } from '../chat/components/scroll-to-bottom/ScrollToBottom';
import { ScrollToBottomButton } from '../chat/components/scroll-to-bottom/ScrollToBottomButton';
import { useChatLLMDropdown } from '../chat/components/useLLMDropdown';
import { EXPERIMENTAL_speakTextStream } from '~/modules/elevenlabs/elevenlabs.client';
import { SystemPurposeId, SystemPurposes } from '../../data';
import { VChatMessageIn } from '~/modules/llms/transports/chatGenerate';
import { streamChat } from '~/modules/llms/transports/streamChat';
import { llmStreamingChatGenerate, VChatMessageIn } from '~/modules/llms/llm.client';
import { useElevenLabsVoiceDropdown } from '~/modules/elevenlabs/useElevenLabsVoiceDropdown';
import { Link } from '~/common/components/Link';
import { SpeechResult, useSpeechRecognition } from '~/common/components/useSpeechRecognition';
import { conversationTitle, createDMessage, DMessage, useChatStore } from '~/common/state/store-chats';
import { launchAppChat, navigateToIndex } from '~/common/app.routes';
import { playSoundUrl, usePlaySoundUrl } from '~/common/util/audioUtils';
import { useLayoutPluggable } from '~/common/layout/store-applayout';
import { usePluggableOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
import type { AppCallIntent } from './AppCall';
import { CallAvatar } from './components/CallAvatar';
import { CallButton } from './components/CallButton';
import { CallMessage } from './components/CallMessage';
import { CallStatus } from './components/CallStatus';
import { useAppCallStore } from './state/store-app-call';
function CallMenuItems(props: {
@@ -39,6 +41,7 @@ function CallMenuItems(props: {
}) {
// external state
const { grayUI, toggleGrayUI } = useAppCallStore();
const { voicesDropdown } = useElevenLabsVoiceDropdown(false, !props.override);
const handlePushToTalkToggle = () => props.setPushToTalk(!props.pushToTalk);
@@ -64,8 +67,14 @@ function CallMenuItems(props: {
{voicesDropdown}
</MenuItem>
<ListDivider />
<MenuItem onClick={toggleGrayUI}>
Grayed UI
<Switch checked={grayUI} sx={{ ml: 'auto' }} />
</MenuItem>
<MenuItem component={Link} href='https://github.com/enricoros/big-agi/issues/175' target='_blank'>
<ListItemDecorator><ChatOutlinedIcon /></ListItemDecorator>
Voice Calls Feedback
</MenuItem>
@@ -73,9 +82,9 @@ function CallMenuItems(props: {
}
export function CallUI(props: {
conversationId: string,
personaId: string,
export function Telephone(props: {
callIntent: AppCallIntent,
backToContacts: () => void,
}) {
// state
@@ -89,16 +98,17 @@ export function CallUI(props: {
const responseAbortController = React.useRef<AbortController | null>(null);
// external state
const { push: routerPush } = useRouter();
const { chatLLMId, chatLLMDropdown } = useChatLLMDropdown();
const { chatTitle, messages } = useChatStore(state => {
const conversation = state.conversations.find(conversation => conversation.id === props.conversationId);
const { chatTitle, reMessages } = useChatStore(state => {
const conversation = props.callIntent.conversationId
? state.conversations.find(conversation => conversation.id === props.callIntent.conversationId) ?? null
: null;
return {
chatTitle: conversation ? conversationTitle(conversation) : 'no conversation',
messages: conversation ? conversation.messages : [],
chatTitle: conversation ? conversationTitle(conversation) : null,
reMessages: conversation ? conversation.messages : null,
};
}, shallow);
const persona = SystemPurposes[props.personaId as SystemPurposeId] ?? undefined;
const persona = SystemPurposes[props.callIntent.personaId as SystemPurposeId] ?? undefined;
const personaCallStarters = persona?.call?.starters ?? undefined;
const personaVoiceId = overridePersonaVoice ? undefined : (persona?.voices?.elevenLabs?.voiceId ?? undefined);
const personaSystemMessage = persona?.systemMessage ?? undefined;
@@ -113,7 +123,7 @@ export function CallUI(props: {
setCallMessages(messages => [...messages, createDMessage('user', transcribed)]);
}
}, []);
const { isSpeechEnabled, isRecording, isRecordingAudio, isRecordingSpeech, startRecording, stopRecording, toggleRecording } = useSpeechRecognition(onSpeechResultCallback, 1000, false);
const { isSpeechEnabled, isRecording, isRecordingAudio, isRecordingSpeech, startRecording, stopRecording, toggleRecording } = useSpeechRecognition(onSpeechResultCallback, 1000);
// derived state
const isRinging = stage === 'ring';
@@ -178,9 +188,7 @@ export function CallUI(props: {
// command: close the call
case 'Goodbye.':
setStage('ended');
setTimeout(() => {
void routerPush('/');
}, 2000);
setTimeout(launchAppChat, 2000);
return;
// command: regenerate answer
case 'Retry.':
@@ -197,8 +205,8 @@ export function CallUI(props: {
if (!chatLLMId) return;
// temp fix: when the chat has no messages, only assume a single system message
const chatMessages: { role: VChatMessageIn['role'], text: string }[] = messages.length > 0
? messages
const chatMessages: { role: VChatMessageIn['role'], text: string }[] = (reMessages && reMessages.length > 0)
? reMessages
: personaSystemMessage
? [{ role: 'system', text: personaSystemMessage }]
: [];
@@ -216,8 +224,9 @@ export function CallUI(props: {
responseAbortController.current = new AbortController();
let finalText = '';
let error: any | null = null;
streamChat(chatLLMId, callPrompt, responseAbortController.current.signal, (updatedMessage: Partial<DMessage>) => {
const text = updatedMessage.text?.trim();
setPersonaTextInterim('💭...');
llmStreamingChatGenerate(chatLLMId, callPrompt, null, null, responseAbortController.current.signal, ({ textSoFar }) => {
const text = textSoFar?.trim();
if (text) {
finalText = text;
setPersonaTextInterim(text);
@@ -227,16 +236,18 @@ export function CallUI(props: {
error = err;
}).finally(() => {
setPersonaTextInterim(null);
setCallMessages(messages => [...messages, createDMessage('assistant', finalText + (error ? ` (ERROR: ${error.message || error.toString()})` : ''))]);
if (finalText || error)
setCallMessages(messages => [...messages, createDMessage('assistant', finalText + (error ? ` (ERROR: ${error.message || error.toString()})` : ''))]);
// fire/forget
void EXPERIMENTAL_speakTextStream(finalText, personaVoiceId);
if (finalText?.length >= 1)
void EXPERIMENTAL_speakTextStream(finalText, personaVoiceId);
});
return () => {
responseAbortController.current?.abort();
responseAbortController.current = null;
};
}, [isConnected, callMessages, chatLLMId, messages, personaVoiceId, personaSystemMessage, routerPush]);
}, [isConnected, callMessages, chatLLMId, personaVoiceId, personaSystemMessage, reMessages]);
// [E] Message interrupter
const abortTrigger = isConnected && isRecordingSpeech;
@@ -273,7 +284,7 @@ export function CallUI(props: {
, [overridePersonaVoice, pushToTalk],
);
useLayoutPluggable(chatLLMDropdown, null, menuItems);
usePluggableOptimaLayout(null, chatLLMDropdown, menuItems, 'CallUI');
return <>
@@ -298,76 +309,110 @@ export function CallUI(props: {
<CallStatus
callerName={isConnected ? undefined : personaName}
statusText={isRinging ? 'is calling you' : isDeclined ? 'call declined' : isEnded ? 'call ended' : callElapsedTime}
statusText={isRinging ? '' /*'is calling you'*/ : isDeclined ? 'call declined' : isEnded ? 'call ended' : callElapsedTime}
regardingText={chatTitle}
micError={!isMicEnabled} speakError={!isTTSEnabled}
/>
{/* Live Transcript, w/ streaming messages, audio indication, etc. */}
{(isConnected || isEnded) && (
<Card variant='soft' sx={{
<Card variant='outlined' sx={{
flexGrow: 1,
minHeight: '15dvh', maxHeight: '24dvh',
overflow: 'auto',
maxHeight: '28%',
minHeight: '20%',
width: '100%',
// style
// backgroundColor: 'background.surface',
borderRadius: 'lg',
flexDirection: 'column-reverse',
// boxShadow: 'sm',
// children
padding: 0, // move this to the ScrollToBottom component
}}>
{/* Messages in reverse order, for auto-scroll from the bottom */}
<Box sx={{ display: 'flex', flexDirection: 'column-reverse', gap: 1 }}>
<ScrollToBottom
// bootToBottom
stickToBottom
sx={{
// allows the content to be scrolled (all browsers)
overflowY: 'auto',
// actually make sure this scrolls & fills
height: '100%',
{/* Listening... */}
{isRecording && (
<CallMessage
text={<>{speechInterim?.transcript ? speechInterim.transcript + ' ' : ''}<i>{speechInterim?.interimTranscript}</i></>}
variant={isRecordingSpeech ? 'solid' : 'outlined'}
role='user'
/>
)}
// content
display: 'grid',
padding: 1,
}}
>
{/* Persona streaming text... */}
{!!personaTextInterim && (
<CallMessage
text={personaTextInterim}
variant='solid' color='neutral'
role='assistant'
/>
)}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{/* Messages (last 6 messages, in reverse order) */}
{callMessages.slice(-6).reverse().map((message) =>
<CallMessage
key={message.id}
text={message.text}
variant={message.role === 'assistant' ? 'solid' : 'soft'} color='neutral'
role={message.role} />,
)}
</Box>
{/* Call Messages [] */}
{callMessages.map((message) =>
<CallMessage
key={message.id}
text={message.text}
variant={message.role === 'assistant' ? 'solid' : 'soft'}
color={message.role === 'assistant' ? 'neutral' : 'primary'}
role={message.role}
/>,
)}
{/* Persona streaming text... */}
{!!personaTextInterim && (
<CallMessage
text={personaTextInterim}
variant='outlined'
color='neutral'
role='assistant'
/>
)}
{/* Listening... */}
{isRecording && (
<CallMessage
text={<>{speechInterim?.transcript.trim() || null}{speechInterim?.interimTranscript.trim() ? <i> {speechInterim.interimTranscript}</i> : null}</>}
variant={(isRecordingSpeech || !!speechInterim?.transcript) ? 'soft' : 'outlined'}
color='primary'
role='user'
/>
)}
</Box>
{/* Visibility and actions are handled via Context */}
<ScrollToBottomButton />
</ScrollToBottom>
</Card>
)}
{/* Call Buttons */}
<Box sx={{ width: '100%', display: 'flex', justifyContent: 'space-evenly' }}>
<Box sx={{ width: '100%', display: 'flex', justifyContent: 'space-evenly', gap: 4 }}>
{/* [ringing] Decline / Accept */}
{isRinging && <CallButton Icon={CallEndIcon} text='Decline' color='danger' onClick={() => setStage('declined')} />}
{isRinging && isEnabled && <CallButton Icon={CallIcon} text='Accept' color='success' variant='soft' onClick={() => setStage('connected')} />}
{isRinging && <CallButton Icon={CallEndIcon} text='Decline' color='danger' variant='solid' onClick={() => setStage('declined')} />}
{isRinging && isEnabled && <CallButton Icon={CallIcon} text='Accept' color='success' variant='solid' onClick={() => setStage('connected')} />}
{/* [Calling] Hang / PTT (mute not enabled yet) */}
{isConnected && <CallButton Icon={CallEndIcon} text='Hang up' color='danger' onClick={handleCallStop} />}
{isConnected && (pushToTalk
? <CallButton Icon={MicIcon} onClick={toggleRecording}
text={isRecordingSpeech ? 'Listening...' : isRecording ? 'Listening' : 'Push To Talk'}
variant={isRecordingSpeech ? 'solid' : isRecording ? 'soft' : 'outlined'} />
: null
{isConnected && <CallButton Icon={CallEndIcon} text='Hang up' color='danger' variant='soft' onClick={handleCallStop} />}
{isConnected && (pushToTalk ? (
<CallButton
Icon={MicIcon} onClick={toggleRecording}
text={isRecordingSpeech ? 'Listening...' : isRecording ? 'Listening' : 'Push To Talk'}
variant={isRecordingSpeech ? 'solid' : isRecording ? 'soft' : 'outlined'}
color='primary'
sx={!isRecording ? { backgroundColor: 'background.surface' } : undefined}
/>
) : null
// <CallButton disabled={true} Icon={MicOffIcon} onClick={() => setMicMuted(muted => !muted)}
// text={micMuted ? 'Muted' : 'Mute'}
// color={micMuted ? 'warning' : undefined} variant={micMuted ? 'solid' : 'outlined'} />
)}
{/* [ended] Back / Call Again */}
{(isEnded || isDeclined) && <Link noLinkStyle href='/'><CallButton Icon={ArrowBackIcon} text='Back' variant='soft' /></Link>}
{(isEnded || isDeclined) && <CallButton Icon={ArrowBackIcon} text='Back' variant='soft' onClick={() => props.callIntent.backTo === 'app-chat' ? navigateToIndex() : props.backToContacts()} />}
{(isEnded || isDeclined) && <CallButton Icon={CallIcon} text='Call Again' color='success' variant='soft' onClick={() => setStage('connected')} />}
</Box>
+5 -6
View File
@@ -16,17 +16,16 @@ const cssScaleKeyframes = keyframes`
}`;
export function CallAvatar(props: { symbol: string, imageUrl?: string, isRinging: boolean, onClick: () => void }) {
export function CallAvatar(props: { symbol: string, imageUrl?: string, isRinging?: boolean, onClick: () => void }) {
return (
<Avatar
variant='soft' color='neutral'
onClick={props.onClick}
src={props.imageUrl}
sx={{
'--Avatar-size': { xs: '160px', md: '200px' },
'--variant-borderWidth': '4px',
boxShadow: !props.imageUrl ? 'md' : null,
fontSize: { xs: '100px', md: '120px' },
'--Avatar-size': { xs: '10rem', md: '11.5rem' },
backgroundColor: 'background.popup',
boxShadow: !props.imageUrl ? 'sm' : null,
fontSize: { xs: '6rem', md: '7rem' },
}}
>
+12 -6
View File
@@ -1,6 +1,7 @@
import * as React from 'react';
import { Box, ColorPaletteProp, IconButton, Typography, VariantProp } from '@mui/joy';
import { ColorPaletteProp, FormControl, IconButton, Typography, VariantProp } from '@mui/joy';
import { SxProps } from '@mui/joy/styles/types';
/**
@@ -14,9 +15,10 @@ export function CallButton(props: {
Icon: React.FC, text: string,
variant?: VariantProp, color?: ColorPaletteProp, disabled?: boolean,
onClick?: () => void,
sx?: SxProps,
}) {
return (
<Box
<FormControl
onClick={() => !props.disabled && props.onClick?.()}
sx={{
display: 'flex', flexDirection: 'column', alignItems: 'center',
@@ -25,19 +27,23 @@ export function CallButton(props: {
>
<IconButton
disabled={props.disabled} variant={props.variant || 'solid'} color={props.color}
aria-label={props.text}
variant={props.variant || 'solid'} color={props.color}
disabled={props.disabled}
sx={{
'--IconButton-size': { xs: '4.2rem', md: '5rem' },
borderRadius: '50%',
// boxShadow: 'lg',
}}>
...props.sx,
}}
>
<props.Icon />
</IconButton>
<Typography level='title-md' variant={props.disabled ? 'soft' : undefined}>
<Typography aria-hidden level='title-md' variant={props.disabled ? 'soft' : undefined}>
{props.text}
</Typography>
</Box>
</FormControl>
);
}
+10 -4
View File
@@ -1,9 +1,9 @@
import * as React from 'react';
import { Chip, ColorPaletteProp, VariantProp } from '@mui/joy';
import { SxProps } from '@mui/system';
import { SxProps } from '@mui/joy/styles/types';
import { VChatMessageIn } from '~/modules/llms/transports/chatGenerate';
import type { VChatMessageIn } from '~/modules/llms/llm.client';
export function CallMessage(props: {
@@ -12,16 +12,22 @@ export function CallMessage(props: {
role: VChatMessageIn['role'],
sx?: SxProps,
}) {
const isUserMessage = props.role === 'user';
return (
<Chip
color={props.color} variant={props.variant}
sx={{
alignSelf: props.role === 'user' ? 'end' : 'start',
alignSelf: isUserMessage ? 'end' : 'start',
whiteSpace: 'break-spaces',
borderRadius: 'lg',
mt: 'auto',
...(isUserMessage ? {
borderBottomRightRadius: 0,
} : {
borderBottomLeftRadius: 0,
}),
// boxShadow: 'md',
py: 1,
px: 1.5,
...(props.sx || {}),
}}
>
+7 -7
View File
@@ -15,7 +15,7 @@ import { InlineError } from '~/common/components/InlineError';
export function CallStatus(props: {
callerName?: string,
statusText: string,
regardingText?: string,
regardingText: string | null,
micError: boolean, speakError: boolean,
// llmComponent?: React.JSX.Element,
}) {
@@ -28,19 +28,19 @@ export function CallStatus(props: {
{/*{props.llmComponent}*/}
<Typography level='body-md' sx={{ textAlign: 'center' }}>
{!!props.statusText && <Typography level='body-md' sx={{ textAlign: 'center' }}>
{props.statusText}
</Typography>
</Typography>}
{!!props.regardingText && <Typography level='body-md' sx={{ textAlign: 'center', mt: 0 }}>
re: {props.regardingText}
{!!props.regardingText && <Typography level='body-md' sx={{ textAlign: 'center', mt: 1 }}>
Re: <Box component='span' sx={{ color: 'text.primary' }}>{props.regardingText}</Box>
</Typography>}
{props.micError && <InlineError
severity='danger' error='But this browser does not support speech recognition... 🤦‍♀️ - Try Chrome on Windows?' />}
severity='danger' error='Looks like this Browser may not support speech recognition. You can try Chrome on Windows or Android instead.' />}
{props.speakError && <InlineError
severity='danger' error='And text-to-speech is not configured... 🤦‍♀️ - Configure it in Settings?' />}
severity='danger' error='Text-to-speech does not appear to be configured. Please set it up in Preferences > Voice.' />}
</Box>
);
+35
View File
@@ -0,0 +1,35 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
// Call settings
interface AppCallStore {
grayUI: boolean;
toggleGrayUI: () => void;
showConversations: boolean;
toggleShowConversations: () => void;
showSupport: boolean;
toggleShowSupport: () => void;
}
export const useAppCallStore = create<AppCallStore>()(persist(
(_set, _get) => ({
grayUI: false,
toggleGrayUI: () => _set(state => ({ grayUI: !state.grayUI })),
showConversations: true,
toggleShowConversations: () => _set(state => ({ showConversations: !state.showConversations })),
showSupport: true,
toggleShowSupport: () => _set(state => ({ showSupport: !state.showSupport })),
}), {
name: 'app-app-call',
},
));
+31
View File
@@ -0,0 +1,31 @@
import * as React from 'react';
import { usePurposeStore } from '../../chat/components/persona-selector/store-purposes';
import { SystemPurposeData, SystemPurposeId, SystemPurposes } from '../../../data';
/**
* This is a 'mock' persona because Soon we'll have real personas definitions
* and stores. Until then, we just mimic a reactive system here.
*/
export interface MockPersona extends SystemPurposeData {
personaId: SystemPurposeId,
}
export function useMockPersonas(): { personas: MockPersona[], personaIDs: SystemPurposeId[] } {
// only react to hiddenPurposeIDs changes
const hiddenPurposeIDs = usePurposeStore(state => state.hiddenPurposeIDs);
// Dependency array is empty because SystemPurposes is constant
return React.useMemo(() => {
const personaIDs = Object.keys(SystemPurposes) as SystemPurposeId[];
const personas = personaIDs
.filter((key) => !hiddenPurposeIDs.includes(key))
.map((key) => ({
...SystemPurposes[key as SystemPurposeId],
personaId: key as SystemPurposeId,
}));
return { personas, personaIDs };
}, [hiddenPurposeIDs]);
}
+536 -184
View File
@@ -1,293 +1,645 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { useTheme } from '@mui/joy';
import { CmdRunProdia } from '~/modules/prodia/prodia.client';
import { CmdRunReact } from '~/modules/aifn/react/react';
import { DiagramConfig, DiagramsModal } from '~/modules/aifn/digrams/DiagramsModal';
import { FlattenerModal } from '~/modules/aifn/flatten/FlattenerModal';
import { TradeConfig, TradeModal } from '~/modules/trade/TradeModal';
import { getChatLLMId, useChatLLM } from '~/modules/llms/store-llms';
import { imaginePromptFromText } from '~/modules/aifn/imagine/imaginePromptFromText';
import { useModelsStore } from '~/modules/llms/store-llms';
import { speakText } from '~/modules/elevenlabs/elevenlabs.client';
import { useCapabilityTextToImage } from '~/modules/t2i/t2i.client';
import { Brand } from '~/common/app.config';
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
import { createDMessage, DMessage, useChatStore } from '~/common/state/store-chats';
import { useGlobalShortcut } from '~/common/components/useGlobalShortcut';
import { useLayoutPluggable } from '~/common/layout/store-applayout';
import { ConversationManager } from '~/common/chats/ConversationHandler';
import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcut';
import { PanelResizeInset } from '~/common/components/panes/GoodPanelResizeHandler';
import { addSnackbar, removeSnackbar } from '~/common/components/useSnackbarsStore';
import { createDMessage, DConversationId, DMessage, getConversation, getConversationSystemPurposeId, useConversation } from '~/common/state/store-chats';
import { getUXLabsHighPerformance, useUXLabsStore } from '~/common/state/store-ux-labs';
import { themeBgAppChatComposer } from '~/common/app.theme';
import { useFolderStore } from '~/common/state/store-folders';
import { useIsMobile } from '~/common/components/useMatchMedia';
import { useOptimaLayout, usePluggableOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
import { useUIPreferencesStore } from '~/common/state/store-ui';
import { ChatDrawerItems } from './components/applayout/ChatDrawerItems';
import { ChatDropdowns } from './components/applayout/ChatDropdowns';
import { ChatMenuItems } from './components/applayout/ChatMenuItems';
import type { ComposerOutputMultiPart } from './components/composer/composer.types';
import { Beam } from './components/beam/Beam';
import { ChatDrawerMemo } from './components/ChatDrawer';
import { ChatDropdowns } from './components/ChatDropdowns';
import { ChatMessageList } from './components/ChatMessageList';
import { ChatModeId } from './components/composer/store-composer';
import { CmdAddRoleMessage, extractCommands } from './commands';
import { ChatPageMenuItems } from './components/ChatPageMenuItems';
import { ChatTitle } from './components/ChatTitle';
import { Composer } from './components/composer/Composer';
import { Ephemerals } from './components/Ephemerals';
import { ScrollToBottom } from './components/scroll-to-bottom/ScrollToBottom';
import { ScrollToBottomButton } from './components/scroll-to-bottom/ScrollToBottomButton';
import { getInstantAppChatPanesCount, usePanesManager } from './components/panes/usePanesManager';
import { TradeConfig, TradeModal } from './trade/TradeModal';
import { extractChatCommand, findAllChatCommands } from './commands/commands.registry';
import { runAssistantUpdatingState } from './editors/chat-stream';
import { runBrowseGetPageUpdatingState } from './editors/browse-load';
import { runImageGenerationUpdatingState } from './editors/image-generate';
import { runReActUpdatingState } from './editors/react-tangent';
const SPECIAL_ID_ALL_CHATS = 'all-chats';
// what to say when a chat is new and has no title
export const CHAT_NOVEL_TITLE = 'Chat';
/**
* Mode: how to treat the input from the Composer
*/
export type ChatModeId =
| 'generate-text'
| 'generate-text-beam'
| 'append-user'
| 'generate-image'
| 'generate-react';
export function AppChat() {
// state
const [isComposerMulticast, setIsComposerMulticast] = React.useState(false);
const [isMessageSelectionMode, setIsMessageSelectionMode] = React.useState(false);
const [diagramConfig, setDiagramConfig] = React.useState<DiagramConfig | null>(null);
const [tradeConfig, setTradeConfig] = React.useState<TradeConfig | null>(null);
const [clearConfirmationId, setClearConfirmationId] = React.useState<string | null>(null);
const [deleteConfirmationId, setDeleteConfirmationId] = React.useState<string | null>(null);
const [flattenConversationId, setFlattenConversationId] = React.useState<string | null>(null);
const [clearConversationId, setClearConversationId] = React.useState<DConversationId | null>(null);
const [deleteConversationIds, setDeleteConversationIds] = React.useState<DConversationId[] | null>(null);
const [flattenConversationId, setFlattenConversationId] = React.useState<DConversationId | null>(null);
const showNextTitleChange = React.useRef(false);
const composerTextAreaRef = React.useRef<HTMLTextAreaElement>(null);
const [_activeFolderId, setActiveFolderId] = React.useState<string | null>(null);
// external state
const { activeConversationId, isConversationEmpty, hasAnyContent, newConversation, duplicateConversation, deleteAllConversations, setMessages, systemPurposeId, setAutoTitle } = useChatStore(state => {
const conversation = state.conversations.find(conversation => conversation.id === state.activeConversationId);
const isConversationEmpty = conversation ? !conversation.messages.length : true;
const hasAnyContent = state.conversations.length > 1 || !isConversationEmpty;
const theme = useTheme();
const isMobile = useIsMobile();
const showAltTitleBar = useUXLabsStore(state => state.labsChatBarAlt === 'title');
const { openLlmOptions } = useOptimaLayout();
const { chatLLM } = useChatLLM();
const {
chatPanes,
focusedConversationId,
navigateHistoryInFocusedPane,
openConversationInFocusedPane,
openConversationInSplitPane,
focusedPaneIndex,
removePane,
setFocusedPane,
} = usePanesManager();
const {
title: focusedChatTitle,
isChatEmpty: isFocusedChatEmpty,
isDeveloper: isFocusedChatDeveloper,
areChatsEmpty,
conversationIdx: focusedChatNumber,
newConversationId,
prependNewConversation,
branchConversation,
deleteConversations,
setMessages,
} = useConversation(focusedConversationId);
const { mayWork: capabilityHasT2I } = useCapabilityTextToImage();
const { activeFolderId } = useFolderStore(({ enableFolders, folders }) => {
const activeFolderId = enableFolders ? _activeFolderId : null;
const activeFolder = activeFolderId ? folders.find(folder => folder.id === activeFolderId) : null;
return {
activeConversationId: state.activeConversationId,
isConversationEmpty,
hasAnyContent,
newConversation: state.createConversationOrSwitch,
duplicateConversation: state.duplicateConversation,
deleteAllConversations: state.deleteAllConversations,
setMessages: state.setMessages,
systemPurposeId: conversation?.systemPurposeId ?? null,
setAutoTitle: state.setAutoTitle,
activeFolderId: activeFolder?.id ?? null,
};
}, shallow);
});
const handleExecuteConversation = async (chatModeId: ChatModeId, conversationId: string, history: DMessage[]) => {
const { chatLLMId } = useModelsStore.getState();
// Window actions
const isMultiPane = chatPanes.length >= 2;
const isMultiAddable = chatPanes.length < 4;
const isMultiConversationId = isMultiPane && new Set(chatPanes.map((pane) => pane.conversationId)).size >= 2;
const willMulticast = isComposerMulticast && isMultiConversationId;
const disableNewButton = isFocusedChatEmpty && !isMultiPane;
const chatHandlers = React.useMemo(() => chatPanes.map(pane => {
return pane.conversationId ? ConversationManager.getHandler(pane.conversationId) : null;
}), [chatPanes]);
const setFocusedConversationId = React.useCallback((conversationId: DConversationId | null) => {
conversationId && openConversationInFocusedPane(conversationId);
}, [openConversationInFocusedPane]);
const openSplitConversationId = React.useCallback((conversationId: DConversationId | null) => {
conversationId && openConversationInSplitPane(conversationId);
}, [openConversationInSplitPane]);
const handleNavigateHistory = React.useCallback((direction: 'back' | 'forward') => {
if (navigateHistoryInFocusedPane(direction))
showNextTitleChange.current = true;
}, [navigateHistoryInFocusedPane]);
React.useEffect(() => {
if (showNextTitleChange.current) {
showNextTitleChange.current = false;
const title = (focusedChatNumber >= 0 ? `#${focusedChatNumber + 1} · ` : '') + (focusedChatTitle || 'New Chat');
const id = addSnackbar({ key: 'focused-title', message: title, type: 'title' });
return () => removeSnackbar(id);
}
}, [focusedChatNumber, focusedChatTitle]);
// Execution
const _handleExecute = React.useCallback(async (chatModeId: ChatModeId, conversationId: DConversationId, history: DMessage[]): Promise<void> => {
const chatLLMId = getChatLLMId();
if (!chatModeId || !conversationId || !chatLLMId) return;
// "/command ...": overrides the chat mode
const lastMessage = history.length > 0 ? history[history.length - 1] : null;
if (lastMessage?.role === 'user') {
const pieces = extractCommands(lastMessage.text);
if (pieces.length == 2 && pieces[0].type === 'cmd' && pieces[1].type === 'text') {
const [command, prompt] = [pieces[0].value, pieces[1].value];
if (CmdRunProdia.includes(command)) {
setMessages(conversationId, history);
return await runImageGenerationUpdatingState(conversationId, prompt);
}
if (CmdRunReact.includes(command) && chatLLMId) {
setMessages(conversationId, history);
return await runReActUpdatingState(conversationId, prompt, chatLLMId);
}
if (CmdAddRoleMessage.includes(command)) {
lastMessage.role = command.startsWith('/s') ? 'system' : command.startsWith('/a') ? 'assistant' : 'user';
lastMessage.sender = 'Bot';
lastMessage.text = prompt;
return setMessages(conversationId, history);
const chatCommand = extractChatCommand(lastMessage.text)[0];
if (chatCommand && chatCommand.type === 'cmd') {
switch (chatCommand.providerId) {
case 'ass-beam':
return ConversationManager.getHandler(conversationId).beamStore.create(history);
case 'ass-browse':
setMessages(conversationId, history);
return await runBrowseGetPageUpdatingState(conversationId, chatCommand.params!);
case 'ass-t2i':
setMessages(conversationId, history);
return await runImageGenerationUpdatingState(conversationId, chatCommand.params!);
case 'ass-react':
setMessages(conversationId, history);
return await runReActUpdatingState(conversationId, chatCommand.params!, chatLLMId);
case 'chat-alter':
if (chatCommand.command === '/clear') {
if (chatCommand.params === 'all')
return setMessages(conversationId, []);
const helpMessage = createDMessage('assistant', 'This command requires the \'all\' parameter to confirm the operation.');
helpMessage.originLLM = Brand.Title.Base;
return setMessages(conversationId, [...history, helpMessage]);
}
Object.assign(lastMessage, {
role: chatCommand.command.startsWith('/s') ? 'system' : chatCommand.command.startsWith('/a') ? 'assistant' : 'user',
sender: 'Bot',
text: chatCommand.params || '',
} satisfies Partial<DMessage>);
return setMessages(conversationId, history);
case 'cmd-help':
const chatCommandsText = findAllChatCommands()
.map(cmd => ` - ${cmd.primary}` + (cmd.alternatives?.length ? ` (${cmd.alternatives.join(', ')})` : '') + `: ${cmd.description}`)
.join('\n');
const helpMessage = createDMessage('assistant', 'Available Chat Commands:\n' + chatCommandsText);
helpMessage.originLLM = Brand.Title.Base;
return setMessages(conversationId, [...history, helpMessage]);
default:
return setMessages(conversationId, [...history, createDMessage('assistant', 'This command is not supported.')]);
}
}
}
// get the focused system purpose (note: we don't react to it, or it would invalidate half UI components..)
const conversationSystemPurposeId = getConversationSystemPurposeId(conversationId);
if (!conversationSystemPurposeId)
return setMessages(conversationId, [...history, createDMessage('assistant', 'No persona selected.')]);
// synchronous long-duration tasks, which update the state as they go
if (chatLLMId && systemPurposeId) {
if (chatLLMId) {
switch (chatModeId) {
case 'immediate':
return await runAssistantUpdatingState(conversationId, history, chatLLMId, systemPurposeId);
case 'write-user':
case 'generate-text':
return await runAssistantUpdatingState(conversationId, history, chatLLMId, conversationSystemPurposeId, getUXLabsHighPerformance() ? 0 : getInstantAppChatPanesCount());
case 'generate-text-beam':
return ConversationManager.getHandler(conversationId).beamStore.create(history);
case 'append-user':
return setMessages(conversationId, history);
case 'react':
case 'generate-image':
if (!lastMessage?.text)
break;
// also add a 'fake' user message with the '/draw' command
setMessages(conversationId, history.map(message => message.id !== lastMessage.id ? message : {
...message,
text: `/draw ${lastMessage.text}`,
}));
return await runImageGenerationUpdatingState(conversationId, lastMessage.text);
case 'generate-react':
if (!lastMessage?.text)
break;
setMessages(conversationId, history);
return await runReActUpdatingState(conversationId, lastMessage.text, chatLLMId);
case 'draw-imagine':
case 'draw-imagine-plus':
if (!lastMessage?.text)
break;
const imagePrompt = chatModeId == 'draw-imagine-plus'
? await imaginePromptFromText(lastMessage.text) || 'An error sign.'
: lastMessage.text;
setMessages(conversationId, history.map(message => message.id !== lastMessage.id ? message : {
...message,
text: `${CmdRunProdia[0]} ${imagePrompt}`,
}));
return await runImageGenerationUpdatingState(conversationId, imagePrompt);
}
}
// ISSUE: if we're here, it means we couldn't do the job, at least sync the history
console.log('handleExecuteConversation: issue running', chatModeId, conversationId, lastMessage);
setMessages(conversationId, history);
};
}, [setMessages]);
const _findConversation = (conversationId: string) =>
conversationId ? useChatStore.getState().conversations.find(c => c.id === conversationId) ?? null : null;
const handleComposerAction = React.useCallback((chatModeId: ChatModeId, conversationId: DConversationId, multiPartMessage: ComposerOutputMultiPart): boolean => {
// validate inputs
if (multiPartMessage.length !== 1 || multiPartMessage[0].type !== 'text-block') {
addSnackbar({
key: 'chat-composer-action-invalid',
message: 'Only a single text part is supported for now.',
type: 'issue',
overrides: {
autoHideDuration: 2000,
},
});
return false;
}
const userText = multiPartMessage[0].text;
const handleExecuteChatHistory = async (conversationId: string, history: DMessage[]) =>
await handleExecuteConversation('immediate', conversationId, history);
// multicast: send the message to all the panes
const uniqueIds = new Set([conversationId]);
if (willMulticast)
chatPanes.forEach(pane => pane.conversationId && uniqueIds.add(pane.conversationId));
const handleDiagramFromText = async (diagramConfig: DiagramConfig | null) => setDiagramConfig(diagramConfig);
const handleImagineFromText = async (conversationId: string, messageText: string) => {
const conversation = _findConversation(conversationId);
if (conversation)
return await handleExecuteConversation('draw-imagine-plus', conversationId, [...conversation.messages, createDMessage('user', messageText)]);
};
const handleComposerNewMessage = async (chatModeId: ChatModeId, conversationId: string, userText: string) => {
const conversation = _findConversation(conversationId);
if (conversation)
return await handleExecuteConversation(chatModeId, conversationId, [...conversation.messages, createDMessage('user', userText)]);
};
const handleRegenerateAssistant = async () => {
const conversation = activeConversationId ? _findConversation(activeConversationId) : null;
if (conversation?.messages?.length) {
const lastMessage = conversation.messages[conversation.messages.length - 1];
if (lastMessage.role === 'assistant') {
const newMessages = [...conversation.messages];
newMessages.pop();
return await handleExecuteConversation('immediate', conversation.id, newMessages);
// we loop to handle both the normal and multicast modes
let enqueued = false;
for (const _cId of uniqueIds) {
const _conversation = getConversation(_cId);
if (_conversation) {
// start execution fire/forget
void _handleExecute(chatModeId, _cId, [..._conversation.messages, createDMessage('user', userText)]);
enqueued = true;
}
}
};
useGlobalShortcut('r', true, true, false, handleRegenerateAssistant);
return enqueued;
}, [chatPanes, willMulticast, _handleExecute]);
const handleConversationExecuteHistory = React.useCallback(async (conversationId: DConversationId, history: DMessage[], chatEffectBeam: boolean): Promise<void> => {
await _handleExecute(!chatEffectBeam ? 'generate-text' : 'generate-text-beam', conversationId, history);
}, [_handleExecute]);
const handleMessageRegenerateLast = React.useCallback(async () => {
const focusedConversation = getConversation(focusedConversationId);
if (focusedConversation?.messages?.length) {
const lastMessage = focusedConversation.messages[focusedConversation.messages.length - 1];
return await _handleExecute('generate-text', focusedConversation.id, lastMessage.role === 'assistant'
? focusedConversation.messages.slice(0, -1)
: [...focusedConversation.messages],
);
}
}, [focusedConversationId, _handleExecute]);
const handleTextDiagram = React.useCallback((diagramConfig: DiagramConfig | null) => setDiagramConfig(diagramConfig), []);
const handleTextImagine = React.useCallback(async (conversationId: DConversationId, messageText: string): Promise<void> => {
const conversation = getConversation(conversationId);
if (!conversation)
return;
const imaginedPrompt = await imaginePromptFromText(messageText) || 'An error sign.';
return await _handleExecute('generate-image', conversationId, [
...conversation.messages,
createDMessage('user', imaginedPrompt),
]);
}, [_handleExecute]);
const handleTextSpeak = React.useCallback(async (text: string): Promise<void> => {
await speakText(text);
}, []);
const handleImportConversation = () => setTradeConfig({ dir: 'import' });
// Chat actions
const handleExportConversation = (conversationId: string | null) => setTradeConfig({ dir: 'export', conversationId });
const handleConversationNew = React.useCallback((forceNoRecycle?: boolean) => {
const handleFlattenConversation = (conversationId: string) => setFlattenConversationId(conversationId);
// activate an existing new conversation if present, or create another
const conversationId = (newConversationId && !forceNoRecycle)
? newConversationId
: prependNewConversation(getConversationSystemPurposeId(focusedConversationId) ?? undefined);
setFocusedConversationId(conversationId);
// if a folder is active, add the new conversation to the folder
if (activeFolderId && conversationId)
useFolderStore.getState().addConversationToFolder(activeFolderId, conversationId);
useGlobalShortcut('n', true, false, true, () => {
newConversation();
// focus the composer
composerTextAreaRef.current?.focus();
});
const handleCloneConversation = (conversationId: string) => duplicateConversation(conversationId);
useGlobalShortcut('f', true, false, true, () => isConversationEmpty || activeConversationId && handleCloneConversation(activeConversationId));
}, [activeFolderId, focusedConversationId, newConversationId, prependNewConversation, setFocusedConversationId]);
const handleClearConversation = (conversationId: string) => setClearConfirmationId(conversationId);
useGlobalShortcut('x', true, false, true, () => isConversationEmpty || setClearConfirmationId(activeConversationId));
const handleConversationImportDialog = React.useCallback(() => setTradeConfig({ dir: 'import' }), []);
const handleConfirmedClearConversation = () => {
if (clearConfirmationId) {
setMessages(clearConfirmationId, []);
setAutoTitle(clearConfirmationId, '');
setClearConfirmationId(null);
const handleConversationExport = React.useCallback((conversationId: DConversationId | null, exportAll: boolean) => {
setTradeConfig({ dir: 'export', conversationId, exportAll });
}, []);
const handleConversationBranch = React.useCallback((srcConversationId: DConversationId, messageId: string | null): DConversationId | null => {
// clone data
const branchedConversationId = branchConversation(srcConversationId, messageId);
// if a folder is active, add the new conversation to the folder
if (activeFolderId && branchedConversationId)
useFolderStore.getState().addConversationToFolder(activeFolderId, branchedConversationId);
// replace/open a new pane with this
showNextTitleChange.current = true;
if (isMultiAddable)
openSplitConversationId(branchedConversationId);
else
setFocusedConversationId(branchedConversationId);
return branchedConversationId;
}, [activeFolderId, branchConversation, isMultiAddable, openSplitConversationId, setFocusedConversationId]);
const handleConversationFlatten = React.useCallback((conversationId: DConversationId) => setFlattenConversationId(conversationId), []);
const handleConfirmedClearConversation = React.useCallback(() => {
if (clearConversationId) {
setMessages(clearConversationId, []);
setClearConversationId(null);
}
};
}, [clearConversationId, setMessages]);
const handleDeleteAllConversations = () => setDeleteConfirmationId(SPECIAL_ID_ALL_CHATS);
const handleConversationClear = React.useCallback((conversationId: DConversationId) => setClearConversationId(conversationId), []);
const handleConfirmedDeleteConversation = () => {
if (deleteConfirmationId) {
if (deleteConfirmationId === SPECIAL_ID_ALL_CHATS) {
deleteAllConversations();
}// else
// deleteConversation(deleteConfirmationId);
setDeleteConfirmationId(null);
}
};
useGlobalShortcut('d', true, false, true, () => isConversationEmpty || setDeleteConfirmationId(activeConversationId));
const handleDeleteConversations = React.useCallback((conversationIds: DConversationId[], bypassConfirmation: boolean) => {
if (!bypassConfirmation)
return setDeleteConversationIds(conversationIds);
// perform deletion
const nextConversationId = deleteConversations(conversationIds, /*focusedSystemPurposeId ??*/ undefined);
setFocusedConversationId(nextConversationId);
setDeleteConversationIds(null);
}, [deleteConversations, setFocusedConversationId]);
const handleConfirmedDeleteConversations = React.useCallback(() => {
!!deleteConversationIds?.length && handleDeleteConversations(deleteConversationIds, true);
}, [deleteConversationIds, handleDeleteConversations]);
// Pluggable ApplicationBar components
// Shortcuts
const centerItems = React.useMemo(() =>
<ChatDropdowns conversationId={activeConversationId} />,
[activeConversationId],
const handleOpenChatLlmOptions = React.useCallback(() => {
const chatLLMId = getChatLLMId();
if (!chatLLMId) return;
openLlmOptions(chatLLMId);
}, [openLlmOptions]);
const shortcuts = React.useMemo((): GlobalShortcutItem[] => [
['o', true, true, false, handleOpenChatLlmOptions],
['r', true, true, false, handleMessageRegenerateLast],
['n', true, false, true, handleConversationNew],
['b', true, false, true, () => isFocusedChatEmpty || (focusedConversationId && handleConversationBranch(focusedConversationId, null))],
['x', true, false, true, () => isFocusedChatEmpty || (focusedConversationId && handleConversationClear(focusedConversationId))],
['d', true, false, true, () => focusedConversationId && handleDeleteConversations([focusedConversationId], false)],
['+', true, true, false, useUIPreferencesStore.getState().increaseContentScaling],
['-', true, true, false, useUIPreferencesStore.getState().decreaseContentScaling],
[ShortcutKeyName.Left, true, false, true, () => handleNavigateHistory('back')],
[ShortcutKeyName.Right, true, false, true, () => handleNavigateHistory('forward')],
], [focusedConversationId, handleConversationBranch, handleConversationClear, handleConversationNew, handleDeleteConversations, handleMessageRegenerateLast, handleNavigateHistory, handleOpenChatLlmOptions, isFocusedChatEmpty]);
useGlobalShortcuts(shortcuts);
// Pluggable Optima components
const barAltTitle = showAltTitleBar ? focusedChatTitle ?? 'No Chat' : null;
const barContent = React.useMemo(() =>
(barAltTitle === null)
? <ChatDropdowns conversationId={focusedConversationId} />
: <ChatTitle conversationId={focusedConversationId} conversationTitle={barAltTitle} />
, [focusedConversationId, barAltTitle],
);
const drawerItems = React.useMemo(() =>
<ChatDrawerItems
conversationId={activeConversationId}
onImportConversation={handleImportConversation}
onDeleteAllConversations={handleDeleteAllConversations}
const drawerContent = React.useMemo(() =>
<ChatDrawerMemo
isMobile={isMobile}
activeConversationId={focusedConversationId}
activeFolderId={activeFolderId}
chatPanesConversationIds={chatPanes.map(pane => pane.conversationId).filter(Boolean) as DConversationId[]}
disableNewButton={disableNewButton}
onConversationActivate={setFocusedConversationId}
onConversationBranch={handleConversationBranch}
onConversationNew={handleConversationNew}
onConversationsDelete={handleDeleteConversations}
onConversationsExportDialog={handleConversationExport}
onConversationsImportDialog={handleConversationImportDialog}
setActiveFolderId={setActiveFolderId}
/>,
[activeConversationId],
[activeFolderId, chatPanes, disableNewButton, focusedConversationId, handleConversationBranch, handleConversationExport, handleConversationImportDialog, handleConversationNew, handleDeleteConversations, isMobile, setFocusedConversationId],
);
const menuItems = React.useMemo(() =>
<ChatMenuItems
conversationId={activeConversationId} isConversationEmpty={isConversationEmpty} hasConversations={hasAnyContent}
isMessageSelectionMode={isMessageSelectionMode} setIsMessageSelectionMode={setIsMessageSelectionMode}
onClearConversation={handleClearConversation}
onDuplicateConversation={duplicateConversation}
onExportConversation={handleExportConversation}
onFlattenConversation={handleFlattenConversation}
<ChatPageMenuItems
isMobile={isMobile}
conversationId={focusedConversationId}
disableItems={!focusedConversationId || isFocusedChatEmpty}
hasConversations={!areChatsEmpty}
isMessageSelectionMode={isMessageSelectionMode}
onConversationBranch={handleConversationBranch}
onConversationClear={handleConversationClear}
onConversationFlatten={handleConversationFlatten}
// onConversationNew={handleConversationNew}
setIsMessageSelectionMode={setIsMessageSelectionMode}
/>,
[activeConversationId, duplicateConversation, hasAnyContent, isConversationEmpty, isMessageSelectionMode],
[areChatsEmpty, focusedConversationId, handleConversationBranch, handleConversationClear, handleConversationFlatten, /*handleConversationNew,*/ isFocusedChatEmpty, isMessageSelectionMode, isMobile],
);
useLayoutPluggable(centerItems, drawerItems, menuItems);
usePluggableOptimaLayout(drawerContent, barContent, menuItems, 'AppChat');
return <>
<ChatMessageList
conversationId={activeConversationId}
isMessageSelectionMode={isMessageSelectionMode} setIsMessageSelectionMode={setIsMessageSelectionMode}
onExecuteChatHistory={handleExecuteChatHistory}
onDiagramFromText={handleDiagramFromText}
onImagineFromText={handleImagineFromText}
sx={{
flexGrow: 1,
backgroundColor: 'background.level1',
overflowY: 'auto', // overflowY: 'hidden'
minHeight: 96,
}} />
<PanelGroup
direction={isMobile ? 'vertical' : 'horizontal'}
id='app-chat-panels'
>
<Ephemerals
conversationId={activeConversationId}
sx={{
// flexGrow: 0.1,
flexShrink: 0.5,
overflowY: 'auto',
minHeight: 64,
}} />
{chatPanes.map((pane, idx) => {
const _paneConversationId = pane.conversationId;
const _paneChatHandler = chatHandlers[idx] ?? null;
const _panesCount = chatPanes.length;
const _keyAndId = `chat-pane-${idx}-${_paneConversationId}`;
const _sepId = `sep-pane-${idx}-${_paneConversationId}`;
return <React.Fragment key={_keyAndId}>
<Panel
id={_keyAndId}
order={idx}
collapsible={chatPanes.length === 2}
defaultSize={(_panesCount === 3 && idx === 1) ? 34 : Math.round(100 / _panesCount)}
minSize={20}
onClick={(event) => {
const setFocus = chatPanes.length < 2 || !event.altKey;
setFocusedPane(setFocus ? idx : -1);
}}
onCollapse={() => {
// NOTE: despite the delay to try to let the draggin settle, there seems to be an issue with the Pane locking the screen
// setTimeout(() => removePane(idx), 50);
// more than 2 will result in an assertion from the framework
if (chatPanes.length === 2) removePane(idx);
}}
style={{
// for anchoring the scroll button in place
position: 'relative',
...(isMultiPane ? {
borderRadius: '0.375rem',
border: `2px solid ${idx === focusedPaneIndex
? ((willMulticast || !isMultiConversationId) ? theme.palette.primary.solidBg : theme.palette.primary.solidBg)
: ((willMulticast || !isMultiConversationId) ? theme.palette.warning.softActiveBg : theme.palette.background.level1)}`,
filter: (!willMulticast && idx !== focusedPaneIndex)
? (!isMultiConversationId ? 'grayscale(66.67%)' /* clone of the same */ : 'grayscale(66.67%)')
: undefined,
} : {
// NOTE: this is a workaround for the 'stuck-after-collapse-close' issue. We will collapse the 'other' pane, which
// will get it removed (onCollapse), and somehow this pane will be stuck with a pointerEvents: 'none' style, which de-facto
// disables further interaction with the chat. This is a workaround to re-enable the pointer events.
// The root cause seems to be a Dragstate not being reset properly, however the pointerEvents has been set since 0.0.56 while
// it was optional before: https://github.com/bvaughn/react-resizable-panels/issues/241
pointerEvents: 'auto',
}),
}}
>
<ScrollToBottom
bootToBottom
stickToBottom
sx={{
// allows the content to be scrolled (all browsers)
overflowY: 'auto',
// actually make sure this scrolls & fills
height: '100%',
}}
>
<ChatMessageList
conversationId={_paneConversationId}
conversationHandler={_paneChatHandler}
capabilityHasT2I={capabilityHasT2I}
chatLLMContextTokens={chatLLM?.contextTokens ?? null}
fitScreen={isMobile || isMultiPane}
isMessageSelectionMode={isMessageSelectionMode}
setIsMessageSelectionMode={setIsMessageSelectionMode}
onConversationBranch={handleConversationBranch}
onConversationExecuteHistory={handleConversationExecuteHistory}
onTextDiagram={handleTextDiagram}
onTextImagine={handleTextImagine}
onTextSpeak={handleTextSpeak}
sx={{
minHeight: '100%', // ensures filling of the blank space on newer chats
}}
/>
{/*<Ephemerals*/}
{/* conversationId={_paneConversationId}*/}
{/* sx={{*/}
{/* // TODO: Fixme post panels?*/}
{/* // flexGrow: 0.1,*/}
{/* flexShrink: 0.5,*/}
{/* overflowY: 'auto',*/}
{/* minHeight: 64,*/}
{/* }}*/}
{/*/>*/}
{/* Visibility and actions are handled via Context */}
<ScrollToBottomButton />
</ScrollToBottom>
{/* Best-Of Mode */}
<Beam
conversationHandler={_paneChatHandler}
isMobile={isMobile}
sx={{
overflowY: 'auto',
backgroundColor: 'background.level2',
position: 'absolute',
inset: 0,
zIndex: 1, // stay on top of Chips :shrug:
}}
/>
</Panel>
{/* Panel Separators & Resizers */}
{idx < _panesCount - 1 && (
<PanelResizeHandle id={_sepId}>
<PanelResizeInset />
</PanelResizeHandle>
)}
</React.Fragment>;
})}
</PanelGroup>
<Composer
conversationId={activeConversationId} messageId={null}
isDeveloperMode={systemPurposeId === 'Developer'}
isMobile={isMobile}
chatLLM={chatLLM}
composerTextAreaRef={composerTextAreaRef}
onNewMessage={handleComposerNewMessage}
conversationId={focusedConversationId}
capabilityHasT2I={capabilityHasT2I}
isMulticast={!isMultiConversationId ? null : isComposerMulticast}
isDeveloperMode={isFocusedChatDeveloper}
onAction={handleComposerAction}
onTextImagine={handleTextImagine}
setIsMulticast={setIsComposerMulticast}
sx={{
zIndex: 21, // position: 'sticky', bottom: 0,
backgroundColor: 'background.surface',
backgroundColor: themeBgAppChatComposer,
borderTop: `1px solid`,
borderTopColor: 'divider',
p: { xs: 1, md: 2 },
}} />
}}
/>
{/* Diagrams */}
{!!diagramConfig && <DiagramsModal config={diagramConfig} onClose={() => setDiagramConfig(null)} />}
{/* Flatten */}
{!!flattenConversationId && <FlattenerModal conversationId={flattenConversationId} onClose={() => setFlattenConversationId(null)} />}
{!!flattenConversationId && (
<FlattenerModal
conversationId={flattenConversationId}
onConversationBranch={handleConversationBranch}
onClose={() => setFlattenConversationId(null)}
/>
)}
{/* Import / Export */}
{!!tradeConfig && <TradeModal config={tradeConfig} onClose={() => setTradeConfig(null)} />}
{!!tradeConfig && (
<TradeModal
config={tradeConfig}
onConversationActivate={setFocusedConversationId}
onClose={() => setTradeConfig(null)}
/>
)}
{/* [confirmation] Reset Conversation */}
{!!clearConfirmationId && <ConfirmationModal
open onClose={() => setClearConfirmationId(null)} onPositive={handleConfirmedClearConversation}
confirmationText={'Are you sure you want to discard all the messages?'} positiveActionText={'Clear conversation'}
/>}
{!!clearConversationId && (
<ConfirmationModal
open onClose={() => setClearConversationId(null)} onPositive={handleConfirmedClearConversation}
confirmationText='Are you sure you want to discard all messages?'
positiveActionText='Clear conversation'
/>
)}
{/* [confirmation] Delete All */}
{!!deleteConfirmationId && <ConfirmationModal
open onClose={() => setDeleteConfirmationId(null)} onPositive={handleConfirmedDeleteConversation}
confirmationText={deleteConfirmationId === SPECIAL_ID_ALL_CHATS
? 'Are you absolutely sure you want to delete ALL conversations? This action cannot be undone.'
: 'Are you sure you want to delete this conversation?'}
positiveActionText={deleteConfirmationId === SPECIAL_ID_ALL_CHATS
? 'Yes, delete all'
: 'Delete conversation'}
/>}
{!!deleteConversationIds?.length && (
<ConfirmationModal
open onClose={() => setDeleteConversationIds(null)} onPositive={handleConfirmedDeleteConversations}
confirmationText={`Are you absolutely sure you want to delete ${deleteConversationIds.length === 1 ? 'this conversation' : 'these conversations'}? This action cannot be undone.`}
positiveActionText={deleteConversationIds.length === 1 ? 'Delete conversation' : `Yes, delete all ${deleteConversationIds.length} conversations`}
/>
)}
</>;
}
-40
View File
@@ -1,40 +0,0 @@
import { CmdRunProdia } from '~/modules/prodia/prodia.client';
import { CmdRunReact } from '~/modules/aifn/react/react';
import { CmdRunSearch } from '~/modules/google/search.client';
export const CmdAddRoleMessage: string[] = ['/assistant', '/a', '/system', '/s'];
export const commands = [...CmdRunProdia, ...CmdRunReact, ...CmdRunSearch, ...CmdAddRoleMessage];
export interface SentencePiece {
type: 'text' | 'cmd';
value: string;
}
/**
* Sentence to pieces (must have a leading slash) from the provided text
* Used by rendering functions, as well as input processing functions.
*/
export function extractCommands(input: string): SentencePiece[] {
const regexFromTags = commands.map(tag => `^\\${tag} `).join('\\b|') + '\\b';
const pattern = new RegExp(regexFromTags, 'g');
const result: SentencePiece[] = [];
let lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = pattern.exec(input)) !== null) {
if (match.index !== lastIndex)
result.push({ type: 'text', value: input.substring(lastIndex, match.index) });
result.push({ type: 'cmd', value: match[0].trim() });
lastIndex = pattern.lastIndex;
// Remove the space after the matched tag
if (input[lastIndex] === ' ')
lastIndex++;
}
if (lastIndex !== input.length)
result.push({ type: 'text', value: input.substring(lastIndex) });
return result;
}
+26
View File
@@ -0,0 +1,26 @@
import ClearIcon from '@mui/icons-material/Clear';
import type { ICommandsProvider } from './ICommandsProvider';
export const CommandsAlter: ICommandsProvider = {
id: 'chat-alter',
rank: 25,
getCommands: () => [{
primary: '/assistant',
alternatives: ['/a'],
arguments: ['text'],
description: 'Injects assistant response',
}, {
primary: '/system',
alternatives: ['/s'],
arguments: ['text'],
description: 'Injects system message',
}, {
primary: '/clear',
arguments: ['all'],
description: 'Clears the chat (removes all messages)',
Icon: ClearIcon,
}],
};
+17
View File
@@ -0,0 +1,17 @@
import { ChatBeamIcon } from '~/common/components/icons/ChatBeamIcon';
import { getUXLabsChatBeam } from '~/common/state/store-ux-labs';
import type { ICommandsProvider } from './ICommandsProvider';
export const CommandsBeam: ICommandsProvider = {
id: 'ass-beam',
rank: 9,
getCommands: () => getUXLabsChatBeam() ? [{
primary: '/beam',
arguments: ['prompt'],
description: 'Best of multiple replies',
Icon: ChatBeamIcon,
}] : [],
};
+16
View File
@@ -0,0 +1,16 @@
import LanguageIcon from '@mui/icons-material/Language';
import type { ICommandsProvider } from './ICommandsProvider';
export const CommandsBrowse: ICommandsProvider = {
id: 'ass-browse',
rank: 20,
getCommands: () => [{
primary: '/browse',
arguments: ['URL'],
description: 'Assistant will download the web page',
Icon: LanguageIcon,
}],
};
+17
View File
@@ -0,0 +1,17 @@
import FormatPaintIcon from '@mui/icons-material/FormatPaint';
import type { ICommandsProvider } from './ICommandsProvider';
export const CommandsDraw: ICommandsProvider = {
id: 'ass-t2i',
rank: 10,
getCommands: () => [{
primary: '/draw',
alternatives: ['/imagine', '/img'],
arguments: ['prompt'],
description: 'Assistant will draw the text',
Icon: FormatPaintIcon,
}],
};
+13
View File
@@ -0,0 +1,13 @@
import type { ICommandsProvider } from './ICommandsProvider';
export const CommandsHelp: ICommandsProvider = {
id: 'cmd-help',
rank: 99,
getCommands: () => [{
primary: '/help',
alternatives: ['/?'],
description: 'Display this list of commands',
}],
};
+16
View File
@@ -0,0 +1,16 @@
import PsychologyIcon from '@mui/icons-material/Psychology';
import type { ICommandsProvider } from './ICommandsProvider';
export const CommandsReact: ICommandsProvider = {
id: 'ass-react',
rank: 15,
getCommands: () => [{
primary: '/react',
arguments: ['prompt'],
description: 'Use the AI ReAct strategy to answer your query',
Icon: PsychologyIcon,
}],
};
@@ -0,0 +1,24 @@
import type { FunctionComponent } from 'react';
import type { CommandsProviderId } from './commands.registry';
export interface ChatCommand {
primary: string; // The primary command
alternatives?: string[]; // Alternative commands
arguments?: string[]; // Arguments for the command
description: string; // Description of what the command does
// usage?: string; // Example of how to use the command
Icon?: FunctionComponent; // Icon to display next to the command
}
export interface ICommandsProvider {
id: CommandsProviderId; // Unique identifier for the command provider
rank: number; // Rank of the provider, used to sort the providers in the UI
// Function to get commands with their alternatives and details
getCommands: () => ChatCommand[];
// Function to execute a command with optional parameters
// executeCommand: (command: string, params?: string[]) => Promise<boolean>;
}
@@ -0,0 +1,69 @@
import { ChatCommand, ICommandsProvider } from './ICommandsProvider';
import { CommandsAlter } from './CommandsAlter';
import { CommandsBeam } from './CommandsBeam';
import { CommandsBrowse } from './CommandsBrowse';
import { CommandsDraw } from './CommandsDraw';
import { CommandsHelp } from './CommandsHelp';
import { CommandsReact } from './CommandsReact';
export type CommandsProviderId = 'ass-beam' | 'ass-browse' | 'ass-t2i' | 'ass-react' | 'chat-alter' | 'cmd-help';
type TextCommandPiece =
| { type: 'text'; value: string; }
| { type: 'cmd'; providerId: CommandsProviderId, command: string; params?: string, isError?: boolean };
const ChatCommandsProviders: Record<CommandsProviderId, ICommandsProvider> = {
'ass-beam': CommandsBeam,
'ass-browse': CommandsBrowse,
'ass-react': CommandsReact,
'ass-t2i': CommandsDraw,
'chat-alter': CommandsAlter,
'cmd-help': CommandsHelp,
};
export function findAllChatCommands(): ChatCommand[] {
return Object.values(ChatCommandsProviders)
.sort((a, b) => a.rank - b.rank)
.map(p => p.getCommands())
.flat();
}
export function extractChatCommand(input: string): TextCommandPiece[] {
const inputTrimmed = input.trim();
// quick exit: command does not start with '/'
if (!inputTrimmed.startsWith('/'))
return [{ type: 'text', value: input }];
// Find the first space to separate the command from its parameters (if any)
const firstSpaceIndex = inputTrimmed.indexOf(' ');
const potentialCommand = inputTrimmed.substring(0, firstSpaceIndex >= 0 ? firstSpaceIndex : inputTrimmed.length);
// Check if the potential command is an actual command
for (const provider of Object.values(ChatCommandsProviders)) {
for (const cmd of provider.getCommands()) {
if (cmd.primary === potentialCommand || cmd.alternatives?.includes(potentialCommand)) {
// command needs arguments: take the rest of the input as parameters
if (cmd.arguments?.length) {
const params = firstSpaceIndex >= 0 ? inputTrimmed.substring(firstSpaceIndex + 1) : '';
return [{ type: 'cmd', providerId: provider.id, command: potentialCommand, params: params || undefined, isError: !params || undefined }];
}
// command without arguments, treat any text after as a separate text piece
const pieces: TextCommandPiece[] = [{ type: 'cmd', providerId: provider.id, command: potentialCommand, params: undefined }];
const textAfterCommand = firstSpaceIndex >= 0 ? inputTrimmed.substring(firstSpaceIndex + 1) : '';
if (textAfterCommand)
pieces.push({ type: 'text', value: textAfterCommand });
return pieces;
}
}
}
// No command found, return the entire input as text
return [{ type: 'text', value: input }];
}
+371
View File
@@ -0,0 +1,371 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { Box, Dropdown, IconButton, ListDivider, ListItem, ListItemButton, ListItemDecorator, Menu, MenuButton, MenuItem, Tooltip, Typography } from '@mui/joy';
import AddIcon from '@mui/icons-material/Add';
import CheckIcon from '@mui/icons-material/Check';
import ClearIcon from '@mui/icons-material/Clear';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import FileUploadOutlinedIcon from '@mui/icons-material/FileUploadOutlined';
import FolderIcon from '@mui/icons-material/Folder';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import type { DConversationId } from '~/common/state/store-chats';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { DFolder, useFolderStore } from '~/common/state/store-folders';
import { DebounceInputMemo } from '~/common/components/DebounceInput';
import { FoldersToggleOff } from '~/common/components/icons/FoldersToggleOff';
import { FoldersToggleOn } from '~/common/components/icons/FoldersToggleOn';
import { PageDrawerHeader } from '~/common/layout/optima/components/PageDrawerHeader';
import { PageDrawerList } from '~/common/layout/optima/components/PageDrawerList';
import { capitalizeFirstLetter } from '~/common/util/textUtils';
import { themeScalingMap, themeZIndexOverMobileDrawer } from '~/common/app.theme';
import { useOptimaDrawers } from '~/common/layout/optima/useOptimaDrawers';
import { useUIPreferencesStore } from '~/common/state/store-ui';
import { ChatDrawerItemMemo, FolderChangeRequest } from './ChatDrawerItem';
import { ChatFolderList } from './folders/ChatFolderList';
import { ChatNavGrouping, useChatNavRenderItems } from './useChatNavRenderItems';
import { ClearFolderText } from './folders/useFolderDropdown';
import { useChatShowRelativeSize } from '../store-app-chat';
// this is here to make shallow comparisons work on the next hook
const noFolders: DFolder[] = [];
/*
* Lists folders and returns the active folder
*/
export const useFolders = (activeFolderId: string | null) => useFolderStore(({ enableFolders, folders, toggleEnableFolders }) => {
// finds the active folder if any
const activeFolder = (enableFolders && activeFolderId)
? folders.find(folder => folder.id === activeFolderId) ?? null
: null;
return {
activeFolder,
allFolders: enableFolders ? folders : noFolders,
enableFolders,
toggleEnableFolders,
};
}, shallow);
export const ChatDrawerMemo = React.memo(ChatDrawer);
function ChatDrawer(props: {
isMobile: boolean,
activeConversationId: DConversationId | null,
activeFolderId: string | null,
chatPanesConversationIds: DConversationId[],
disableNewButton: boolean,
onConversationActivate: (conversationId: DConversationId) => void,
onConversationBranch: (conversationId: DConversationId, messageId: string | null) => void,
onConversationNew: (forceNoRecycle: boolean) => void,
onConversationsDelete: (conversationIds: DConversationId[], bypassConfirmation: boolean) => void,
onConversationsExportDialog: (conversationId: DConversationId | null, exportAll: boolean) => void,
onConversationsImportDialog: () => void,
setActiveFolderId: (folderId: string | null) => void,
}) {
const { onConversationActivate, onConversationBranch, onConversationNew, onConversationsDelete, onConversationsExportDialog } = props;
// local state
const [navGrouping, setNavGrouping] = React.useState<ChatNavGrouping>('date');
const [debouncedSearchQuery, setDebouncedSearchQuery] = React.useState('');
const [folderChangeRequest, setFolderChangeRequest] = React.useState<FolderChangeRequest | null>(null);
// external state
const { closeDrawer, closeDrawerOnMobile } = useOptimaDrawers();
const { showRelativeSize, toggleRelativeSize } = useChatShowRelativeSize();
const { activeFolder, allFolders, enableFolders, toggleEnableFolders } = useFolders(props.activeFolderId);
const { filteredChatsCount, filteredChatIDs, filteredChatsAreEmpty, filteredChatsBarBasis, filteredChatsIncludeActive, renderNavItems } = useChatNavRenderItems(
props.activeConversationId, props.chatPanesConversationIds, debouncedSearchQuery, activeFolder, allFolders, navGrouping, showRelativeSize,
);
const { contentScaling, showSymbols } = useUIPreferencesStore(state => ({
contentScaling: state.contentScaling,
showSymbols: state.zenMode !== 'cleaner',
}), shallow);
// New/Activate/Delete Conversation
const isMultiPane = props.chatPanesConversationIds.length >= 2;
const disableNewButton = props.disableNewButton && filteredChatsIncludeActive;
const newButtonDontRecycle = isMultiPane || !filteredChatsIncludeActive;
const handleButtonNew = React.useCallback(() => {
onConversationNew(newButtonDontRecycle);
closeDrawerOnMobile();
}, [closeDrawerOnMobile, newButtonDontRecycle, onConversationNew]);
const handleConversationActivate = React.useCallback((conversationId: DConversationId, closeMenu: boolean) => {
onConversationActivate(conversationId);
if (closeMenu)
closeDrawerOnMobile();
}, [closeDrawerOnMobile, onConversationActivate]);
const handleConversationsDeleteFiltered = React.useCallback(() => {
!!filteredChatIDs?.length && onConversationsDelete(filteredChatIDs, false);
}, [filteredChatIDs, onConversationsDelete]);
const handleConversationDeleteNoConfirmation = React.useCallback((conversationId: DConversationId) => {
conversationId && onConversationsDelete([conversationId], true);
}, [onConversationsDelete]);
const handleConversationsExport = React.useCallback(() => {
props.activeConversationId && onConversationsExportDialog(props.activeConversationId, true);
}, [onConversationsExportDialog, props.activeConversationId]);
// Folder change request
const handleConversationFolderChange = React.useCallback((folderChangeRequest: FolderChangeRequest) => setFolderChangeRequest(folderChangeRequest), []);
const handleConversationFolderCancel = React.useCallback(() => setFolderChangeRequest(null), []);
const handleConversationFolderSet = React.useCallback((conversationId: DConversationId, nextFolderId: string | null) => {
// Remove conversation from existing folders
const { addConversationToFolder, folders, removeConversationFromFolder } = useFolderStore.getState();
folders.forEach(folder => folder.conversationIds.includes(conversationId) && removeConversationFromFolder(folder.id, conversationId));
// Add conversation to the selected folder
nextFolderId && addConversationToFolder(nextFolderId, conversationId);
// Close the menu
setFolderChangeRequest(null);
}, []);
// memoize the group dropdown
const groupingComponent = React.useMemo(() => (
<Dropdown>
<MenuButton
aria-label='View options'
slots={{ root: IconButton }}
slotProps={{ root: { size: 'sm' } }}
>
<MoreVertIcon sx={{ fontSize: 'xl' }} />
</MenuButton>
<Menu placement='bottom-start' sx={{ minWidth: 180, zIndex: themeZIndexOverMobileDrawer /* need to be on top of the Modal on Mobile */ }}>
<ListItem>
<Typography level='body-sm'>Group By</Typography>
</ListItem>
{(['date', 'persona'] as const).map(_gName => (
<MenuItem
key={'group-' + _gName}
aria-label={`Group by ${_gName}`}
selected={navGrouping === _gName}
onClick={() => setNavGrouping(grouping => grouping === _gName ? false : _gName)}
>
<ListItemDecorator>{navGrouping === _gName && <CheckIcon />}</ListItemDecorator>
{capitalizeFirstLetter(_gName)}
</MenuItem>
))}
<ListDivider />
<ListItem>
<Typography level='body-sm'>Show</Typography>
</ListItem>
<MenuItem onClick={toggleRelativeSize}>
<ListItemDecorator>{showRelativeSize && <CheckIcon />}</ListItemDecorator>
Relative Size
</MenuItem>
</Menu>
</Dropdown>
), [navGrouping, showRelativeSize, toggleRelativeSize]);
return <>
{/* Drawer Header */}
<PageDrawerHeader title='Chats' onClose={closeDrawer}>
<Tooltip title={enableFolders ? 'Hide Folders' : 'Use Folders'}>
<IconButton onClick={toggleEnableFolders}>
{enableFolders ? <FoldersToggleOn /> : <FoldersToggleOff />}
</IconButton>
</Tooltip>
</PageDrawerHeader>
{/* Folders List */}
{/*<Box sx={{*/}
{/* display: 'grid',*/}
{/* gridTemplateRows: !enableFolders ? '0fr' : '1fr',*/}
{/* transition: 'grid-template-rows 0.42s cubic-bezier(.17,.84,.44,1)',*/}
{/* '& > div': {*/}
{/* padding: enableFolders ? 2 : 0,*/}
{/* transition: 'padding 0.42s cubic-bezier(.17,.84,.44,1)',*/}
{/* overflow: 'hidden',*/}
{/* },*/}
{/*}}>*/}
{enableFolders && (
<ChatFolderList
folders={allFolders}
contentScaling={contentScaling}
activeFolderId={props.activeFolderId}
onFolderSelect={props.setActiveFolderId}
/>
)}
{/*</Box>*/}
{/* Chats List */}
<PageDrawerList variant='plain' noTopPadding noBottomPadding tallRows>
{enableFolders && <ListDivider sx={{ mb: 0 }} />}
{/* Search Input Field */}
<DebounceInputMemo
minChars={2}
onDebounce={setDebouncedSearchQuery}
debounceTimeout={300}
placeholder='Search...'
aria-label='Search'
endDecorator={groupingComponent}
sx={{ m: 2 }}
/>
{/* New Chat Button */}
<ListItem sx={{ mx: '0.25rem', mb: 0.5 }}>
<ListItemButton
// variant='outlined'
variant={disableNewButton ? undefined : 'outlined'}
disabled={disableNewButton}
onClick={handleButtonNew}
sx={{
// ...PageDrawerTallItemSx,
px: 'calc(var(--ListItem-paddingX) - 0.25rem)',
// text size
fontSize: 'sm',
fontWeight: 'lg',
// style
borderRadius: 'md',
boxShadow: (disableNewButton || props.isMobile) ? 'none' : 'sm',
backgroundColor: 'background.popup',
transition: 'box-shadow 0.2s',
}}
>
<ListItemDecorator><AddIcon sx={{ '--Icon-fontSize': 'var(--joy-fontSize-xl)', pl: '0.125rem' }} /></ListItemDecorator>
New chat
</ListItemButton>
</ListItem>
{/*<ListDivider sx={{ mt: 0 }} />*/}
{/* List of Chat Titles (and actions) */}
<Box sx={{ flex: 1, overflowY: 'auto', ...themeScalingMap[contentScaling].chatDrawerItemSx }}>
{/*<ListItem sticky sx={{ justifyContent: 'space-between', boxShadow: 'sm' }}>*/}
{/* <Typography level='body-sm'>*/}
{/* Conversations*/}
{/* </Typography>*/}
{/* <ToggleButtonGroup variant='soft' size='sm' value={grouping} onChange={(_event, newValue) => newValue && setGrouping(newValue)}>*/}
{/* <IconButton value='off'>*/}
{/* <AccessTimeIcon />*/}
{/* </IconButton>*/}
{/* <IconButton value='persona'>*/}
{/* <PersonIcon />*/}
{/* </IconButton>*/}
{/* </ToggleButtonGroup>*/}
{/*</ListItem>*/}
{renderNavItems.map((item, idx) => item.type === 'nav-item-chat-data' ? (
<ChatDrawerItemMemo
key={'nav-chat-' + item.conversationId}
item={item}
showSymbols={showSymbols}
bottomBarBasis={filteredChatsBarBasis}
onConversationActivate={handleConversationActivate}
onConversationBranch={onConversationBranch}
onConversationDelete={handleConversationDeleteNoConfirmation}
onConversationExport={onConversationsExportDialog}
onConversationFolderChange={handleConversationFolderChange}
/>
) : item.type === 'nav-item-group' ? (
<Typography key={'nav-divider-' + idx} level='body-xs' sx={{ textAlign: 'center', my: 'calc(var(--ListItem-minHeight) / 4)' }}>
{item.title}
</Typography>
) : item.type === 'nav-item-info-message' ? (
<Typography key={'nav-info-' + idx} level='body-xs' sx={{ textAlign: 'center', my: 'calc(var(--ListItem-minHeight) / 2)' }}>
{item.message}
</Typography>
) : null,
)}
</Box>
<ListDivider sx={{ my: 0 }} />
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<ListItemButton onClick={props.onConversationsImportDialog} sx={{ flex: 1 }}>
<ListItemDecorator>
<FileUploadOutlinedIcon />
</ListItemDecorator>
Import
{/*<OpenAIIcon sx={{ ml: 'auto' }} />*/}
</ListItemButton>
<ListItemButton disabled={filteredChatsAreEmpty} onClick={handleConversationsExport} sx={{ flex: 1 }}>
<ListItemDecorator>
<FileDownloadOutlinedIcon />
</ListItemDecorator>
Export
</ListItemButton>
</Box>
<ListItemButton disabled={filteredChatsAreEmpty} onClick={handleConversationsDeleteFiltered}>
<ListItemDecorator>
<DeleteOutlineIcon />
</ListItemDecorator>
Delete {filteredChatsCount >= 2 ? `all ${filteredChatsCount} chats` : 'chat'}
</ListItemButton>
</PageDrawerList>
{/* [Menu] Chat Item Folder Change */}
{!!folderChangeRequest?.anchorEl && (
<CloseableMenu
bigIcons
open anchorEl={folderChangeRequest.anchorEl} onClose={handleConversationFolderCancel}
placement='bottom-start'
zIndex={themeZIndexOverMobileDrawer /* need to be on top of the Modal on Mobile */}
sx={{ minWidth: 200 }}
>
{/* Folder Assignment Buttons */}
{allFolders.map(folder => {
const isRequestFolder = folder === folderChangeRequest.currentFolder;
return (
<ListItem
key={folder.id}
variant={isRequestFolder ? 'soft' : 'plain'}
onClick={() => handleConversationFolderSet(folderChangeRequest.conversationId, folder.id)}
>
<ListItemButton>
<ListItemDecorator>
<FolderIcon sx={{ color: folder.color }} />
</ListItemDecorator>
{folder.title}
</ListItemButton>
</ListItem>
);
})}
{/* Remove Folder Assignment */}
{!!folderChangeRequest.currentFolder && (
<ListItem onClick={() => handleConversationFolderSet(folderChangeRequest.conversationId, null)}>
<ListItemButton>
<ListItemDecorator>
<ClearIcon />
</ListItemDecorator>
{ClearFolderText}
</ListItemButton>
</ListItem>
)}
</CloseableMenu>
)}
</>;
}
+390
View File
@@ -0,0 +1,390 @@
import * as React from 'react';
import { Avatar, Box, IconButton, ListItem, ListItemButton, ListItemDecorator, Sheet, styled, Tooltip, Typography } from '@mui/joy';
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import EditIcon from '@mui/icons-material/Edit';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import FolderIcon from '@mui/icons-material/Folder';
import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined';
import ForkRightIcon from '@mui/icons-material/ForkRight';
import { SystemPurposeId, SystemPurposes } from '../../../data';
import { conversationAutoTitle } from '~/modules/aifn/autotitle/autoTitle';
import type { DFolder } from '~/common/state/store-folders';
import { DConversationId, useChatStore } from '~/common/state/store-chats';
import { InlineTextarea } from '~/common/components/InlineTextarea';
import { isDeepEqual } from '~/common/util/jsUtils';
import { CHAT_NOVEL_TITLE } from '../AppChat';
// set to true to display the conversation IDs
// const DEBUG_CONVERSATION_IDS = false;
export const FadeInButton = styled(IconButton)({
opacity: 0.5,
transition: 'opacity 0.2s',
'&:hover': { opacity: 1 },
});
export const ChatDrawerItemMemo = React.memo(ChatDrawerItem, (prev, next) =>
// usign a custom function because `ChatNavigationItemData` is a complex object and memo won't work
isDeepEqual(prev.item, next.item) &&
prev.showSymbols === next.showSymbols &&
prev.bottomBarBasis === next.bottomBarBasis &&
prev.onConversationActivate === next.onConversationActivate &&
prev.onConversationBranch === next.onConversationBranch &&
prev.onConversationDelete === next.onConversationDelete &&
prev.onConversationExport === next.onConversationExport &&
prev.onConversationFolderChange === next.onConversationFolderChange,
);
export interface ChatNavigationItemData {
type: 'nav-item-chat-data',
conversationId: DConversationId;
isActive: boolean;
isAlsoOpen: string | false;
isEmpty: boolean;
title: string;
folder: DFolder | null | undefined; // null: 'All', undefined: do not show folder select
updatedAt: number;
messageCount: number;
assistantTyping: boolean;
systemPurposeId: SystemPurposeId;
searchFrequency: number;
}
export interface FolderChangeRequest {
conversationId: DConversationId;
anchorEl: HTMLButtonElement;
currentFolder: DFolder | null;
}
function ChatDrawerItem(props: {
// NOTE: always update the Memo comparison if you add or remove props
item: ChatNavigationItemData,
showSymbols: boolean,
bottomBarBasis: number,
onConversationActivate: (conversationId: DConversationId, closeMenu: boolean) => void,
onConversationBranch: (conversationId: DConversationId, messageId: string | null) => void,
onConversationDelete: (conversationId: DConversationId) => void,
onConversationExport: (conversationId: DConversationId, exportAll: boolean) => void,
onConversationFolderChange: (folderChangeRequest: FolderChangeRequest) => void,
}) {
// state
const [isEditingTitle, setIsEditingTitle] = React.useState(false);
const [isAutoEditingTitle, setIsAutoEditingTitle] = React.useState(false);
const [deleteArmed, setDeleteArmed] = React.useState(false);
// derived state
const { onConversationBranch, onConversationExport, onConversationFolderChange } = props;
const { conversationId, isActive, isAlsoOpen, title, folder, messageCount, assistantTyping, systemPurposeId, searchFrequency } = props.item;
const isNew = messageCount === 0;
// [effect] auto-disarm when inactive
const shallClose = deleteArmed && !isActive;
React.useEffect(() => {
if (shallClose)
setDeleteArmed(false);
}, [shallClose]);
// Activate
const handleConversationActivate = () => props.onConversationActivate(conversationId, true);
// branch
const handleConversationBranch = React.useCallback((event: React.MouseEvent) => {
event.stopPropagation();
conversationId && onConversationBranch(conversationId, null);
}, [conversationId, onConversationBranch]);
// export
const handleConversationExport = React.useCallback((event: React.MouseEvent) => {
event.stopPropagation();
conversationId && onConversationExport(conversationId, false);
}, [conversationId, onConversationExport]);
// Folder change
const handleFolderChangeBegin = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
onConversationFolderChange({
conversationId,
anchorEl: event.currentTarget,
currentFolder: folder ?? null,
});
}, [conversationId, folder, onConversationFolderChange]);
// Title Edit
const handleTitleEditBegin = React.useCallback(() => setIsEditingTitle(true), []);
const handleTitleEditCancel = React.useCallback(() => {
setIsEditingTitle(false);
}, []);
const handleTitleEditChange = React.useCallback((text: string) => {
setIsEditingTitle(false);
useChatStore.getState().setUserTitle(conversationId, text.trim());
}, [conversationId]);
const handleTitleEditAuto = React.useCallback(async () => {
setIsAutoEditingTitle(true);
await conversationAutoTitle(conversationId, true);
setIsAutoEditingTitle(false);
}, [conversationId]);
// Delete
const handleDeleteButtonShow = React.useCallback(() => setDeleteArmed(true), []);
const handleDeleteButtonHide = React.useCallback(() => setDeleteArmed(false), []);
const handleConversationDelete = React.useCallback((event: React.MouseEvent) => {
if (deleteArmed) {
setDeleteArmed(false);
event.stopPropagation();
props.onConversationDelete(conversationId);
}
}, [conversationId, deleteArmed, props]);
const textSymbol = SystemPurposes[systemPurposeId]?.symbol || '❓';
const progress = props.bottomBarBasis ? 100 * (searchFrequency || messageCount) / props.bottomBarBasis : 0;
const titleRowComponent = React.useMemo(() => <>
{/* Symbol, if globally enabled */}
{props.showSymbols && <ListItemDecorator>
{assistantTyping
? (
<Avatar
alt='typing' variant='plain'
src='https://i.giphy.com/media/jJxaUysjzO9ri/giphy.webp'
sx={{
width: '1.5rem',
height: '1.5rem',
borderRadius: 'var(--joy-radius-sm)',
}}
/>
) : (
<Typography sx={isNew ? { opacity: 0.4, filter: 'grayscale(0.75)' } : undefined}>
{/*{isNew ? '' : textSymbol}*/}
{textSymbol}
</Typography>
)}
</ListItemDecorator>}
{/* Title */}
{!isEditingTitle ? (
// using Box to not reset the parent font scaling
<Box
onDoubleClick={handleTitleEditBegin}
sx={{
color: isActive ? 'text.primary' : 'text.secondary',
flex: 1,
}}
>
{/*{DEBUG_CONVERSATION_IDS && `${conversationId} - `}*/}
{title.trim() ? title : CHAT_NOVEL_TITLE}{assistantTyping && '...'}
</Box>
) : (
<InlineTextarea
invertedColors
initialText={title}
onEdit={handleTitleEditChange}
onCancel={handleTitleEditCancel}
sx={{
flexGrow: 1,
ml: -1.5, mr: -0.5,
}}
/>
)}
{/* Display search frequency if it exists and is greater than 0 */}
{searchFrequency > 0 && (
<Box sx={{ ml: 1 }}>
<Typography level='body-sm'>
{searchFrequency}
</Typography>
</Box>
)}
</>, [assistantTyping, handleTitleEditBegin, handleTitleEditCancel, handleTitleEditChange, isActive, isEditingTitle, isNew, props.showSymbols, searchFrequency, textSymbol, title]);
const progressBarFixedComponent = React.useMemo(() =>
progress > 0 && (
<Box sx={{
backgroundColor: 'neutral.softHoverBg',
position: 'absolute', left: 0, bottom: 0, width: progress + '%', height: 4,
}} />
), [progress]);
return (isActive || isAlsoOpen) ? (
// Active or Also Open
<Sheet
variant={isActive ? 'solid' : 'outlined'}
invertedColors={isActive}
onClick={!isActive ? handleConversationActivate : undefined}
sx={{
// common
// position: 'relative', // for the progress bar (now disabled)
'--ListItem-minHeight': '2.75rem',
// differences between primary and secondary variants
...(isActive ? {
border: 'none', // there's a default border of 1px and invisible.. hmm
} : {
// '--variant-borderWidth': '0.125rem',
cursor: 'pointer',
}),
// style
backgroundColor: isActive ? 'neutral.solidActiveBg' : 'neutral.softBg',
borderRadius: 'md',
mx: '0.25rem',
'&:hover > button': {
opacity: 1, // fade in buttons when hovering, but by default wash them out a bit
},
}}
>
<ListItem sx={{ border: 'none', display: 'grid', gap: 0, px: 'calc(var(--ListItem-paddingX) - 0.25rem)' }}>
{/* Title row */}
<Box sx={{ display: 'flex', gap: 'var(--ListItem-gap)', minHeight: '2.25rem', alignItems: 'center' }}>
{titleRowComponent}
</Box>
{/* buttons row */}
{isActive && (
<Box sx={{ display: 'flex', gap: 0.5, minHeight: '2.25rem', alignItems: 'center' }}>
<ListItemDecorator />
{/* Current Folder color, and change initiator */}
{!deleteArmed && <>
{(folder !== undefined) && <>
<Tooltip disableInteractive title={folder ? `Change Folder (${folder.title})` : 'Add to Folder'}>
{folder ? (
<IconButton size='sm' onClick={handleFolderChangeBegin}>
<FolderIcon style={{ color: folder.color || 'inherit' }} />
</IconButton>
) : (
<FadeInButton size='sm' onClick={handleFolderChangeBegin}>
<FolderOutlinedIcon />
</FadeInButton>
)}
</Tooltip>
{/*<Divider orientation='vertical' sx={{ my: 1, opacity: 0.5 }} />*/}
</>}
<Tooltip disableInteractive title='Rename'>
<FadeInButton size='sm' disabled={isEditingTitle || isAutoEditingTitle} onClick={handleTitleEditBegin}>
<EditIcon />
</FadeInButton>
</Tooltip>
{!isNew && <>
<Tooltip disableInteractive title='Auto-Title'>
<FadeInButton size='sm' disabled={isEditingTitle || isAutoEditingTitle} onClick={handleTitleEditAuto}>
<AutoFixHighIcon />
</FadeInButton>
</Tooltip>
<Tooltip disableInteractive title='Branch'>
<FadeInButton size='sm' onClick={handleConversationBranch}>
<ForkRightIcon />
</FadeInButton>
</Tooltip>
<Tooltip disableInteractive title='Export Chat'>
<FadeInButton size='sm' onClick={handleConversationExport}>
<FileDownloadOutlinedIcon />
</FadeInButton>
</Tooltip>
</>}
</>}
{/* --> */}
<Box sx={{ flex: 1 }} />
{/* Delete [armed, arming] buttons */}
{/*{!searchFrequency && <>*/}
{deleteArmed && (
<Tooltip disableInteractive title='Confirm Deletion'>
<FadeInButton key='btn-del' variant='solid' color='success' size='sm' onClick={handleConversationDelete} sx={{ opacity: 1, mr: 0.5 }}>
<DeleteForeverIcon sx={{ color: 'danger.solidBg' }} />
</FadeInButton>
</Tooltip>
)}
<Tooltip disableInteractive title={deleteArmed ? 'Cancel Delete' : 'Delete'}>
<FadeInButton key='btn-arm' size='sm' onClick={deleteArmed ? handleDeleteButtonHide : handleDeleteButtonShow} sx={deleteArmed ? { opacity: 1 } : {}}>
{deleteArmed ? <CloseRoundedIcon /> : <DeleteOutlineIcon />}
</FadeInButton>
</Tooltip>
{/*</>}*/}
</Box>
)}
{/* View places row */}
{isAlsoOpen && (
<Typography level='body-xs' sx={{ mx: 'auto' }}>
<em>In view {isAlsoOpen}</em>
</Typography>
)}
</ListItem>
{/* Optional progress bar, underlay */}
{/* NOTE: disabled on 20240204: quite distracting on the active chat sheet */}
{/*{progressBarFixedComponent}*/}
</Sheet>
) : (
// Inactive Conversation - click to activate
<ListItem
// sx={{ '--ListItem-minHeight': '2.75rem' }}
>
<ListItemButton
onClick={handleConversationActivate}
sx={{
border: 'none', // there's a default border of 1px and invisible.. hmm
position: 'relative', // for the progress bar
}}
>
{titleRowComponent}
{/* Optional progress bar, underlay */}
{progressBarFixedComponent}
</ListItemButton>
</ListItem>
);
}
@@ -1,24 +1,31 @@
import * as React from 'react';
import type { DConversationId } from '~/common/state/store-chats';
import { useChatLLMDropdown } from './useLLMDropdown';
import { usePersonaIdDropdown } from './usePersonaDropdown';
import { useFolderDropdown } from './folders/useFolderDropdown';
export function ChatDropdowns(props: {
conversationId: string | null
conversationId: DConversationId | null
}) {
// state
const { chatLLMDropdown } = useChatLLMDropdown();
const { personaDropdown } = usePersonaIdDropdown(props.conversationId);
const { folderDropdown } = useFolderDropdown(props.conversationId);
return <>
{/* Model selector */}
{chatLLMDropdown}
{/* Persona selector */}
{personaDropdown}
{/* Model selector */}
{chatLLMDropdown}
{/* Folder selector */}
{folderDropdown}
</>;
}
+168 -106
View File
@@ -4,31 +4,42 @@ import { shallow } from 'zustand/shallow';
import { Box, List } from '@mui/joy';
import { SxProps } from '@mui/joy/styles/types';
import { DiagramConfig } from '~/modules/aifn/digrams/DiagramsModal';
import { speakText } from '~/modules/elevenlabs/elevenlabs.client';
import { useChatLLM } from '~/modules/llms/store-llms';
import type { DiagramConfig } from '~/modules/aifn/digrams/DiagramsModal';
import { GlobalShortcut, useGlobalShortcut } from '~/common/components/useGlobalShortcut';
import { createDMessage, DMessage, useChatStore } from '~/common/state/store-chats';
import { openLayoutPreferences } from '~/common/layout/store-applayout';
import { useCapabilityElevenLabs, useCapabilityProdia } from '~/common/components/useCapabilities';
import type { ConversationHandler } from '~/common/chats/ConversationHandler';
import { InlineError } from '~/common/components/InlineError';
import { PreferencesTab, useOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
import { ShortcutKeyName, useGlobalShortcut } from '~/common/components/useGlobalShortcut';
import { createDMessage, DConversationId, DMessage, getConversation, useChatStore } from '~/common/state/store-chats';
import { useBrowserTranslationWarning } from '~/common/components/useIsBrowserTranslating';
import { useCapabilityElevenLabs } from '~/common/components/useCapabilities';
import { useEphemerals } from '~/common/chats/EphemeralsStore';
import { ChatMessage } from './message/ChatMessage';
import { ChatMessage, ChatMessageMemo } from './message/ChatMessage';
import { CleanerMessage, MessagesSelectionHeader } from './message/CleanerMessage';
import { Ephemerals } from './Ephemerals';
import { PersonaSelector } from './persona-selector/PersonaSelector';
import { useChatShowSystemMessages } from '../store-app-chat';
import { useScrollToBottom } from './scroll-to-bottom/useScrollToBottom';
/**
* A list of ChatMessages
*/
export function ChatMessageList(props: {
conversationId: string | null,
isMessageSelectionMode: boolean, setIsMessageSelectionMode: (isMessageSelectionMode: boolean) => void,
onExecuteChatHistory: (conversationId: string, history: DMessage[]) => void,
onDiagramFromText: (diagramConfig: DiagramConfig | null) => Promise<any>,
onImagineFromText: (conversationId: string, selectedText: string) => Promise<any>,
sx?: SxProps
conversationId: DConversationId | null,
conversationHandler: ConversationHandler | null,
capabilityHasT2I: boolean,
chatLLMContextTokens: number | null,
fitScreen: boolean,
isMessageSelectionMode: boolean,
onConversationBranch: (conversationId: DConversationId, messageId: string) => void,
onConversationExecuteHistory: (conversationId: DConversationId, history: DMessage[], chatEffectBeam: boolean) => Promise<void>,
onTextDiagram: (diagramConfig: DiagramConfig | null) => void,
onTextImagine: (conversationId: DConversationId, selectedText: string) => Promise<void>,
onTextSpeak: (selectedText: string) => Promise<void>,
setIsMessageSelectionMode: (isMessageSelectionMode: boolean) => void,
sx?: SxProps,
}) {
// state
@@ -37,66 +48,85 @@ export function ChatMessageList(props: {
const [selectedMessages, setSelectedMessages] = React.useState<Set<string>>(new Set());
// external state
const { notifyBooting } = useScrollToBottom();
const { openPreferencesTab } = useOptimaLayout();
const [showSystemMessages] = useChatShowSystemMessages();
const { messages, editMessage, deleteMessage, historyTokenCount } = useChatStore(state => {
const optionalTranslationWarning = useBrowserTranslationWarning();
const { conversationMessages, historyTokenCount, editMessage, deleteMessage, setMessages } = useChatStore(state => {
const conversation = state.conversations.find(conversation => conversation.id === props.conversationId);
return {
messages: conversation ? conversation.messages : [],
editMessage: state.editMessage, deleteMessage: state.deleteMessage,
conversationMessages: conversation ? conversation.messages : [],
historyTokenCount: conversation ? conversation.tokenCount : 0,
deleteMessage: state.deleteMessage,
editMessage: state.editMessage,
setMessages: state.setMessages,
};
}, shallow);
const { chatLLM } = useChatLLM();
const { mayWork: isImaginable } = useCapabilityProdia();
const ephemerals = useEphemerals(props.conversationHandler);
const { mayWork: isSpeakable } = useCapabilityElevenLabs();
// derived state
const { conversationId, capabilityHasT2I, onConversationBranch, onConversationExecuteHistory, onTextDiagram, onTextImagine, onTextSpeak } = props;
// text actions
const handleAppendMessage = (text: string) =>
props.conversationId && props.onExecuteChatHistory(props.conversationId, [...messages, createDMessage('user', text)]);
const handleTextDiagram = async (messageId: string, text: string) => {
if (props.conversationId) {
await props.onDiagramFromText({ conversationId: props.conversationId, messageId, text });
} else
return Promise.reject('No conversation');
};
const handleTextImagine = async (text: string) => {
if (!isImaginable) {
openLayoutPreferences(2);
} else if (props.conversationId) {
setIsImagining(true);
await props.onImagineFromText(props.conversationId, text);
setIsImagining(false);
} else
return Promise.reject('No conversation');
};
const handleTextSpeak = async (text: string) => {
if (!isSpeakable) {
openLayoutPreferences(3);
} else {
setIsSpeaking(true);
await speakText(text);
setIsSpeaking(false);
}
};
const handleRunExample = React.useCallback(async (text: string) => {
conversationId && await onConversationExecuteHistory(conversationId, [...conversationMessages, createDMessage('user', text)], false);
}, [conversationId, conversationMessages, onConversationExecuteHistory]);
// message menu methods proxy
const handleMessageDelete = (messageId: string) =>
props.conversationId && deleteMessage(props.conversationId, messageId);
const handleConversationBranch = React.useCallback((messageId: string) => {
conversationId && onConversationBranch(conversationId, messageId);
}, [conversationId, onConversationBranch]);
const handleMessageEdit = (messageId: string, newText: string) =>
props.conversationId && editMessage(props.conversationId, messageId, { text: newText }, true);
const handleConversationRestartFrom = React.useCallback(async (messageId: string, offset: number, chatEffectBeam: boolean) => {
const messages = getConversation(conversationId)?.messages;
if (messages) {
const truncatedHistory = messages.slice(0, messages.findIndex(m => m.id === messageId) + offset + 1);
conversationId && await onConversationExecuteHistory(conversationId, truncatedHistory, chatEffectBeam);
}
}, [conversationId, onConversationExecuteHistory]);
const handleMessageRestartFrom = (messageId: string, offset: number) => {
const truncatedHistory = messages.slice(0, messages.findIndex(m => m.id === messageId) + offset + 1);
props.conversationId && props.onExecuteChatHistory(props.conversationId, truncatedHistory);
};
const handleConversationTruncate = React.useCallback((messageId: string) => {
const messages = getConversation(conversationId)?.messages;
if (conversationId && messages) {
const truncatedHistory = messages.slice(0, messages.findIndex(m => m.id === messageId) + 1);
setMessages(conversationId, truncatedHistory);
}
}, [conversationId, setMessages]);
const handleMessageDelete = React.useCallback((messageId: string) => {
conversationId && deleteMessage(conversationId, messageId);
}, [conversationId, deleteMessage]);
const handleMessageEdit = React.useCallback((messageId: string, newText: string) => {
conversationId && editMessage(conversationId, messageId, { text: newText }, true);
}, [conversationId, editMessage]);
const handleTextDiagram = React.useCallback(async (messageId: string, text: string) => {
conversationId && onTextDiagram({ conversationId: conversationId, messageId, text });
}, [conversationId, onTextDiagram]);
const handleTextImagine = React.useCallback(async (text: string) => {
if (!capabilityHasT2I)
return openPreferencesTab(PreferencesTab.Draw);
if (conversationId) {
setIsImagining(true);
await onTextImagine(conversationId, text);
setIsImagining(false);
}
}, [capabilityHasT2I, conversationId, onTextImagine, openPreferencesTab]);
const handleTextSpeak = React.useCallback(async (text: string) => {
if (!isSpeakable)
return openPreferencesTab(PreferencesTab.Voice);
setIsSpeaking(true);
await onTextSpeak(text);
setIsSpeaking(false);
}, [isSpeakable, onTextSpeak, openPreferencesTab]);
// operate on the local selection set
@@ -104,7 +134,7 @@ export function ChatMessageList(props: {
const handleSelectAll = (selected: boolean) => {
const newSelected = new Set<string>();
if (selected)
for (const message of messages)
for (const message of conversationMessages)
newSelected.add(message.id);
setSelectedMessages(newSelected);
};
@@ -116,86 +146,68 @@ export function ChatMessageList(props: {
};
const handleSelectionDelete = () => {
if (props.conversationId)
if (conversationId)
for (const selectedMessage of selectedMessages)
deleteMessage(props.conversationId, selectedMessage);
deleteMessage(conversationId, selectedMessage);
setSelectedMessages(new Set());
};
useGlobalShortcut(props.isMessageSelectionMode && GlobalShortcut.Esc, false, false, false, () => {
useGlobalShortcut(props.isMessageSelectionMode && ShortcutKeyName.Esc, false, false, false, () => {
props.setIsMessageSelectionMode(false);
});
// text-diff functionality, find the messages to diff with
// text-diff functionality: only diff the last message and when it's complete (not typing), and they're similar in size
const { diffMessage, diffText } = React.useMemo(() => {
const [msgB, msgA] = messages.filter(m => m.role === 'assistant').reverse();
const { diffTargetMessage, diffPrevText } = React.useMemo(() => {
const [msgB, msgA] = conversationMessages.filter(m => m.role === 'assistant').reverse();
if (msgB?.text && msgA?.text && !msgB?.typing) {
const textA = msgA.text, textB = msgB.text;
const lenA = textA.length, lenB = textB.length;
if (lenA > 80 && lenB > 80 && lenA > lenB / 3 && lenB > lenA / 3)
return { diffMessage: msgB, diffText: textA };
return { diffTargetMessage: msgB, diffPrevText: textA };
}
return { diffMessage: undefined, diffText: undefined };
}, [messages]);
return { diffTargetMessage: undefined, diffPrevText: undefined };
}, [conversationMessages]);
// scroll to the very bottom of a new chat
React.useEffect(() => {
if (conversationId)
notifyBooting();
}, [conversationId, notifyBooting]);
// no content: show the persona selector
const filteredMessages = messages
.filter(m => m.role !== 'system' || showSystemMessages) // hide the System message if the user choses to
.reverse(); // 'reverse' is because flexDirection: 'column-reverse' to auto-snap-to-bottom
const filteredMessages = conversationMessages
.filter(m => m.role !== 'system' || showSystemMessages); // hide the System message if the user choses to
// when there are no messages, show the purpose selector
if (!filteredMessages.length)
return props.conversationId ? (
<Box sx={props.sx || {}}>
<PersonaSelector conversationId={props.conversationId} runExample={handleAppendMessage} />
return (
<Box sx={{ ...props.sx }}>
{conversationId
? <PersonaSelector conversationId={conversationId} runExample={handleRunExample} />
: <InlineError severity='info' error='Select a conversation' sx={{ m: 2 }} />}
</Box>
) : null;
);
return (
<List sx={{
p: 0, ...(props.sx || {}),
// this makes sure that the the window is scrolled to the bottom (column-reverse)
display: 'flex', flexDirection: 'column-reverse',
display: 'flex',
flexDirection: 'column',
// fix for the double-border on the last message (one by the composer, one to the bottom of the message)
marginBottom: '-1px',
// marginBottom: '-1px',
}}>
{filteredMessages.map((message, idx) =>
props.isMessageSelectionMode ? (
{optionalTranslationWarning}
<CleanerMessage
key={'sel-' + message.id}
message={message}
isBottom={idx === 0} remainingTokens={(chatLLM ? chatLLM.contextTokens : 0) - historyTokenCount}
selected={selectedMessages.has(message.id)} onToggleSelected={handleSelectMessage}
/>
) : (
<ChatMessage
key={'msg-' + message.id}
message={message}
diffPreviousText={message === diffMessage ? diffText : undefined}
isBottom={idx === 0}
isImagining={isImagining} isSpeaking={isSpeaking}
onMessageDelete={() => handleMessageDelete(message.id)}
onMessageEdit={newText => handleMessageEdit(message.id, newText)}
onMessageRunFrom={(offset: number) => handleMessageRestartFrom(message.id, offset)}
onTextDiagram={(text: string) => handleTextDiagram(message.id, text)}
onTextImagine={handleTextImagine} onTextSpeak={handleTextSpeak}
/>
),
)}
{/* Header at the bottom because of 'row-reverse' */}
{props.isMessageSelectionMode && (
<MessagesSelectionHeader
hasSelected={selectedMessages.size > 0}
isBottom={filteredMessages.length === 0}
sumTokens={historyTokenCount}
onClose={() => props.setIsMessageSelectionMode(false)}
onSelectAll={handleSelectAll}
@@ -203,6 +215,56 @@ export function ChatMessageList(props: {
/>
)}
{filteredMessages.map((message, idx, { length: count }) => {
// Optimization: if the component is going to change (e.g. the message is typing), we don't want to memoize it to not throw garbage in memory
const ChatMessageMemoOrNot = message.typing ? ChatMessage : ChatMessageMemo;
return props.isMessageSelectionMode ? (
<CleanerMessage
key={'sel-' + message.id}
message={message}
remainingTokens={props.chatLLMContextTokens ? (props.chatLLMContextTokens - historyTokenCount) : undefined}
selected={selectedMessages.has(message.id)} onToggleSelected={handleSelectMessage}
/>
) : (
<ChatMessageMemoOrNot
key={'msg-' + message.id}
message={message}
diffPreviousText={message === diffTargetMessage ? diffPrevText : undefined}
fitScreen={props.fitScreen}
isBottom={idx === count - 1}
isImagining={isImagining}
isSpeaking={isSpeaking}
onConversationBranch={handleConversationBranch}
onConversationRestartFrom={handleConversationRestartFrom}
onConversationTruncate={handleConversationTruncate}
onMessageDelete={handleMessageDelete}
onMessageEdit={handleMessageEdit}
onTextDiagram={handleTextDiagram}
onTextImagine={handleTextImagine}
onTextSpeak={handleTextSpeak}
/>
);
},
)}
{!!ephemerals.length && (
<Ephemerals
ephemerals={ephemerals}
conversationId={props.conversationId}
sx={{
mt: 'auto',
overflowY: 'auto',
minHeight: 64,
}}
/>
)}
</List>
);
}
@@ -0,0 +1,151 @@
import * as React from 'react';
import { Box, IconButton, ListDivider, ListItemDecorator, MenuItem, Switch, Tooltip } from '@mui/joy';
import AddIcon from '@mui/icons-material/Add';
import CheckBoxOutlineBlankOutlinedIcon from '@mui/icons-material/CheckBoxOutlineBlankOutlined';
import CheckBoxOutlinedIcon from '@mui/icons-material/CheckBoxOutlined';
import ClearIcon from '@mui/icons-material/Clear';
import CompressIcon from '@mui/icons-material/Compress';
import ForkRightIcon from '@mui/icons-material/ForkRight';
import HorizontalSplitIcon from '@mui/icons-material/HorizontalSplit';
import HorizontalSplitOutlinedIcon from '@mui/icons-material/HorizontalSplitOutlined';
import SettingsSuggestOutlinedIcon from '@mui/icons-material/SettingsSuggestOutlined';
import VerticalSplitIcon from '@mui/icons-material/VerticalSplit';
import VerticalSplitOutlinedIcon from '@mui/icons-material/VerticalSplitOutlined';
import type { DConversationId } from '~/common/state/store-chats';
import { KeyStroke } from '~/common/components/KeyStroke';
import { useOptimaDrawers } from '~/common/layout/optima/useOptimaDrawers';
import { useChatShowSystemMessages } from '../store-app-chat';
import { usePaneDuplicateOrClose } from './panes/usePanesManager';
export function ChatPageMenuItems(props: {
isMobile: boolean,
conversationId: DConversationId | null,
disableItems: boolean,
hasConversations: boolean,
isMessageSelectionMode: boolean,
onConversationBranch: (conversationId: DConversationId, messageId: string | null) => void,
onConversationClear: (conversationId: DConversationId) => void,
onConversationFlatten: (conversationId: DConversationId) => void,
// onConversationNew: (forceNoRecycle: boolean) => void,
setIsMessageSelectionMode: (isMessageSelectionMode: boolean) => void,
}) {
// external state
const { closePageMenu } = useOptimaDrawers();
const { canAddPane, isMultiPane, duplicateFocusedPane, removeOtherPanes } = usePaneDuplicateOrClose();
const [showSystemMessages, setShowSystemMessages] = useChatShowSystemMessages();
const handleIncreaseMultiPane = React.useCallback((event?: React.MouseEvent) => {
event?.stopPropagation();
// create a new pane with the current conversation
duplicateFocusedPane();
// load a brand new conversation inside
// FIXME: still testing this
// props.onConversationNew(true);
}, [duplicateFocusedPane]);
const handleToggleMultiPane = React.useCallback((_event: React.MouseEvent) => {
if (isMultiPane)
removeOtherPanes();
else
handleIncreaseMultiPane(undefined);
}, [handleIncreaseMultiPane, isMultiPane, removeOtherPanes]);
const closeMenu = (event: React.MouseEvent) => {
event.stopPropagation();
closePageMenu();
};
const handleConversationClear = (event: React.MouseEvent<HTMLDivElement>) => {
closeMenu(event);
props.conversationId && props.onConversationClear(props.conversationId);
};
const handleConversationBranch = (event: React.MouseEvent<HTMLDivElement>) => {
closeMenu(event);
props.conversationId && props.onConversationBranch(props.conversationId, null);
};
const handleConversationFlatten = (event: React.MouseEvent<HTMLDivElement>) => {
closeMenu(event);
props.conversationId && props.onConversationFlatten(props.conversationId);
};
const handleToggleMessageSelectionMode = (event: React.MouseEvent) => {
closeMenu(event);
props.setIsMessageSelectionMode(!props.isMessageSelectionMode);
};
const handleToggleSystemMessages = () => setShowSystemMessages(!showSystemMessages);
return <>
{/* System Message(s) */}
<MenuItem onClick={handleToggleSystemMessages}>
<ListItemDecorator><SettingsSuggestOutlinedIcon /></ListItemDecorator>
System messages
<Switch checked={showSystemMessages} onChange={handleToggleSystemMessages} sx={{ ml: 'auto' }} />
</MenuItem>
{/* Un /Split */}
<MenuItem onClick={handleToggleMultiPane}>
<ListItemDecorator>{props.isMobile
? (isMultiPane ? <HorizontalSplitIcon /> : <HorizontalSplitOutlinedIcon />)
: (isMultiPane ? <VerticalSplitIcon /> : <VerticalSplitOutlinedIcon />)
}</ListItemDecorator>
{/* Unsplit / Split text*/}
{isMultiPane ? 'Unsplit' : props.isMobile ? 'Split Down' : 'Split Right'}
{/* '+' */}
{isMultiPane && (
<Tooltip title='Add Another Split'>
<IconButton
size='sm'
variant='outlined'
disabled={!canAddPane}
onClick={handleIncreaseMultiPane}
sx={{ ml: 'auto', /*mr: '2px',*/ my: '-0.25rem' /* absorb the menuItem padding */ }}
>
<AddIcon />
</IconButton>
</Tooltip>
)}
</MenuItem>
<MenuItem disabled={props.disableItems} onClick={handleConversationBranch}>
<ListItemDecorator><ForkRightIcon /></ListItemDecorator>
Branch
</MenuItem>
<ListDivider />
<MenuItem disabled={props.disableItems} onClick={handleToggleMessageSelectionMode} sx={props.isMessageSelectionMode ? { fontWeight: 'lg' } : {}}>
<ListItemDecorator>{props.isMessageSelectionMode ? <CheckBoxOutlinedIcon /> : <CheckBoxOutlineBlankOutlinedIcon />}</ListItemDecorator>
Cleanup ...
</MenuItem>
<MenuItem disabled={props.disableItems} onClick={handleConversationFlatten}>
<ListItemDecorator><CompressIcon color='success' /></ListItemDecorator>
Compress ...
</MenuItem>
<ListDivider />
<MenuItem disabled={props.disableItems} onClick={handleConversationClear}>
<ListItemDecorator><ClearIcon /></ListItemDecorator>
<Box sx={{ flexGrow: 1, display: 'flex', justifyContent: 'space-between', gap: 1 }}>
Reset Chat
{!props.disableItems && <KeyStroke combo='Ctrl + Alt + X' />}
</Box>
</MenuItem>
</>;
}
+52
View File
@@ -0,0 +1,52 @@
import * as React from 'react';
import { Box, Typography } from '@mui/joy';
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
import { conversationAutoTitle } from '~/modules/aifn/autotitle/autoTitle';
import type { DConversationId } from '~/common/state/store-chats';
import { capitalizeFirstLetter } from '~/common/util/textUtils';
import { CHAT_NOVEL_TITLE } from '../AppChat';
import { FadeInButton } from './ChatDrawerItem';
export function ChatTitle(props: {
conversationId: DConversationId | null,
conversationTitle: string,
}) {
// state
const [isEditingTitle, setIsEditingTitle] = React.useState<boolean>(false);
// derived state
const { conversationId, conversationTitle } = props;
const hasConversation = !!conversationId;
const handleTitleEditAuto = React.useCallback(async () => {
if (!conversationId) return;
setIsEditingTitle(true);
await conversationAutoTitle(conversationId, true);
setIsEditingTitle(false);
}, [conversationId]);
return (
<Box sx={{ display: 'flex', gap: { xs: 1, md: 3 }, alignItems: 'center' }}>
<Typography>
{capitalizeFirstLetter(conversationTitle?.trim() || CHAT_NOVEL_TITLE)}
</Typography>
{hasConversation && (
<FadeInButton size='sm' disabled={isEditingTitle} onClick={handleTitleEditAuto}>
<AutoFixHighIcon />
</FadeInButton>
)}
</Box>
);
}
+42 -26
View File
@@ -1,11 +1,13 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { Box, Grid, IconButton, Sheet, Stack, styled, Typography, useTheme } from '@mui/joy';
import { Box, Grid, IconButton, Sheet, styled, Typography } from '@mui/joy';
import { SxProps } from '@mui/joy/styles/types';
import CloseIcon from '@mui/icons-material/Close';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import { DEphemeral, useChatStore } from '~/common/state/store-chats';
import { ConversationManager } from '~/common/chats/ConversationHandler';
import { DConversationId } from '~/common/state/store-chats';
import { DEphemeral } from '~/common/chats/EphemeralsStore';
import { lineHeightChatTextMd } from '~/common/app.theme';
const StateLine = styled(Typography)(({ theme }) => ({
@@ -15,7 +17,7 @@ const StateLine = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSize.xs,
fontFamily: theme.fontFamily.code,
marginLeft: theme.spacing(1),
lineHeight: 2,
lineHeight: lineHeightChatTextMd,
}));
function isPrimitive(value: any): boolean {
@@ -52,11 +54,11 @@ function StateRenderer(props: { state: object }) {
const entries = Object.entries(props.state);
return (
<Stack>
<Typography level='body-sm' sx={{ mb: 1 }}>
Internal State
<Box>
<Typography fontSize='smaller' sx={{ mb: 1 }}>
## Internal State
</Typography>
<Sheet>
<Sheet sx={{ p: 1 }}>
{!entries && <Typography level='body-sm'>No state variables</Typography>}
{entries.map(([key, value]) =>
isPrimitive(value)
@@ -68,13 +70,17 @@ function StateRenderer(props: { state: object }) {
: <Typography key={'state-' + key} level='body-sm'>{key}: {value}</Typography>,
)}
</Sheet>
</Stack>
</Box>
);
}
function EphemeralItem({ conversationId, ephemeral }: { conversationId: string, ephemeral: DEphemeral }) {
const theme = useTheme();
const handleDelete = React.useCallback(() => {
ConversationManager.getHandler(conversationId).ephemeralsStore.delete(ephemeral.id);
}, [conversationId, ephemeral.id]);
return <Box
sx={{
p: { xs: 1, md: 2 },
@@ -84,8 +90,8 @@ function EphemeralItem({ conversationId, ephemeral }: { conversationId: string,
}}>
{/* Title */}
{ephemeral.title && <Typography>
{ephemeral.title} <b>Development Tools</b>
{ephemeral.title && <Typography level='title-sm' sx={{ mb: 1.5 }}>
{ephemeral.title} Development Tools
</Typography>}
{/* Vertical | split */}
@@ -93,7 +99,7 @@ function EphemeralItem({ conversationId, ephemeral }: { conversationId: string,
{/* Left pane (console) */}
<Grid xs={12} md={ephemeral.state ? 6 : 12}>
<Typography fontSize='smaller' sx={{ overflowWrap: 'anywhere', whiteSpace: 'break-spaces', lineHeight: 1.75 }}>
<Typography fontSize='smaller' sx={{ overflowWrap: 'anywhere', whiteSpace: 'break-spaces', lineHeight: lineHeightChatTextMd }}>
{ephemeral.text}
</Typography>
</Grid>
@@ -102,8 +108,8 @@ function EphemeralItem({ conversationId, ephemeral }: { conversationId: string,
{!!ephemeral.state && <Grid
xs={12} md={6}
sx={{
borderLeft: { md: `1px solid ${theme.palette.divider}` },
borderTop: { xs: `1px solid ${theme.palette.divider}`, md: 'none' },
borderLeft: { md: `1px dashed` },
borderTop: { xs: `1px dashed`, md: 'none' },
}}>
<StateRenderer state={ephemeral.state} />
</Grid>}
@@ -112,33 +118,43 @@ function EphemeralItem({ conversationId, ephemeral }: { conversationId: string,
{/* Close button (right of title) */}
<IconButton
size='sm'
onClick={() => useChatStore.getState().deleteEphemeral(conversationId, ephemeral.id)}
onClick={handleDelete}
sx={{
position: 'absolute', top: 8, right: 8,
opacity: { xs: 1, sm: 0.5 }, transition: 'opacity 0.3s',
}}>
<CloseIcon />
<CloseRoundedIcon />
</IconButton>
</Box>;
}
// const dashedBorderSVG = encodeURIComponent(`
// <svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'>
// <rect x='0' y='0' width='100%' height='100%' fill='none' stroke='currentColor' stroke-width='2' stroke-dasharray='16, 2' />
// </svg>
// `);
export function Ephemerals(props: { conversationId: string | null, sx?: SxProps }) {
export function Ephemerals(props: { ephemerals: DEphemeral[], conversationId: DConversationId | null, sx?: SxProps }) {
// global state
const theme = useTheme();
const ephemerals = useChatStore(state => {
const conversation = state.conversations.find(conversation => conversation.id === props.conversationId);
return conversation ? conversation.ephemerals : [];
}, shallow);
// const ephemerals = useChatStore(state => {
// const conversation = state.conversations.find(conversation => conversation.id === props.conversationId);
// return conversation ? conversation.ephemerals : [];
// }, shallow);
if (!ephemerals?.length) return null;
const ephemerals = props.ephemerals;
// if (!ephemerals?.length) return null;
return (
<Sheet
variant='soft' color='success' invertedColors
sx={{
border: `4px dashed ${theme.palette.divider}`,
borderTop: '1px solid',
borderTopColor: 'divider',
// backgroundImage: `url("data:image/svg+xml,${dashedBorderSVG.replace('currentColor', '%23A1E8A1')}")`,
// backgroundSize: '100% 100%',
// backgroundRepeat: 'no-repeat',
...(props.sx || {}),
}}>
@@ -1,160 +0,0 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { Box, ListDivider, ListItemDecorator, MenuItem, Typography } from '@mui/joy';
import AddIcon from '@mui/icons-material/Add';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import FileUploadIcon from '@mui/icons-material/FileUpload';
import { OpenAIIcon } from '~/common/components/icons/OpenAIIcon';
import { closeLayoutDrawer } from '~/common/layout/store-applayout';
import { useChatStore } from '~/common/state/store-chats';
import { useUIPreferencesStore } from '~/common/state/store-ui';
import { ConversationItem } from './ConversationItem';
type ListGrouping = 'off' | 'persona';
export function ChatDrawerItems(props: {
conversationId: string | null
onDeleteAllConversations: () => void,
onImportConversation: () => void,
}) {
// local state
const [grouping] = React.useState<ListGrouping>('off');
// external state
const { conversationIDs, topNewConversationId, maxChatMessages, setActiveConversationId, createConversationOrSwitch, deleteConversation } = useChatStore(state => ({
conversationIDs: state.conversations.map(conversation => conversation.id),
topNewConversationId: state.conversations.length ? state.conversations[0].messages.length === 0 ? state.conversations[0].id : null : null,
maxChatMessages: state.conversations.reduce((longest, conversation) => Math.max(longest, conversation.messages.length), 0),
setActiveConversationId: state.setActiveConversationId,
createConversationOrSwitch: state.createConversationOrSwitch,
deleteConversation: state.deleteConversation,
}), shallow);
const { experimentalLabs, showSymbols } = useUIPreferencesStore(state => ({
experimentalLabs: state.experimentalLabs,
showSymbols: state.zenMode !== 'cleaner',
}), shallow);
const totalConversations = conversationIDs.length;
const hasChats = totalConversations > 0;
const singleChat = totalConversations === 1;
const softMaxReached = totalConversations >= 50;
const handleNew = () => {
createConversationOrSwitch();
closeLayoutDrawer();
};
const handleConversationActivate = React.useCallback((conversationId: string, closeMenu: boolean) => {
setActiveConversationId(conversationId);
if (closeMenu)
closeLayoutDrawer();
}, [setActiveConversationId]);
const handleConversationDelete = React.useCallback((conversationId: string) => {
if (!singleChat && conversationId)
deleteConversation(conversationId);
}, [deleteConversation, singleChat]);
// grouping
let sortedIds = conversationIDs;
if (grouping === 'persona') {
const conversations = useChatStore.getState().conversations;
// group conversations by persona
const groupedConversations: { [personaId: string]: string[] } = {};
conversations.forEach(conversation => {
const persona = conversation.systemPurposeId;
if (persona) {
if (!groupedConversations[persona])
groupedConversations[persona] = [];
groupedConversations[persona].push(conversation.id);
}
});
// flatten grouped conversations
sortedIds = Object.values(groupedConversations).flat();
}
return <>
{/*<ListItem>*/}
{/* <Typography level='body-sm'>*/}
{/* Active chats*/}
{/* </Typography>*/}
{/*</ListItem>*/}
<MenuItem disabled={!!topNewConversationId && topNewConversationId === props.conversationId} onClick={handleNew}>
<ListItemDecorator><AddIcon /></ListItemDecorator>
<Box sx={{ flexGrow: 1, display: 'flex', justifyContent: 'space-between', gap: 1 }}>
New
{/*<KeyStroke combo='Ctrl + Alt + N' />*/}
</Box>
</MenuItem>
<ListDivider sx={{ mb: 0 }} />
<Box sx={{ flex: 1, overflowY: 'auto' }}>
{/*<ListItem sticky sx={{ justifyContent: 'space-between', boxShadow: 'sm' }}>*/}
{/* <Typography level='body-sm'>*/}
{/* Conversations*/}
{/* </Typography>*/}
{/* <ToggleButtonGroup variant='soft' size='sm' value={grouping} onChange={(_event, newValue) => newValue && setGrouping(newValue)}>*/}
{/* <IconButton value='off'>*/}
{/* <AccessTimeIcon />*/}
{/* </IconButton>*/}
{/* <IconButton value='persona'>*/}
{/* <PersonIcon />*/}
{/* </IconButton>*/}
{/* </ToggleButtonGroup>*/}
{/*</ListItem>*/}
{sortedIds.map(conversationId =>
<ConversationItem
key={'c-id-' + conversationId}
conversationId={conversationId}
isActive={conversationId === props.conversationId}
isSingle={singleChat}
showSymbols={showSymbols}
maxChatMessages={(experimentalLabs || softMaxReached) ? maxChatMessages : 0}
conversationActivate={handleConversationActivate}
conversationDelete={handleConversationDelete}
/>)}
</Box>
<ListDivider sx={{ mt: 0 }} />
<MenuItem onClick={props.onImportConversation}>
<ListItemDecorator>
<FileUploadIcon />
</ListItemDecorator>
Import chats
<OpenAIIcon sx={{ fontSize: 'xl', ml: 'auto' }} />
</MenuItem>
<MenuItem disabled={!hasChats} onClick={props.onDeleteAllConversations}>
<ListItemDecorator><DeleteOutlineIcon /></ListItemDecorator>
<Typography>
Delete {totalConversations >= 2 ? `all ${totalConversations} chats` : 'chat'}
</Typography>
</MenuItem>
{/*<ListItem>*/}
{/* <Typography level='body-sm'>*/}
{/* Scratchpad*/}
{/* </Typography>*/}
{/*</ListItem>*/}
{/*<MenuItem>*/}
{/* <ListItemDecorator />*/}
{/* <Typography sx={{ opacity: 0.5 }}>*/}
{/* Feature <Link href={`${Brand.URIs.OpenRepo}/issues/17`} target='_blank'>#17</Link>*/}
{/* </Typography>*/}
{/*</MenuItem>*/}
</>;
}
@@ -1,126 +0,0 @@
import * as React from 'react';
import { Box, ListDivider, ListItemDecorator, MenuItem, Switch } from '@mui/joy';
import CheckBoxOutlineBlankOutlinedIcon from '@mui/icons-material/CheckBoxOutlineBlankOutlined';
import CheckBoxOutlinedIcon from '@mui/icons-material/CheckBoxOutlined';
import ClearIcon from '@mui/icons-material/Clear';
import CompressIcon from '@mui/icons-material/Compress';
import FileDownloadIcon from '@mui/icons-material/FileDownload';
import ForkRightIcon from '@mui/icons-material/ForkRight';
import SettingsSuggestIcon from '@mui/icons-material/SettingsSuggest';
import { KeyStroke } from '~/common/components/KeyStroke';
import { closeLayoutMenu } from '~/common/layout/store-applayout';
import { useUICounter } from '~/common/state/store-ui';
import { useChatShowSystemMessages } from '../../store-app-chat';
export function ChatMenuItems(props: {
conversationId: string | null, isConversationEmpty: boolean, hasConversations: boolean,
isMessageSelectionMode: boolean, setIsMessageSelectionMode: (isMessageSelectionMode: boolean) => void,
onClearConversation: (conversationId: string) => void,
onDuplicateConversation: (conversationId: string) => void,
onExportConversation: (conversationId: string | null) => void,
onFlattenConversation: (conversationId: string) => void,
}) {
// external state
const { touch: shareTouch } = useUICounter('export-share');
const [showSystemMessages, setShowSystemMessages] = useChatShowSystemMessages();
// derived state
const disabled = !props.conversationId || props.isConversationEmpty;
const handleSystemMessagesToggle = () => setShowSystemMessages(!showSystemMessages);
const handleConversationExport = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
closeLayoutMenu();
props.onExportConversation(!disabled ? props.conversationId : null);
shareTouch();
};
const handleConversationDuplicate = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
closeLayoutMenu();
props.conversationId && props.onDuplicateConversation(props.conversationId);
};
const handleConversationFlatten = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
closeLayoutMenu();
props.conversationId && props.onFlattenConversation(props.conversationId);
};
const handleToggleMessageSelectionMode = (e: React.MouseEvent) => {
e.stopPropagation();
closeLayoutMenu();
props.setIsMessageSelectionMode(!props.isMessageSelectionMode);
};
const handleConversationClear = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
props.conversationId && props.onClearConversation(props.conversationId);
};
return <>
{/*<ListItem>*/}
{/* <Typography level='body-sm'>*/}
{/* Conversation*/}
{/* </Typography>*/}
{/*</ListItem>*/}
<MenuItem onClick={handleSystemMessagesToggle}>
<ListItemDecorator><SettingsSuggestIcon /></ListItemDecorator>
System message
<Switch checked={showSystemMessages} onChange={handleSystemMessagesToggle} sx={{ ml: 'auto' }} />
</MenuItem>
<ListDivider inset='startContent' />
<MenuItem disabled={disabled} onClick={handleConversationDuplicate}>
<ListItemDecorator>
{/*<Badge size='sm' color='success'>*/}
<ForkRightIcon color='success' />
{/*</Badge>*/}
</ListItemDecorator>
Duplicate
</MenuItem>
<MenuItem disabled={disabled} onClick={handleConversationFlatten}>
<ListItemDecorator>
{/*<Badge size='sm' color='success'>*/}
<CompressIcon color='success' />
{/*</Badge>*/}
</ListItemDecorator>
Flatten
</MenuItem>
<ListDivider inset='startContent' />
<MenuItem disabled={disabled} onClick={handleToggleMessageSelectionMode}>
<ListItemDecorator>{props.isMessageSelectionMode ? <CheckBoxOutlinedIcon /> : <CheckBoxOutlineBlankOutlinedIcon />}</ListItemDecorator>
<span style={props.isMessageSelectionMode ? { fontWeight: 800 } : {}}>
Cleanup ...
</span>
</MenuItem>
<MenuItem disabled={!props.hasConversations} onClick={handleConversationExport}>
<ListItemDecorator>
<FileDownloadIcon />
</ListItemDecorator>
Share / Export ...
</MenuItem>
<MenuItem disabled={disabled} onClick={handleConversationClear}>
<ListItemDecorator><ClearIcon /></ListItemDecorator>
<Box sx={{ flexGrow: 1, display: 'flex', justifyContent: 'space-between', gap: 1 }}>
Reset
{!disabled && <KeyStroke combo='Ctrl + Alt + X' />}
</Box>
</MenuItem>
</>;
}
@@ -1,175 +0,0 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { Avatar, Box, IconButton, ListItemDecorator, MenuItem, Typography } from '@mui/joy';
import { SxProps } from '@mui/joy/styles/types';
import CloseIcon from '@mui/icons-material/Close';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import { SystemPurposes } from '../../../../data';
import { InlineTextarea } from '~/common/components/InlineTextarea';
import { conversationTitle, useChatStore } from '~/common/state/store-chats';
import { useUIPreferencesStore } from '~/common/state/store-ui';
const DEBUG_CONVERSATION_IDs = false;
export function ConversationItem(props: {
conversationId: string,
isActive: boolean, isSingle: boolean, showSymbols: boolean, maxChatMessages: number,
conversationActivate: (conversationId: string, closeMenu: boolean) => void,
conversationDelete: (conversationId: string) => void,
}) {
// state
const [isEditingTitle, setIsEditingTitle] = React.useState(false);
const [deleteArmed, setDeleteArmed] = React.useState(false);
const doubleClickToEdit = useUIPreferencesStore(state => state.doubleClickToEdit);
// bind to conversation
const cState = useChatStore(state => {
const conversation = state.conversations.find(conversation => conversation.id === props.conversationId);
return conversation && {
isNew: conversation.messages.length === 0,
messageCount: conversation.messages.length,
assistantTyping: !!conversation.abortController,
systemPurposeId: conversation.systemPurposeId,
title: conversationTitle(conversation, 'new conversation'),
setUserTitle: state.setUserTitle,
};
}, shallow);
// auto-close the arming menu when clicking away
// NOTE: there currently is a bug (race condition) where the menu closes on a new item right after opening
// because the isActive prop is not yet updated
React.useEffect(() => {
if (deleteArmed && !props.isActive)
setDeleteArmed(false);
}, [deleteArmed, props.isActive]);
// sanity check: shouldn't happen, but just in case
if (!cState) return null;
const { isNew, messageCount, assistantTyping, setUserTitle, systemPurposeId, title } = cState;
const handleActivate = () => props.conversationActivate(props.conversationId, true);
const handleEditBegin = () => setIsEditingTitle(true);
const handleEdited = (text: string) => {
setIsEditingTitle(false);
setUserTitle(props.conversationId, text);
};
const handleDeleteBegin = (e: React.MouseEvent) => {
e.stopPropagation();
if (!props.isActive)
props.conversationActivate(props.conversationId, false);
else
setDeleteArmed(true);
};
const handleDeleteConfirm = (e: React.MouseEvent) => {
if (deleteArmed) {
setDeleteArmed(false);
e.stopPropagation();
props.conversationDelete(props.conversationId);
}
};
const handleDeleteCancel = () => setDeleteArmed(false);
const textSymbol = SystemPurposes[systemPurposeId]?.symbol || '❓';
const buttonSx: SxProps = { ml: 1, ...(props.isActive ? { color: 'white' } : {}) };
const progress = props.maxChatMessages ? 100 * messageCount / props.maxChatMessages : 0;
return (
<MenuItem
variant={props.isActive ? 'solid' : 'plain'} color='neutral'
selected={props.isActive}
onClick={handleActivate}
sx={{
// py: 0,
position: 'relative',
border: 'none', // note, there's a default border of 1px and invisible.. hmm
'&:hover > button': { opacity: 1 },
}}
>
{/* Optional prgoress bar */}
{progress > 0 && (
<Box sx={{
backgroundColor: 'neutral.softActiveBg',
position: 'absolute', left: 0, bottom: 0, width: progress + '%', height: 4,
}} />
)}
{/* Icon */}
{props.showSymbols && <ListItemDecorator>
{assistantTyping
? (
<Avatar
alt='typing' variant='plain'
src='https://i.giphy.com/media/jJxaUysjzO9ri/giphy.webp'
sx={{
width: 24,
height: 24,
borderRadius: 'var(--joy-radius-sm)',
}}
/>
) : (
<Typography sx={{ fontSize: '18px' }}>
{isNew ? '' : textSymbol}
</Typography>
)}
</ListItemDecorator>}
{/* Text */}
{!isEditingTitle ? (
<Box onDoubleClick={() => doubleClickToEdit ? handleEditBegin() : null} sx={{ flexGrow: 1 }}>
{DEBUG_CONVERSATION_IDs ? props.conversationId.slice(0, 10) : title}{assistantTyping && '...'}
</Box>
) : (
<InlineTextarea initialText={title} onEdit={handleEdited} sx={{ ml: -1.5, mr: -0.5, flexGrow: 1 }} />
)}
{/* // TODO: Commented code */}
{/* Edit */}
{/*<IconButton*/}
{/* variant='plain' color='neutral'*/}
{/* onClick={() => props.onEditTitle(props.conversationId)}*/}
{/* sx={{*/}
{/* opacity: 0, transition: 'opacity 0.3s', ml: 'auto',*/}
{/* }}>*/}
{/* <EditIcon />*/}
{/*</IconButton>*/}
{/* Delete Arming */}
{!props.isSingle && !deleteArmed && (
<IconButton
variant={props.isActive ? 'solid' : 'outlined'} color='neutral'
size='sm' sx={{ opacity: { xs: 1, sm: 0 }, transition: 'opacity 0.3s', ...buttonSx }}
onClick={handleDeleteBegin}>
<DeleteOutlineIcon />
</IconButton>
)}
{/* Delete / Cancel buttons */}
{!props.isSingle && deleteArmed && <>
<IconButton size='sm' variant='solid' color='danger' sx={buttonSx} onClick={handleDeleteConfirm}>
<DeleteOutlineIcon />
</IconButton>
<IconButton size='sm' variant='solid' color='neutral' sx={buttonSx} onClick={handleDeleteCancel}>
<CloseIcon />
</IconButton>
</>}
</MenuItem>
);
}
@@ -1,97 +0,0 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { Box, ListItemButton, ListItemDecorator } from '@mui/joy';
import BuildCircleIcon from '@mui/icons-material/BuildCircle';
import SettingsIcon from '@mui/icons-material/Settings';
import { DLLM, DLLMId, DModelSourceId, useModelsStore } from '~/modules/llms/store-llms';
import { AppBarDropdown, DropdownItems } from '~/common/layout/AppBarDropdown';
import { KeyStroke } from '~/common/components/KeyStroke';
import { openLayoutLLMOptions, openLayoutModelsSetup } from '~/common/layout/store-applayout';
function AppBarLLMDropdown(props: {
llms: DLLM[],
llmId: DLLMId | null,
setLlmId: (llmId: DLLMId | null) => void,
placeholder?: string,
}) {
// build model menu items, filtering-out hidden models, and add Source separators
const llmItems: DropdownItems = {};
let prevSourceId: DModelSourceId | null = null;
for (const llm of props.llms) {
if (!llm.hidden || llm.id === props.llmId) {
if (!prevSourceId || llm.sId !== prevSourceId) {
if (prevSourceId)
llmItems[`sep-${llm.id}`] = { type: 'separator', title: llm.sId };
prevSourceId = llm.sId;
}
llmItems[llm.id] = { title: llm.label };
}
}
const handleChatLLMChange = (_event: any, value: DLLMId | null) => value && props.setLlmId(value);
const handleOpenLLMOptions = () => props.llmId && openLayoutLLMOptions(props.llmId);
return (
<AppBarDropdown
items={llmItems}
value={props.llmId} onChange={handleChatLLMChange}
placeholder={props.placeholder || 'Models …'}
appendOption={<>
{props.llmId && (
<ListItemButton key='menu-opt' onClick={handleOpenLLMOptions}>
<ListItemDecorator><SettingsIcon color='success' /></ListItemDecorator>
Options
</ListItemButton>
)}
<ListItemButton key='menu-llms' onClick={openLayoutModelsSetup}>
<ListItemDecorator><BuildCircleIcon color='success' /></ListItemDecorator>
<Box sx={{ flexGrow: 1, display: 'flex', justifyContent: 'space-between', gap: 1 }}>
Models
<KeyStroke combo='Ctrl + Shift + M' />
</Box>
</ListItemButton>
</>}
/>
);
}
export function useChatLLMDropdown() {
// external state
const { llms, chatLLMId, setChatLLMId } = useModelsStore(state => ({
llms: state.llms,
chatLLMId: state.chatLLMId,
setChatLLMId: state.setChatLLMId,
}), shallow);
const chatLLMDropdown = React.useMemo(
() => <AppBarLLMDropdown llms={llms} llmId={chatLLMId} setLlmId={setChatLLMId} />,
[llms, chatLLMId, setChatLLMId],
);
return { chatLLMId, chatLLMDropdown };
}
/*export function useTempLLMDropdown(props: { initialLlmId: DLLMId | null }) {
// local state
const [llmId, setLlmId] = React.useState<DLLMId | null>(props.initialLlmId);
// external state
const llms = useModelsStore(state => state.llms, shallow);
const chatLLMDropdown = React.useMemo(
() => <AppBarLLMDropdown llms={llms} llmId={llmId} setLlmId={setLlmId} />,
[llms, llmId, setLlmId],
);
return { llmId, chatLLMDropdown };
}*/
@@ -1,81 +0,0 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { ListItemButton, ListItemDecorator } from '@mui/joy';
import CallIcon from '@mui/icons-material/Call';
import { APP_CALL_ENABLED } from '../../../call/AppCall';
import { SystemPurposeId, SystemPurposes } from '../../../../data';
import { AppBarDropdown } from '~/common/layout/AppBarDropdown';
import { launchAppCall } from '~/common/app.routes';
import { useChatStore } from '~/common/state/store-chats';
import { useUIPreferencesStore } from '~/common/state/store-ui';
function AppBarPersonaDropdown(props: {
systemPurposeId: SystemPurposeId | null,
setSystemPurposeId: (systemPurposeId: SystemPurposeId | null) => void,
onCall?: () => void,
}) {
// external state
const { zenMode } = useUIPreferencesStore(state => ({
zenMode: state.zenMode,
}), shallow);
const handleSystemPurposeChange = (_event: any, value: SystemPurposeId | null) => props.setSystemPurposeId(value);
// options
let appendOption: React.JSX.Element | undefined = undefined;
if (props.onCall) {
const enableCallOption = !!props.systemPurposeId;
appendOption = (
<ListItemButton color='primary' disabled={!enableCallOption} key='menu-call-persona' onClick={props.onCall} sx={{ minWidth: 160 }}>
<ListItemDecorator><CallIcon color={enableCallOption ? 'primary' : 'warning'} /></ListItemDecorator>
Call&nbsp; {!!props.systemPurposeId && SystemPurposes[props.systemPurposeId]?.symbol}
</ListItemButton>
);
}
return (
<AppBarDropdown
items={SystemPurposes} showSymbols={zenMode !== 'cleaner'}
value={props.systemPurposeId} onChange={handleSystemPurposeChange}
appendOption={appendOption}
/>
);
}
export function usePersonaIdDropdown(conversationId: string | null) {
// external state
const { systemPurposeId } = useChatStore(state => {
const conversation = state.conversations.find(conversation => conversation.id === conversationId);
return {
systemPurposeId: conversation?.systemPurposeId ?? null,
};
}, shallow);
const personaDropdown = React.useMemo(() => systemPurposeId
? <AppBarPersonaDropdown
systemPurposeId={systemPurposeId}
setSystemPurposeId={(systemPurposeId) => {
if (conversationId && systemPurposeId)
useChatStore.getState().setSystemPurposeId(conversationId, systemPurposeId);
}}
onCall={APP_CALL_ENABLED ? () => {
if (conversationId && systemPurposeId)
launchAppCall(conversationId, systemPurposeId);
} : undefined}
/> : null,
[conversationId, systemPurposeId],
);
return { personaDropdown };
}
+137
View File
@@ -0,0 +1,137 @@
import * as React from 'react';
import type { SxProps } from '@mui/joy/styles/types';
import { Alert, Box, Sheet, Typography } from '@mui/joy';
import { ConversationHandler } from '~/common/chats/ConversationHandler';
import { useBeam } from '~/common/chats/BeamStore';
import { useLLMSelect } from '~/common/components/forms/useLLMSelect';
export function Beam(props: {
conversationHandler: ConversationHandler | null,
isMobile: boolean,
sx?: SxProps
}) {
// state
const { config, candidates } = useBeam(props.conversationHandler);
// external state
const [allChatLlm, allChatLlmComponent] = useLLMSelect(true, 'Beam LLM');
if (!config)
return null;
const lastMessage = config.history.slice(-1)[0] ?? null;
return (
<Box sx={{ ...props.sx, px: 2, display: 'flex', flexDirection: 'column', gap: 2 }}>
{/* Issues */}
{!!config.configError && (
<Alert>
{config.configError}
</Alert>
)}
{/* Models, [x] all same, */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'start', gap: 2 }}>
<Box sx={{ minWidth: 200 }}>
{allChatLlmComponent}
</Box>
{!!lastMessage && (
<Box sx={{
backgroundColor: 'background.surface',
boxShadow: 'xs',
borderRadius: 'lg',
borderTopRightRadius: 0,
borderTopLeftRadius: 0,
py: 1,
px: 1,
mb: 'auto',
flex: 1,
}}>
{lastMessage.text}
</Box>
// <ChatMessageMemo
// message={lastMessage}
// fitScreen={props.isMobile}
// sx={{
// borderRadius: 'lg',
// borderBottomRightRadius: lastMessage.role === 'assistant' ? undefined : 0,
// borderBottomLeftRadius: lastMessage.role === 'user' ? undefined : 0,
// boxShadow: 'xs',
// my: 2,
// px: 0,
// py: 1,
// alignSelf: 'self-end',
// flex: 1,
// maxHeight: '5rem',
// overflow: 'hidden',
// }}
// />
)}
</Box>
{/* Grid */}
<Box sx={{
// my: 'auto',
// display: 'flex', flexDirection: 'column', alignItems: 'center',
border: '1px solid purple',
minHeight: '300px',
// layout
display: 'grid',
gridTemplateColumns: props.isMobile ? 'repeat(auto-fit, minmax(320px, 1fr))' : 'repeat(auto-fit, minmax(400px, 1fr))',
gap: { xs: 2, md: 2 },
}}>
<Sheet sx={{ minHeight: '50%' }}>
b
</Sheet>
<Sheet>
a
</Sheet>
<Sheet>
a
</Sheet>
<Sheet>
a
</Sheet>
</Box>
{/* Auto-Gatherer: All-in-one, Best-Of */}
<Box>
Gatherer
</Box>
<Box sx={{ flex: 1 }}>
<Typography level='body-sm' sx={{ whiteSpace: 'break-spaces' }}>
{/*{JSON.stringify(config, null, 2)}*/}
</Typography>
</Box>
<Box sx={{
height: '100%',
borderRadius: 'lg',
borderBottomLeftRadius: 0,
backgroundColor: 'background.surface',
boxShadow: 'lg',
m: 2,
p: '0.25rem 1rem',
}}>
</Box>
<Box>
a
</Box>
</Box>
);
}
@@ -1,34 +0,0 @@
import * as React from 'react';
import { Button, IconButton } from '@mui/joy';
import AddAPhotoIcon from '@mui/icons-material/AddAPhoto';
import { CameraCaptureModal } from './CameraCaptureModal';
const CAMERA_ENABLE_ON_DESKTOP = false;
export function ButtonCameraCapture(props: { isMobile: boolean, onOCR: (ocrText: string) => void }) {
// state
const [open, setOpen] = React.useState(false);
return <>
{/* The Button */}
{props.isMobile ? (
<IconButton variant='plain' color='neutral' onClick={() => setOpen(true)}>
<AddAPhotoIcon />
</IconButton>
) : CAMERA_ENABLE_ON_DESKTOP ? (
<Button
fullWidth variant='plain' color='neutral' onClick={() => setOpen(true)} startDecorator={<AddAPhotoIcon />}
sx={{ justifyContent: 'flex-start' }}>
OCR
</Button>
) : undefined}
{/* The actual capture dialog, which will stream the video */}
{open && <CameraCaptureModal onCloseModal={() => setOpen(false)} onOCR={props.onOCR} />}
</>;
}
@@ -1,31 +0,0 @@
import * as React from 'react';
import { Box, Button, IconButton, Tooltip } from '@mui/joy';
import ContentPasteGoIcon from '@mui/icons-material/ContentPasteGo';
import { KeyStroke } from '~/common/components/KeyStroke';
const pasteClipboardLegend =
<Box sx={{ p: 1, lineHeight: 2 }}>
<b>Paste as 📚 Markdown attachment</b><br />
Also converts Code and Tables<br />
<KeyStroke combo='Ctrl + Shift + V' />
</Box>;
export function ButtonClipboardPaste(props: { isMobile: boolean, isDeveloperMode: boolean, onPaste: () => void }) {
return props.isMobile ? (
<IconButton onClick={props.onPaste}>
<ContentPasteGoIcon />
</IconButton>
) : (
<Tooltip
variant='solid' placement='top-start'
title={pasteClipboardLegend}>
<Button fullWidth variant='plain' color='neutral' startDecorator={<ContentPasteGoIcon />} onClick={props.onPaste}
sx={{ justifyContent: 'flex-start' }}>
{props.isDeveloperMode ? 'Paste code' : 'Paste'}
</Button>
</Tooltip>
);
}
@@ -1,70 +0,0 @@
import { Box, Button, IconButton, Stack, Tooltip } from '@mui/joy';
import * as React from 'react';
import AttachFileOutlinedIcon from '@mui/icons-material/AttachFileOutlined';
const attachFileLegend =
<Stack sx={{ p: 1, gap: 1 }}>
<Box sx={{ mb: 1 }}>
<b>Attach a file</b>
</Box>
<table>
<tbody>
<tr>
<td><b>Text</b></td>
<td align='center' style={{ opacity: 0.5 }}></td>
<td>📝 As-is</td>
</tr>
<tr>
<td><b>Code</b></td>
<td align='center' style={{ opacity: 0.5 }}></td>
<td>📚 Markdown</td>
</tr>
<tr>
<td><b>PDF</b></td>
<td width={36} align='center' style={{ opacity: 0.5 }}></td>
<td>📝 Text (summarized)</td>
</tr>
</tbody>
</table>
<Box sx={{ mt: 1, fontSize: '14px' }}>
Drag & drop in chat for faster loads
</Box>
</Stack>;
export function ButtonFileAttach(props: { isMobile: boolean, onAttachFiles: (files: FileList) => Promise<void> }) {
// state
const attachmentFileInputRef = React.useRef<HTMLInputElement>(null);
const handleShowFilePicker = () => attachmentFileInputRef.current?.click();
const handleLoadAttachment = (event: React.ChangeEvent<HTMLInputElement>) => {
// NOTE: resetting the target value allows for the selector dialog to pop-up again
const files = event.target?.files;
if (files && files.length >= 1)
props.onAttachFiles(files).finally(() => event.target.value = '');
else
event.target.value = '';
};
return <>
{/* Mobile icon or Desktop button */}
{props.isMobile ? (
<IconButton onClick={handleShowFilePicker}>
<AttachFileOutlinedIcon />
</IconButton>
) : (
<Tooltip variant='solid' placement='top-start' title={attachFileLegend}>
<Button fullWidth variant='plain' color='neutral' onClick={handleShowFilePicker} startDecorator={<AttachFileOutlinedIcon />}
sx={{ justifyContent: 'flex-start' }}>
Attach
</Button>
</Tooltip>
)}
<input type='file' multiple hidden ref={attachmentFileInputRef} onChange={handleLoadAttachment} />
</>;
}
@@ -1,43 +1,27 @@
import * as React from 'react';
import { Box, Button, CircularProgress, IconButton, LinearProgress, Modal, ModalClose, Option, Select, Sheet, Typography } from '@mui/joy';
import { Box, Button, IconButton, Modal, ModalClose, Option, Select, Sheet, Typography } from '@mui/joy';
import CameraAltIcon from '@mui/icons-material/CameraAlt';
import DownloadIcon from '@mui/icons-material/Download';
import InfoIcon from '@mui/icons-material/Info';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import { InlineError } from '~/common/components/InlineError';
import { downloadVideoFrameAsPNG, renderVideoFrameAsPNGFile } from '~/common/util/videoUtils';
import { useCameraCapture } from '~/common/components/useCameraCapture';
function renderVideoFrameToCanvas(videoElement: HTMLVideoElement): HTMLCanvasElement {
// paint the video on a canvas, to save it
const canvas = document.createElement('canvas');
canvas.width = videoElement.videoWidth || 640;
canvas.height = videoElement.videoHeight || 480;
const ctx = canvas.getContext('2d');
ctx?.drawImage(videoElement, 0, 0);
return canvas;
}
export function CameraCaptureModal(props: {
onCloseModal: () => void,
onAttachImage: (file: File) => void
// onOCR: (ocrText: string) => void }
}) {
function downloadVideoFrameAsPNG(videoElement: HTMLVideoElement) {
// video to canvas to png
const renderedFrame = renderVideoFrameToCanvas(videoElement);
const imageDataURL = renderedFrame.toDataURL('image/png');
// auto-download
const link = document.createElement('a');
link.download = 'image.png';
link.href = imageDataURL;
link.click();
}
export function CameraCaptureModal(props: { onCloseModal: () => void, onOCR: (ocrText: string) => void }) {
// state
const [ocrProgress, setOCRProgress] = React.useState<number | null>(null);
const [showInfo, setShowInfo] = React.useState(false);
// const [ocrProgress/*, setOCRProgress*/] = React.useState<number | null>(null);
// camera operations
// external state
const {
videoRef,
cameras, cameraIdx, setCameraIdx,
@@ -46,12 +30,16 @@ export function CameraCaptureModal(props: { onCloseModal: () => void, onOCR: (oc
} = useCameraCapture();
const stopAndClose = () => {
resetVideo();
props.onCloseModal();
};
// derived state
const { onCloseModal, onAttachImage } = props;
const handleVideoOCRClicked = async () => {
const stopAndClose = React.useCallback(() => {
resetVideo();
onCloseModal();
}, [onCloseModal, resetVideo]);
/*const handleVideoOCRClicked = async () => {
if (!videoRef.current) return;
const renderedFrame = renderVideoFrameToCanvas(videoRef.current);
@@ -68,12 +56,23 @@ export function CameraCaptureModal(props: { onCloseModal: () => void, onOCR: (oc
setOCRProgress(null);
stopAndClose();
props.onOCR(result.data.text);
};
};*/
const handleVideoDownloadClicked = () => {
const handleVideoSnapClicked = React.useCallback(async () => {
if (!videoRef.current) return;
downloadVideoFrameAsPNG(videoRef.current);
};
try {
const file = await renderVideoFrameAsPNGFile(videoRef.current, 'camera');
onAttachImage(file);
stopAndClose();
} catch (error) {
console.error('Error capturing video frame:', error);
}
}, [onAttachImage, stopAndClose, videoRef]);
const handleVideoDownloadClicked = React.useCallback(() => {
if (!videoRef.current) return;
downloadVideoFrameAsPNG(videoRef.current, 'camera');
}, [videoRef]);
return (
@@ -111,7 +110,7 @@ export function CameraCaptureModal(props: { onCloseModal: () => void, onOCR: (oc
ref={videoRef} autoPlay playsInline
style={{
display: 'block', width: '100%', maxHeight: 'calc(100vh - 200px)',
background: '#8888', opacity: ocrProgress !== null ? 0.5 : 1,
background: '#8888', //opacity: ocrProgress !== null ? 0.5 : 1,
}}
/>
@@ -124,7 +123,7 @@ export function CameraCaptureModal(props: { onCloseModal: () => void, onOCR: (oc
{info}
</Typography>}
{ocrProgress !== null && <CircularProgress sx={{ position: 'absolute', top: 'calc(50% - 34px / 2)', left: 'calc(50% - 34px / 2)', zIndex: 2 }} />}
{/*{ocrProgress !== null && <CircularProgress sx={{ position: 'absolute', top: 'calc(50% - 34px / 2)', left: 'calc(50% - 34px / 2)', zIndex: 2 }} />}*/}
</Box>
{/* Bottom controls (zoom, ocr, download) & progress */}
@@ -134,16 +133,30 @@ export function CameraCaptureModal(props: { onCloseModal: () => void, onOCR: (oc
{zoomControl}
{ocrProgress !== null && <LinearProgress color='primary' determinate value={100 * ocrProgress} sx={{ px: 2 }} />}
{/*{ocrProgress !== null && <LinearProgress color='primary' determinate value={100 * ocrProgress} sx={{ px: 2 }} />}*/}
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'space-between' }}>
<IconButton disabled={!info} variant='soft' color='neutral' size='lg' onClick={() => setShowInfo(info => !info)} sx={{ zIndex: 30 }}>
{/* Info */}
<IconButton size='lg' disabled={!info} variant='soft' onClick={() => setShowInfo(info => !info)} sx={{ zIndex: 30 }}>
<InfoIcon />
</IconButton>
<Button disabled={ocrProgress !== null} fullWidth variant='solid' size='lg' onClick={handleVideoOCRClicked} sx={{ flex: 1, maxWidth: 260 }}>
Extract Text
{/*<Button disabled={ocrProgress !== null} fullWidth variant='solid' size='lg' onClick={handleVideoOCRClicked} sx={{ flex: 1, maxWidth: 260 }}>*/}
{/* Extract Text*/}
{/*</Button>*/}
{/* Capture */}
<Button
fullWidth
variant='solid' color='neutral'
onClick={handleVideoSnapClicked}
endDecorator={<CameraAltIcon />}
sx={{ flex: 1, maxWidth: 200, py: 2, borderRadius: '3rem' }}
>
Capture
</Button>
<IconButton variant='soft' color='neutral' size='lg' onClick={handleVideoDownloadClicked}>
{/* Download */}
<IconButton size='lg' variant='soft' onClick={handleVideoDownloadClicked}>
<DownloadIcon />
</IconButton>
</Box>
@@ -6,7 +6,41 @@ import { CloseableMenu } from '~/common/components/CloseableMenu';
import { KeyStroke } from '~/common/components/KeyStroke';
import { useUIPreferencesStore } from '~/common/state/store-ui';
import { ChatModeId, ChatModeItems } from './store-composer';
import { ChatModeId } from '../../AppChat';
import { useUXLabsStore } from '~/common/state/store-ux-labs';
interface ChatModeDescription {
label: string;
description: string | React.JSX.Element;
shortcut?: string;
requiresTTI?: boolean;
}
const ChatModeItems: { [key in ChatModeId]: ChatModeDescription } = {
'generate-text': {
label: 'Chat',
description: 'Persona replies',
},
'append-user': {
label: 'Write',
description: 'Appends a message',
shortcut: 'Alt + Enter',
},
'generate-image': {
label: 'Draw',
description: 'AI Image Generation',
requiresTTI: true,
},
'generate-text-beam': {
label: 'Best-Of', // Best of, Auto-Prime, Top Pick, Select Best
description: 'Smarter: best of multiple replies',
},
'generate-react': {
label: 'Reason + Act', // · α
description: 'Answers questions in multiple steps',
},
};
function fixNewLineShortcut(shortcut: string, enterIsNewLine: boolean) {
@@ -15,38 +49,46 @@ function fixNewLineShortcut(shortcut: string, enterIsNewLine: boolean) {
return shortcut;
}
export const ChatModeMenu = (props: { anchorEl: HTMLAnchorElement | null, onClose: () => void, experimental: boolean, chatModeId: ChatModeId, onSetChatModeId: (chatMode: ChatModeId) => void }) => {
export function ChatModeMenu(props: {
anchorEl: HTMLAnchorElement | null, onClose: () => void,
chatModeId: ChatModeId, onSetChatModeId: (chatMode: ChatModeId) => void
capabilityHasTTI: boolean,
}) {
// external state
const labsChatBeam = useUXLabsStore(state => state.labsChatBeam);
const enterIsNewline = useUIPreferencesStore(state => state.enterIsNewline);
return <CloseableMenu
placement='top-end' sx={{ minWidth: 320 }}
open anchorEl={props.anchorEl} onClose={props.onClose}
>
return (
<CloseableMenu
placement='top-end'
open anchorEl={props.anchorEl} onClose={props.onClose}
sx={{ minWidth: 320 }}
>
{/*<MenuItem color='neutral' selected>*/}
{/* Conversation Mode*/}
{/*</MenuItem>*/}
{/**/}
{/*<ListDivider />*/}
{/*<MenuItem color='neutral' selected>*/}
{/* Conversation Mode*/}
{/*</MenuItem>*/}
{/**/}
{/*<ListDivider />*/}
{/* ChatMode items */}
{Object.entries(ChatModeItems)
.filter(([, { experimental }]) => props.experimental || !experimental)
.map(([key, data]) =>
<MenuItem key={'chat-mode-' + key} onClick={() => props.onSetChatModeId(key as ChatModeId)}>
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 2 }}>
<Radio checked={key === props.chatModeId} />
<Box sx={{ flexGrow: 1 }}>
<Typography>{data.label}</Typography>
<Typography level='body-xs'>{data.description}</Typography>
{/* ChatMode items */}
{Object.entries(ChatModeItems)
.filter(([key, data]) => key !== 'generate-text-beam' || labsChatBeam)
.map(([key, data]) =>
<MenuItem key={'chat-mode-' + key} onClick={() => props.onSetChatModeId(key as ChatModeId)}>
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 2 }}>
<Radio checked={key === props.chatModeId} />
<Box sx={{ flexGrow: 1 }}>
<Typography>{data.label}</Typography>
<Typography level='body-xs'>{data.description}{(data.requiresTTI && !props.capabilityHasTTI) ? 'Unconfigured' : ''}</Typography>
</Box>
{(key === props.chatModeId || !!data.shortcut) && (
<KeyStroke combo={fixNewLineShortcut((key === props.chatModeId) ? 'ENTER' : data.shortcut ? data.shortcut : 'ENTER', enterIsNewline)} />
)}
</Box>
{(key === props.chatModeId || !!data.shortcut) && (
<KeyStroke combo={fixNewLineShortcut((key === props.chatModeId) ? 'ENTER' : data.shortcut ? data.shortcut : "ENTER", enterIsNewline)} />
)}
</Box>
</MenuItem>)}
</MenuItem>)}
</CloseableMenu>;
};
</CloseableMenu>
);
}
File diff suppressed because it is too large Load Diff
@@ -1,11 +1,18 @@
import * as React from 'react';
import { Badge, ColorPaletteProp, Tooltip } from '@mui/joy';
import { SxProps } from '@mui/joy/styles/types';
import { Badge, Box, ColorPaletteProp, Tooltip } from '@mui/joy';
export function tokensPrettyMath(tokenLimit: number | 0, directTokens: number, indirectTokens?: number) {
const usedTokens = directTokens + (indirectTokens || 0);
function alignRight(value: number, columnSize: number = 7) {
const str = value.toLocaleString();
return str.padStart(columnSize);
}
export function tokensPrettyMath(tokenLimit: number | 0, directTokens: number, historyTokens?: number, responseMaxTokens?: number): {
color: ColorPaletteProp, message: string, remainingTokens: number
} {
const usedTokens = directTokens + (historyTokens || 0) + (responseMaxTokens || 0);
const remainingTokens = tokenLimit - usedTokens;
const gteLimit = (remainingTokens <= 0 && tokenLimit > 0);
@@ -17,23 +24,24 @@ export function tokensPrettyMath(tokenLimit: number | 0, directTokens: number, i
message += `Requested: ${usedTokens.toLocaleString()} tokens`;
}
// has full information (d + i < l)
else if (indirectTokens) {
else if (historyTokens || responseMaxTokens) {
message +=
`${Math.abs(remainingTokens).toLocaleString()} ${remainingTokens > 0 ? 'available' : 'excess'} tokens\n\n` +
` = Model max tokens: ${tokenLimit.toLocaleString()}\n` +
` - Chat Message: ${directTokens.toLocaleString()}` +
(indirectTokens ? `\n- History + Response: ${indirectTokens?.toLocaleString()}` : '');
`${Math.abs(remainingTokens).toLocaleString()} ${remainingTokens >= 0 ? 'available' : 'excess'} message tokens\n\n` +
` = Model max tokens: ${alignRight(tokenLimit)}\n` +
` - This message: ${alignRight(directTokens)}\n` +
` - History: ${alignRight(historyTokens || 0)}\n` +
` - Max response: ${alignRight(responseMaxTokens || 0)}`;
}
// Cleaner mode: d + ? < R (total is the remaining in this case)
else {
message +=
`${(tokenLimit + usedTokens).toLocaleString()} available tokens after deleting this\n\n` +
` = Currently free: ${tokenLimit.toLocaleString()}\n` +
` + This message: ${usedTokens.toLocaleString()}`;
` = Currently free: ${alignRight(tokenLimit)}\n` +
` + This message: ${alignRight(usedTokens)}`;
}
const color: ColorPaletteProp =
(tokenLimit && remainingTokens < 1)
(tokenLimit && remainingTokens < 0)
? 'danger'
: remainingTokens < tokenLimit / 4
? 'warning'
@@ -43,35 +51,61 @@ export function tokensPrettyMath(tokenLimit: number | 0, directTokens: number, i
}
export const TokenTooltip = (props: { message: string | null, color: ColorPaletteProp, placement?: 'top' | 'top-end', children: React.JSX.Element }) =>
<Tooltip
placement={props.placement}
variant={props.color !== 'primary' ? 'solid' : 'soft'} color={props.color}
title={props.message
? <Box sx={{ p: 2, whiteSpace: 'pre' }}>
{props.message}
</Box>
: null
}
sx={{
fontFamily: 'code',
boxShadow: 'xl',
}}
>
{props.children}
</Tooltip>;
/**
* Simple little component to show the token count (and a tooltip on hover)
*/
export function TokenBadge({ directTokens, indirectTokens, tokenLimit, showExcess, absoluteBottomRight, inline, sx }: { directTokens: number, indirectTokens?: number, tokenLimit: number, showExcess?: boolean, absoluteBottomRight?: boolean, inline?: boolean, sx?: SxProps }) {
export const TokenBadgeMemo = React.memo(TokenBadge);
const fontSx: SxProps = { fontFamily: 'code', ...(sx || {}) };
const outerSx: SxProps = absoluteBottomRight ? { position: 'absolute', bottom: 8, right: 8 } : {};
const innerSx: SxProps = (absoluteBottomRight || inline) ? { position: 'static', transform: 'none', ...fontSx } : fontSx;
function TokenBadge(props: {
direct: number, history?: number, responseMax?: number, limit: number,
showExcess?: boolean, absoluteBottomRight?: boolean, inline?: boolean,
}) {
const { message, color, remainingTokens } = tokensPrettyMath(tokenLimit, directTokens, indirectTokens);
const { message, color, remainingTokens } = tokensPrettyMath(props.limit, props.direct, props.history, props.responseMax);
// show the direct tokens, unless we exceed the limit and 'showExcess' is enabled
const value = (showExcess && (tokenLimit && remainingTokens <= 0))
const value = (props.showExcess && (props.limit && remainingTokens <= 0))
? Math.abs(remainingTokens)
: directTokens;
: props.direct;
return (
<Badge
variant='solid' color={color} max={100000}
invisible={!directTokens && remainingTokens >= 0}
invisible={!props.direct && remainingTokens >= 0}
badgeContent={
<Tooltip title={<span style={{ whiteSpace: 'pre' }}>{message}</span>} color={color} sx={fontSx}>
<TokenTooltip color={color} message={message}>
<span>{value.toLocaleString()}</span>
</Tooltip>
</TokenTooltip>
}
sx={outerSx}
sx={{
...((props.absoluteBottomRight) && { position: 'absolute', bottom: 8, right: 8 }),
cursor: 'help',
}}
slotProps={{
badge: {
sx: innerSx,
sx: {
fontFamily: 'code',
...((props.absoluteBottomRight || props.inline) && { position: 'static', transform: 'none' }),
},
},
}}
/>

Some files were not shown because too many files have changed in this diff Show More