├── .claude └── settings.local.json ├── .dockerignore ├── .eslintrc.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── maintainers-release.md │ └── roadmap-request.md └── workflows │ └── docker-image.yml ├── .gitignore ├── .npmrc ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── app └── api │ ├── cloud │ └── [trpc] │ │ └── route.ts │ └── edge │ └── [trpc] │ └── route.ts ├── docker-compose.yaml ├── docs ├── 2024-AI-APIs-Comparison.md ├── README.md ├── changelog.md ├── config-azure-openai.md ├── config-feature-browse.md ├── config-local-lmstudio.md ├── config-local-localai.md ├── config-local-ollama.md ├── config-openrouter.md ├── customizations.md ├── deploy-analytics.md ├── deploy-authentication.md ├── deploy-cloudflare.md ├── deploy-database.md ├── deploy-docker.md ├── deploy-k8s.md ├── deploy-reverse-proxy.md ├── docker │ └── docker-compose-browserless.yaml ├── draft-big-agi.md ├── environment-variables.md ├── help-advanced-tricks.md ├── help-data-ownership.md ├── help-faq.md ├── help-feature-livefile.md ├── help-feature-microphone.md ├── installation.md ├── k8s │ ├── big-agi-deployment.yaml │ └── env-secret.yaml ├── pixels │ ├── big-AGI-compo-20240201_small.png │ ├── big-AGI-compo1.png │ ├── big-AGI-compo2.png │ ├── big-AGI-compo2b.png │ ├── config-azure-openai-create.png │ ├── config-azure-openai-deploy.png │ ├── config-deploy-cloudflare-compat2.png │ ├── config-localai-1-models.png │ ├── config-localai-2-gallery.png │ ├── config-ollama-0-example.png │ ├── config-ollama-1-models.png │ ├── config-ollama-2-admin-pull.png │ ├── config-ollama-3-chat.png │ ├── config-ollama-network.png │ ├── config-oobabooga-0.png │ ├── data_ownership_deployed.png │ ├── data_ownership_hosted.png │ ├── data_ownership_local_storage.png │ ├── feature-openrouter-add.png │ ├── feature-openrouter-configure.png │ ├── feature_drop_target.png │ ├── feature_imagine_command.png │ ├── feature_language.png │ ├── feature_paste_gg.png │ ├── feature_purpose_two.png │ ├── feature_pwa.png │ ├── feature_react_google.png │ ├── feature_react_turn_on.png │ ├── feature_speak.png │ ├── feature_svg_drawing.png │ ├── feature_token_counter.png │ ├── feature_voice_1.png │ ├── gif_typing_040123.gif │ ├── gif_typing_orig.gif │ ├── screenshot_purpose_growth_1.png │ ├── screenshot_pwa_1.png │ ├── screenshot_pwa_2.png │ ├── zold_feature_system.png │ ├── zold_screenshot_mobile_clean.png │ └── zold_screenshot_web.png └── use-chat-react.md ├── middleware_BASIC_AUTH.ts ├── next.config.ts ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── call.tsx ├── dev │ └── beam.tsx ├── diff.tsx ├── draw.tsx ├── index.tsx ├── info │ └── debug.tsx ├── link │ ├── callback_openrouter.tsx │ ├── chat │ │ └── [chatLinkId].tsx │ └── share_target.tsx ├── news.tsx ├── personas.tsx ├── tokens.tsx └── workspace.tsx ├── public ├── apple-touch-icon.png ├── favicon.ico ├── icons │ ├── card-dark-1200.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── icon-1024x1024.png │ ├── icon-192x192.png │ ├── icon-512x512.png │ ├── icon-call-96x96.png │ └── icon-voicechat-96x96.png ├── images │ ├── covers │ │ ├── release-cover-v1.12.0.png │ │ ├── release-cover-v1.13.0.png │ │ ├── release-cover-v1.14.0.png │ │ ├── release-cover-v1.15.0.png │ │ └── release-cover-v1.16.0.png │ ├── explainers │ │ ├── explainer-beam-gather-1600px-alpha.png │ │ └── explainer-beam-scatter-1200px-alpha.png │ └── personas │ │ └── dev_preview_icon_120x120.webp ├── manifest.json ├── sounds │ ├── chat-begin.mp3 │ ├── chat-end.mp3 │ ├── chat-ringtone.mp3 │ ├── mic-off-mid.mp3 │ └── mic-off.mp3 └── workers │ └── pdf.worker.min.mjs ├── src ├── apps │ ├── AppPlaceholder.tsx │ ├── AppSmallContainer.tsx │ ├── beam │ │ └── AppBeam.tsx │ ├── call │ │ ├── AppCall.tsx │ │ ├── CallWizard.tsx │ │ ├── Contacts.tsx │ │ ├── Telephone.tsx │ │ ├── components │ │ │ ├── CallAvatar.tsx │ │ │ ├── CallButton.tsx │ │ │ ├── CallMessage.tsx │ │ │ └── CallStatus.tsx │ │ └── state │ │ │ ├── store-app-call.ts │ │ │ └── useMockPersonas.tsx │ ├── chat │ │ ├── AppChat.tsx │ │ ├── commands │ │ │ ├── CommandsAlter.tsx │ │ │ ├── CommandsDraw.tsx │ │ │ ├── CommandsHelp.tsx │ │ │ ├── CommandsReact.tsx │ │ │ ├── ICommandsProvider.ts │ │ │ ├── commands.dmessage.ts │ │ │ └── commands.registry.ts │ │ ├── components │ │ │ ├── ChatBeamWrapper.tsx │ │ │ ├── ChatMessageList.tsx │ │ │ ├── Ephemerals.tsx │ │ │ ├── PaneTitleOverlay.tsx │ │ │ ├── StatusBar.tsx │ │ │ ├── composer │ │ │ │ ├── CameraCaptureModal.tsx │ │ │ │ ├── Composer.tsx │ │ │ │ ├── WebInputModal.tsx │ │ │ │ ├── actile │ │ │ │ │ ├── ActilePopup.tsx │ │ │ │ │ ├── ActileProvider.tsx │ │ │ │ │ ├── providerAttachmentLabels.tsx │ │ │ │ │ ├── providerCommands.tsx │ │ │ │ │ ├── providerStarredMessage.tsx │ │ │ │ │ └── useActileManager.tsx │ │ │ │ ├── buttons │ │ │ │ │ ├── ButtonAttachCamera.tsx │ │ │ │ │ ├── ButtonAttachClipboard.tsx │ │ │ │ │ ├── ButtonAttachNewDoc.tsx │ │ │ │ │ ├── ButtonAttachScreenCapture.tsx │ │ │ │ │ ├── ButtonAttachWeb.tsx │ │ │ │ │ ├── ButtonBeam.tsx │ │ │ │ │ ├── ButtonCall.tsx │ │ │ │ │ ├── ButtonGroupDrawRepeat.tsx │ │ │ │ │ ├── ButtonMic.tsx │ │ │ │ │ ├── ButtonMicContinuation.tsx │ │ │ │ │ ├── ButtonMultiChat.tsx │ │ │ │ │ └── ButtonOptionsDraw.tsx │ │ │ │ ├── llmattachments │ │ │ │ │ ├── LLMAttachmentButton.tsx │ │ │ │ │ ├── LLMAttachmentMenu.tsx │ │ │ │ │ ├── LLMAttachmentsList.tsx │ │ │ │ │ ├── LLMAttachmentsPromptsButton.tsx │ │ │ │ │ └── useLLMAttachmentDrafts.ts │ │ │ │ ├── textarea │ │ │ │ │ ├── AttachmentsPromptsButton.tsx │ │ │ │ │ ├── ComposerTextAreaActions.tsx │ │ │ │ │ └── ComposerTextAreaDrawActions.tsx │ │ │ │ ├── tokens │ │ │ │ │ ├── TokenBadge.tsx │ │ │ │ │ ├── TokenProgressbar.tsx │ │ │ │ │ ├── TokenTooltip.tsx │ │ │ │ │ └── useTextTokenCounter.tsx │ │ │ │ └── useComposerDragDrop.tsx │ │ │ ├── layout-bar │ │ │ │ ├── ChatBarAltBeam.tsx │ │ │ │ ├── ChatBarAltTitle.tsx │ │ │ │ ├── ChatBarDropdowns.tsx │ │ │ │ ├── useFolderDropdown.tsx │ │ │ │ ├── useLLMDropdown.tsx │ │ │ │ └── usePersonaDropdown.tsx │ │ │ ├── layout-drawer │ │ │ │ ├── ChatDrawer.tsx │ │ │ │ ├── ChatDrawerItem.tsx │ │ │ │ ├── folders │ │ │ │ │ ├── AddFolderButton.tsx │ │ │ │ │ ├── ChatFolderList.tsx │ │ │ │ │ └── FolderListItem.tsx │ │ │ │ └── useChatDrawerRenderItems.tsx │ │ │ ├── layout-pane │ │ │ │ └── ChatPane.tsx │ │ │ ├── message │ │ │ │ ├── BlockOpContinue.tsx │ │ │ │ ├── BlockOpOptions.tsx │ │ │ │ ├── ChatMessage.styles.ts │ │ │ │ ├── ChatMessage.tsx │ │ │ │ ├── CleanerMessage.tsx │ │ │ │ ├── explainServiceErrors.tsx │ │ │ │ ├── fragments-attachment-doc │ │ │ │ │ ├── DocAttachmentFragment.tsx │ │ │ │ │ ├── DocAttachmentFragmentButton.tsx │ │ │ │ │ ├── DocumentAttachmentFragments.tsx │ │ │ │ │ └── livefile-sync │ │ │ │ │ │ ├── LiveFileControlButton.tsx │ │ │ │ │ │ └── useLiveFileSync.tsx │ │ │ │ ├── fragments-attachment-image │ │ │ │ │ └── ImageAttachmentFragments.tsx │ │ │ │ ├── fragments-content │ │ │ │ │ ├── BlockEdit_TextFragment.tsx │ │ │ │ │ ├── BlockOpEmpty.tsx │ │ │ │ │ ├── BlockPartError.tsx │ │ │ │ │ ├── BlockPartImageRef.tsx │ │ │ │ │ ├── BlockPartText_AutoBlocks.tsx │ │ │ │ │ ├── BlockPartToolInvocation.tsx │ │ │ │ │ ├── BlockPartToolResponse.tsx │ │ │ │ │ ├── ContentFragments.tsx │ │ │ │ │ ├── ViewDocPartModal.tsx │ │ │ │ │ └── ViewImageRefPartModal.tsx │ │ │ │ ├── fragments-void │ │ │ │ │ ├── BlockPartModelAnnotations.tsx │ │ │ │ │ ├── BlockPartModelAux.tsx │ │ │ │ │ ├── BlockPartPlaceholder.tsx │ │ │ │ │ └── VoidFragments.tsx │ │ │ │ ├── in-reference-to │ │ │ │ │ ├── InReferenceToBubble.tsx │ │ │ │ │ └── InReferenceToList.tsx │ │ │ │ └── useSelHighlighterMemo.ts │ │ │ ├── messages-list │ │ │ │ └── CMLZeroConversation.tsx │ │ │ ├── panes │ │ │ │ └── store-panes-manager.ts │ │ │ └── persona-selector │ │ │ │ ├── PersonaSelector.tsx │ │ │ │ └── store-purposes.ts │ │ ├── editors │ │ │ ├── _handleExecute.ts │ │ │ ├── _handleExecuteCommand.ts │ │ │ ├── browse-load.ts │ │ │ ├── chat-persona.ts │ │ │ ├── image-generate.ts │ │ │ ├── persona │ │ │ │ └── PersonaChatMessageSpeak.ts │ │ │ └── react-tangent.ts │ │ ├── execute-mode │ │ │ ├── ExecuteModeMenu.tsx │ │ │ ├── execute-mode.items.ts │ │ │ ├── execute-mode.types.ts │ │ │ └── useChatExecuteMode.tsx │ │ └── store-app-chat.ts │ ├── diff │ │ └── AppDiff.tsx │ ├── draw │ │ ├── AppDraw.tsx │ │ ├── DrawCreate.tsx │ │ ├── DrawGallery.tsx │ │ ├── create │ │ │ ├── ButtonPromptFromIdea.tsx │ │ │ ├── ButtonPromptFromX.tsx │ │ │ ├── DrawProviderConfigure.tsx │ │ │ ├── DrawProviderSelector.tsx │ │ │ ├── DrawSectionHeading.tsx │ │ │ ├── PromptComposer.tsx │ │ │ ├── ZeroDrawConfig.tsx │ │ │ ├── ZeroGenerations.tsx │ │ │ └── useDrawIdeas.tsx │ │ ├── gallery │ │ │ └── ZeroGallery.tsx │ │ ├── promptfx │ │ │ └── PromptFX.tsx │ │ ├── queue-draw-create.tsx │ │ └── useDrawSectionDropdown.tsx │ ├── link-chat │ │ ├── AppLinkChat.tsx │ │ ├── LinkChatAppMenuItems.tsx │ │ ├── LinkChatDrawer.tsx │ │ └── LinkChatViewer.tsx │ ├── news │ │ ├── AppNews.tsx │ │ ├── beam.data.tsx │ │ ├── bigAgi2.data.tsx │ │ └── news.data.tsx │ ├── personas │ │ ├── AppPersonas.tsx │ │ ├── creator │ │ │ ├── Creator.tsx │ │ │ ├── CreatorDrawer.tsx │ │ │ ├── CreatorDrawerItem.tsx │ │ │ ├── FromText.tsx │ │ │ ├── FromYouTube.tsx │ │ │ └── Viewer.tsx │ │ └── store-app-personas.ts │ ├── settings-modal │ │ ├── AppChatSettingsAI.tsx │ │ ├── SettingsModal.tsx │ │ ├── ShortcutsModal.tsx │ │ ├── UxLabsSettings.tsx │ │ ├── VoiceSettings.tsx │ │ └── settings-ui │ │ │ ├── AppChatSettingsUI.tsx │ │ │ ├── SettingUIComplexity.tsx │ │ │ ├── SettingUIComposerQuickButton.tsx │ │ │ └── SettingUIContentScaling.tsx │ └── tokens │ │ └── AppTokens.tsx ├── common │ ├── app.config.ts │ ├── app.nav.ts │ ├── app.queryclient.ts │ ├── app.release.ts │ ├── app.routes.ts │ ├── app.theme.ts │ ├── attachment-drafts │ │ ├── attachment.dblobs.ts │ │ ├── attachment.livefile.ts │ │ ├── attachment.mimetypes.ts │ │ ├── attachment.pipeline.ts │ │ ├── attachment.types.ts │ │ ├── file-converters │ │ │ └── DocxToMarkdown.ts │ │ ├── store-attachment-drafts_slice.ts │ │ ├── store-attachment-drafts_vanilla.ts │ │ └── useAttachmentDrafts.tsx │ ├── chat-overlay │ │ ├── ConversationHandler.ts │ │ ├── ConversationsManager.ts │ │ ├── store-perchat-composer_slice.ts │ │ ├── store-perchat-ephemerals_slice.ts │ │ ├── store-perchat-variform_slice.ts │ │ └── store-perchat_vanilla.ts │ ├── components │ │ ├── 3rdparty │ │ │ ├── GoogleAnalytics.tsx │ │ │ └── PostHogAnalytics.tsx │ │ ├── AlreadySet.tsx │ │ ├── AppBreadcrumbs.tsx │ │ ├── AvatarDomainFavicon.tsx │ │ ├── ButtonAttachFiles.tsx │ │ ├── ChipExpander.tsx │ │ ├── ChipToggleButton.tsx │ │ ├── CloseablePopup.tsx │ │ ├── DarkModeToggleButton.tsx │ │ ├── DataStreamViz.tsx │ │ ├── DebouncedInput.tsx │ │ ├── ErrorBoundary.tsx │ │ ├── ExpanderAccordion.tsx │ │ ├── ExpanderControlledBox.tsx │ │ ├── ExplainerCarousel.tsx │ │ ├── ExternalDocsLink.tsx │ │ ├── ExternalLink.tsx │ │ ├── FeatureBadge.tsx │ │ ├── GitHubProjectIssueCard.tsx │ │ ├── GoodTooltip.tsx │ │ ├── InlineError.tsx │ │ ├── InlineTextarea.tsx │ │ ├── KeyStroke.tsx │ │ ├── LanguageSelect.tsx │ │ ├── Languages.json │ │ ├── Link.tsx │ │ ├── LogoProgress.tsx │ │ ├── Section.tsx │ │ ├── StackedBarBreakdown.tsx │ │ ├── TooltipOutlined.tsx │ │ ├── VideoPlayerVimeo.tsx │ │ ├── VideoPlayerYouTube.tsx │ │ ├── dnd-dt │ │ │ ├── GlobalDragOverlay.tsx │ │ │ ├── useDragDropDataTransfer.tsx │ │ │ └── volstore-drag-global.ts │ │ ├── forms │ │ │ ├── FormChipControl.tsx │ │ │ ├── FormInputKey.tsx │ │ │ ├── FormLabelStart.tsx │ │ │ ├── FormRadioControl.tsx │ │ │ ├── FormSelectControl.tsx │ │ │ ├── FormSliderControl.tsx │ │ │ ├── FormSwitchControl.tsx │ │ │ ├── FormTextField.tsx │ │ │ ├── SetupFormRefetchButton.tsx │ │ │ ├── useFormEditTextArray.tsx │ │ │ ├── useFormRadio.tsx │ │ │ ├── useFormRadioLlmType.tsx │ │ │ └── useLLMSelect.tsx │ │ ├── icons │ │ │ ├── 3rdparty │ │ │ │ ├── AuthGitHubIcon.tsx │ │ │ │ ├── AuthGoogleIcon.tsx │ │ │ │ ├── CodePenIcon.tsx │ │ │ │ ├── DiscordIcon.tsx │ │ │ │ ├── ExternalTickIcon.tsx │ │ │ │ ├── GoogleColabIcon.tsx │ │ │ │ ├── JSFiddleIcon.tsx │ │ │ │ └── StackBlitzIcon.tsx │ │ │ ├── CalloutTopRightIcon.tsx │ │ │ ├── ChatBeamIcon.tsx │ │ │ ├── ChatMulticastOffIcon.tsx │ │ │ ├── ChatMulticastOnIcon.tsx │ │ │ ├── CodiconSplitHorizontal.tsx │ │ │ ├── CodiconSplitHorizontalRemove.tsx │ │ │ ├── CodiconSplitVertical.tsx │ │ │ ├── CodiconSplitVerticalRemove.tsx │ │ │ ├── CodiconUnsplit.tsx │ │ │ ├── FoldersToggleOff.tsx │ │ │ ├── FoldersToggleOn.tsx │ │ │ ├── LayoutSidebarRight.tsx │ │ │ ├── LiveFilePatchIcon.tsx │ │ │ ├── MarkHighlightIcon.tsx │ │ │ ├── WindowPaneRightClose.tsx │ │ │ ├── WindowPaneRightOpen.tsx │ │ │ ├── big-agi │ │ │ │ ├── BigAgiCircleIcon.tsx │ │ │ │ ├── BigAgiCircleInnerIcon.tsx │ │ │ │ └── BigAgiSquircleIcon.tsx │ │ │ ├── phosphor │ │ │ │ ├── PhGearSixIcon.tsx │ │ │ │ ├── PhPaintBrush.tsx │ │ │ │ ├── PhPaintBrushHousehold.tsx │ │ │ │ ├── PhSlidersHorizontalIcon.tsx │ │ │ │ ├── PhSlidersIcon.tsx │ │ │ │ └── PhUsers.tsx │ │ │ └── vendors │ │ │ │ ├── AlibabaCloudIcon.tsx │ │ │ │ ├── AnthropicIcon.tsx │ │ │ │ ├── AzureIcon.tsx │ │ │ │ ├── DeepseekIcon.tsx │ │ │ │ ├── GeminiIcon.tsx │ │ │ │ ├── GroqIcon.tsx │ │ │ │ ├── LMStudioIcon.tsx │ │ │ │ ├── LocalAIIcon.tsx │ │ │ │ ├── MistralIcon.tsx │ │ │ │ ├── OllamaIcon.tsx │ │ │ │ ├── OpenAIIcon.tsx │ │ │ │ ├── OpenPipeIcon.tsx │ │ │ │ ├── OpenRouterIcon.tsx │ │ │ │ ├── PerplexityIcon.tsx │ │ │ │ ├── TogetherIcon.tsx │ │ │ │ └── XAIIcon.tsx │ │ ├── modals │ │ │ ├── ConfirmationModal.tsx │ │ │ ├── GoodModal.tsx │ │ │ └── SherpaModal.tsx │ │ ├── panes │ │ │ └── GoodPanelResizeHandler.tsx │ │ ├── shortcuts │ │ │ ├── globalShortcutsHandler.ts │ │ │ ├── store-global-shortcuts.ts │ │ │ └── useGlobalShortcuts.ts │ │ ├── snackbar │ │ │ ├── SnackbarInsert.tsx │ │ │ └── useSnackbarsStore.ts │ │ ├── speechrecognition │ │ │ ├── AudioRecorderEngine.ts │ │ │ ├── WebSpeechApiEngine.ts │ │ │ └── useSpeechRecognition.ts │ │ ├── useCameraCapture.tsx │ │ ├── useCapabilities.ts │ │ ├── useChipBoolean.tsx │ │ ├── useDebouncer.ts │ │ ├── useDebugHook.ts │ │ ├── useDontBlurTextarea.ts │ │ ├── useFullscreenElement.tsx │ │ ├── useIsBrowserTranslating.tsx │ │ ├── useMatchMedia.ts │ │ ├── useNextLoadProgress.tsx │ │ └── useSingleTabEnforcer.ts │ ├── events │ │ ├── events.bus.ts │ │ ├── events.flow.ts │ │ ├── events.stateful.ts │ │ ├── events.types.ts │ │ └── index.ts │ ├── layout │ │ ├── container │ │ │ └── ContainerLayout.tsx │ │ ├── optima │ │ │ ├── InvertedBar.tsx │ │ │ ├── Modals.tsx │ │ │ ├── OptimaLayout.tsx │ │ │ ├── OptimaMOTD.tsx │ │ │ ├── PageCore.tsx │ │ │ ├── PageWrapper.tsx │ │ │ ├── bar │ │ │ │ ├── OptimaBar.tsx │ │ │ │ └── OptimaBarDropdown.tsx │ │ │ ├── drawer │ │ │ │ ├── DesktopDrawer.tsx │ │ │ │ ├── MobileDrawer.tsx │ │ │ │ ├── OptimaDrawerHeader.tsx │ │ │ │ └── OptimaDrawerList.tsx │ │ │ ├── nav │ │ │ │ ├── BringTheLove.tsx │ │ │ │ ├── DesktopNav.tsx │ │ │ │ ├── DesktopNavIcon.tsx │ │ │ │ ├── MobileNav.tsx │ │ │ │ ├── MobileNavIcon.tsx │ │ │ │ └── MobileNavItems.tsx │ │ │ ├── optima.config.ts │ │ │ ├── page │ │ │ │ └── OptimaAppPageHeading.tsx │ │ │ ├── panel │ │ │ │ ├── DesktopPanel.tsx │ │ │ │ ├── MobilePanel.tsx │ │ │ │ ├── MobilePreferencesListItem.tsx │ │ │ │ ├── OptimaPanelGroupedList.tsx │ │ │ │ ├── PanelContentPortal.tsx │ │ │ │ └── PopupPanel.tsx │ │ │ ├── portals │ │ │ │ ├── OptimaPortalsIn.tsx │ │ │ │ ├── store-layout-portals.ts │ │ │ │ ├── useOptimaPortalHasInputs.ts │ │ │ │ └── useOptimaPortalOutRef.ts │ │ │ ├── scratchclip │ │ │ │ ├── ScratchClip.tsx │ │ │ │ ├── store-scratchclip.ts │ │ │ │ └── useGlobalClipboardSaver.ts │ │ │ ├── store-layout-optima.ts │ │ │ └── useOptima.tsx │ │ ├── overlays │ │ │ ├── OverlaysInsert.tsx │ │ │ ├── store-layout-overlays.ts │ │ │ └── useOverlayComponents.tsx │ │ └── withLayout.tsx │ ├── livefile │ │ ├── liveFile.icons.ts │ │ ├── liveFile.types.ts │ │ ├── livefile.theme.ts │ │ ├── store-live-file.ts │ │ ├── useLiveFileContent.tsx │ │ └── useLiveFileMetadata.tsx │ ├── logger │ │ ├── hooks │ │ │ └── useClientLoggerInterception.ts │ │ ├── index.ts │ │ ├── interceptors │ │ │ ├── logger.network.ts │ │ │ └── logger.unhandled.ts │ │ ├── logger.client.ts │ │ ├── logger.factory.ts │ │ ├── logger.types.ts │ │ ├── store-logger.ts │ │ └── viewer │ │ │ ├── LogEntryDetails.tsx │ │ │ └── LoggerViewerDialog.tsx │ ├── logic │ │ ├── ProcessingQueue.ts │ │ ├── reconfigureBackendModels.ts │ │ └── store-logic-sherpa.ts │ ├── providers │ │ ├── ProviderBackendCapabilities.tsx │ │ ├── ProviderBootstrapLogic.tsx │ │ ├── ProviderSingleTab.tsx │ │ └── ProviderTheming.tsx │ ├── scroll-to-bottom │ │ ├── ScrollToBottom.tsx │ │ ├── ScrollToBottomButton.tsx │ │ └── useScrollToBottom.tsx │ ├── stores │ │ ├── chat │ │ │ ├── chat.conversation.ts │ │ │ ├── chat.fragments.ts │ │ │ ├── chat.gc.ts │ │ │ ├── chat.message.ts │ │ │ ├── chat.tokens.ts │ │ │ ├── chats.converters.ts │ │ │ ├── hooks │ │ │ │ ├── useChatsCount.ts │ │ │ │ ├── useConversationTitle.ts │ │ │ │ └── useFragmentBuckets.ts │ │ │ └── store-chats.ts │ │ ├── folders │ │ │ └── store-chat-folders.ts │ │ ├── llms │ │ │ ├── hooks │ │ │ │ ├── useAllLLMs.ts │ │ │ │ ├── useModelDomain.ts │ │ │ │ └── useModelDomains.ts │ │ │ ├── llms.hooks.ts │ │ │ ├── llms.parameters.ts │ │ │ ├── llms.pricing.ts │ │ │ ├── llms.service.types.ts │ │ │ ├── llms.types.ts │ │ │ ├── model.domains.registry.ts │ │ │ ├── model.domains.types.ts │ │ │ ├── modelconfiguration.types.ts │ │ │ ├── store-llms-domains_slice.ts │ │ │ └── store-llms.ts │ │ ├── metrics │ │ │ ├── metrics.chatgenerate.ts │ │ │ ├── metrics.modelservice.ts │ │ │ └── store-metrics.ts │ │ ├── store-client.ts │ │ ├── store-ui.ts │ │ ├── store-ux-labs.ts │ │ └── workspace │ │ │ ├── WorkspaceIdProvider.tsx │ │ │ ├── WorkspaceLiveFilePicker.tsx │ │ │ ├── store-client-workspace.ts │ │ │ ├── useWorkspaceContentsMetadata.ts │ │ │ └── workspace.types.ts │ ├── styles │ │ ├── CodePrism.css │ │ ├── GithubMarkdown.css │ │ ├── NProgress.css │ │ ├── agi.effects.css │ │ └── app.styles.css │ ├── tokens │ │ ├── tokens.image.ts │ │ ├── tokens.text.ts │ │ └── useTokenizerSelect.tsx │ ├── types │ │ ├── immutable.types.ts │ │ ├── next.page.d.ts │ │ └── useful.types.ts │ └── util │ │ ├── animUtils.ts │ │ ├── audio │ │ ├── AudioGenerator.ts │ │ ├── AudioLivePlayer.ts │ │ ├── AudioPlayer.ts │ │ └── usePlayUrl.ts │ │ ├── blobUtils.ts │ │ ├── canvasUtils.ts │ │ ├── clientFetchers.ts │ │ ├── clipboardUtils.ts │ │ ├── costUtils.ts │ │ ├── dMessageUtils.tsx │ │ ├── downloadUtils.ts │ │ ├── errorUtils.ts │ │ ├── eventUtils.ts │ │ ├── fileSystemUtils.ts │ │ ├── hooks │ │ ├── useAsyncCallBusy.ts │ │ ├── useDeep.ts │ │ ├── useShallowObject.ts │ │ └── useToggleableBoolean.ts │ │ ├── htmlTableToMarkdown.ts │ │ ├── idUtils.ts │ │ ├── idbUtils.ts │ │ ├── imageUtils.ts │ │ ├── jsonUtils.ts │ │ ├── mediasession │ │ ├── MediaSessionManager.ts │ │ └── useMediaSessionCallbacks.ts │ │ ├── pdfUtils.ts │ │ ├── promptUtils.ts │ │ ├── pwaUtils.ts │ │ ├── screenCaptureUtils.ts │ │ ├── stateUtils.tsx │ │ ├── storageUtils.ts │ │ ├── textUtils.ts │ │ ├── timeUtils.ts │ │ ├── trpc.client.ts │ │ ├── urlUtils.ts │ │ ├── videoUtils.ts │ │ ├── viewTransitionUtils.ts │ │ ├── webGeolocationUtils.ts │ │ └── windowUtils.tsx ├── data.ts ├── modules │ ├── 3rdparty │ │ ├── THIRD_PARTY_NOTICES.md │ │ ├── aider │ │ │ ├── AIDER-LICENSE.txt │ │ │ ├── coderPrompts.ts │ │ │ ├── editBlockCoder.ts │ │ │ ├── editBlockPrompts.ts │ │ │ ├── glue.ts │ │ │ └── wholeFilePrompts.ts │ │ └── t3-env │ │ │ ├── env-core.ts │ │ │ ├── index.ts │ │ │ └── standard.ts │ ├── aifn │ │ ├── agiattachmentprompts │ │ │ ├── agiAttachmentPrompts.ts │ │ │ └── useAgiAttachmentPrompts.tsx │ │ ├── agicodefixup │ │ │ ├── agiFixupCode.ts │ │ │ └── useAgiFixupCode.tsx │ │ ├── auto-chat-follow-ups │ │ │ └── autoChatFollowUps.ts │ │ ├── autotitle │ │ │ └── autoTitle.ts │ │ ├── digrams │ │ │ ├── DiagramsModal.tsx │ │ │ └── diagrams.data.ts │ │ ├── flatten │ │ │ ├── FlattenerModal.tsx │ │ │ └── flatten.data.ts │ │ ├── imagine │ │ │ └── imaginePromptFromText.ts │ │ ├── react │ │ │ └── react.ts │ │ ├── summarize │ │ │ ├── ContentReducer.tsx │ │ │ └── summerize.ts │ │ ├── useLLMChain.ts │ │ └── useStreamChatText.ts │ ├── aix │ │ ├── AIX.README.md │ │ ├── client │ │ │ ├── ContentReassembler.ts │ │ │ ├── aix.client.chatGenerateRequest.ts │ │ │ ├── aix.client.fromSimpleFunction.ts │ │ │ ├── aix.client.test.ts │ │ │ ├── aix.client.ts │ │ │ ├── debugger │ │ │ │ ├── AixDebuggerDialog.tsx │ │ │ │ ├── AixDebuggerFrame.tsx │ │ │ │ ├── AixDebuggerMeasurementsTable.tsx │ │ │ │ ├── memstore-aix-client-debugger.ts │ │ │ │ └── reassembler-debug.ts │ │ │ └── withDecimator.ts │ │ └── server │ │ │ ├── api │ │ │ ├── aix.router.ts │ │ │ └── aix.wiretypes.ts │ │ │ └── dispatch │ │ │ ├── PerformanceProfiler.ts │ │ │ ├── chatGenerate │ │ │ ├── ChatGenerateTransmitter.ts │ │ │ ├── IParticleTransmitter.ts │ │ │ ├── adapters │ │ │ │ ├── anthropic.messageCreate.ts │ │ │ │ ├── gemini.generateContent.ts │ │ │ │ └── openai.chatCompletions.ts │ │ │ ├── chatGenerate.dispatch.ts │ │ │ └── parsers │ │ │ │ ├── anthropic.parser.ts │ │ │ │ ├── gemini.audioutils.ts │ │ │ │ ├── gemini.parser.ts │ │ │ │ └── openai.parser.ts │ │ │ ├── heartbeatsWhileAwaiting.ts │ │ │ ├── stream.demuxer.fastsse.ts │ │ │ ├── stream.demuxer.sse.ts │ │ │ ├── stream.demuxers.ts │ │ │ └── wiretypes │ │ │ ├── anthropic.wiretypes.ts │ │ │ ├── gemini.wiretypes.ts │ │ │ └── openai.wiretypes.ts │ ├── backend │ │ ├── backend.router.ts │ │ └── store-backend-capabilities.ts │ ├── beam │ │ ├── BeamCard.tsx │ │ ├── BeamExplainer.tsx │ │ ├── BeamView.tsx │ │ ├── beam.config.ts │ │ ├── gather │ │ │ ├── BeamFusionGrid.tsx │ │ │ ├── BeamGatherPane.tsx │ │ │ ├── Fusion.tsx │ │ │ ├── FusionControls.tsx │ │ │ ├── FusionInstructionsEditor.tsx │ │ │ ├── beam.gather.ts │ │ │ └── instructions │ │ │ │ ├── GatherInstruction.tsx │ │ │ │ ├── UserInputChecklistComponent.tsx │ │ │ │ ├── UserInputChecklistInstruction.tsx │ │ │ │ ├── beam.gather.execution.tsx │ │ │ │ └── beam.gather.factories.ts │ │ ├── scatter │ │ │ ├── BeamRay.tsx │ │ │ ├── BeamRayGrid.tsx │ │ │ ├── BeamScatterInput.tsx │ │ │ ├── BeamScatterPane.tsx │ │ │ ├── BeamScatterPaneDropdown.tsx │ │ │ └── beam.scatter.ts │ │ ├── store-beam.hooks.ts │ │ ├── store-beam_vanilla.ts │ │ └── store-module-beam.tsx │ ├── blocks │ │ ├── AutoBlocksRenderer.tsx │ │ ├── BlocksContainers.tsx │ │ ├── OverlayButton.tsx │ │ ├── ScaledTextBlockRenderer.tsx │ │ ├── ToggleExpansionButton.tsx │ │ ├── blocks.hooks.ts │ │ ├── blocks.styles.ts │ │ ├── blocks.textparser.ts │ │ ├── blocks.types.ts │ │ ├── code │ │ │ ├── RenderCode.css │ │ │ ├── RenderCode.tsx │ │ │ ├── RenderCodePanelFrame.tsx │ │ │ ├── code-buttons │ │ │ │ ├── openInCodePen.tsx │ │ │ │ ├── openInGoogleColab.tsx │ │ │ │ ├── openInJsFiddle.tsx │ │ │ │ ├── openInStackBlitz.tsx │ │ │ │ └── useOpenInWebEditors.tsx │ │ │ ├── code-highlight │ │ │ │ └── codePrism.ts │ │ │ └── code-renderers │ │ │ │ ├── RenderCodeHtmlIFrame.tsx │ │ │ │ ├── RenderCodeMermaid.tsx │ │ │ │ ├── RenderCodePlantUML.tsx │ │ │ │ ├── RenderCodeSVG.tsx │ │ │ │ ├── RenderCodeSyntax.tsx │ │ │ │ └── plantuml.utils.ts │ │ ├── danger-html │ │ │ └── RenderDangerousHtml.tsx │ │ ├── enhanced-code │ │ │ ├── EnhancedRenderCode.tsx │ │ │ ├── EnhancedRenderCodeMenu.tsx │ │ │ ├── codeCollapseManager.ts │ │ │ └── livefile-patch │ │ │ │ ├── useLiveFilePatch.tsx │ │ │ │ └── usePatchingWorkflow.tsx │ │ ├── image │ │ │ ├── RenderImageRefDBlob.tsx │ │ │ └── RenderImageURL.tsx │ │ ├── markdown │ │ │ ├── CustomARenderer.tsx │ │ │ ├── CustomMarkdownRenderer.tsx │ │ │ ├── RenderMarkdown.tsx │ │ │ └── markdown.wrapper.ts │ │ ├── plaintext │ │ │ └── RenderPlainText.tsx │ │ └── wordsdiff │ │ │ └── RenderWordsDiff.tsx │ ├── browse │ │ ├── BrowseSettings.tsx │ │ ├── browse.client.ts │ │ ├── browse.files.ts │ │ ├── browse.router.ts │ │ └── store-module-browsing.tsx │ ├── dblobs │ │ ├── dblobs.db.ts │ │ ├── dblobs.hooks.ts │ │ ├── dblobs.images.ts │ │ └── dblobs.types.ts │ ├── elevenlabs │ │ ├── ElevenlabsSettings.tsx │ │ ├── elevenlabs.client.ts │ │ ├── elevenlabs.router.ts │ │ ├── store-module-elevenlabs.ts │ │ └── useElevenLabsVoiceDropdown.tsx │ ├── google │ │ ├── GoogleSearchSettings.tsx │ │ ├── search.client.ts │ │ ├── search.router.ts │ │ ├── search.types.ts │ │ └── store-module-google.ts │ ├── llms │ │ ├── llm.client.hooks.ts │ │ ├── llm.client.ts │ │ ├── models-modal │ │ │ ├── LLMOptionsGlobal.tsx │ │ │ ├── LLMOptionsModal.tsx │ │ │ ├── LLMParametersEditor.tsx │ │ │ ├── ModelsList.tsx │ │ │ ├── ModelsModal.tsx │ │ │ ├── ModelsServiceSelector.tsx │ │ │ └── ModelsWizard.tsx │ │ ├── server │ │ │ ├── anthropic │ │ │ │ ├── anthropic.models.ts │ │ │ │ └── anthropic.router.ts │ │ │ ├── gemini │ │ │ │ ├── gemini.models.ts │ │ │ │ └── gemini.router.ts │ │ │ ├── llm.server.types.ts │ │ │ ├── ollama │ │ │ │ ├── ollama.models.ts │ │ │ │ ├── ollama.router.ts │ │ │ │ └── ollama.wiretypes.ts │ │ │ └── openai │ │ │ │ ├── fireworksai.wiretypes.ts │ │ │ │ ├── groq.wiretypes.ts │ │ │ │ ├── localai.wiretypes.ts │ │ │ │ ├── mistral.wiretypes.ts │ │ │ │ ├── models.cba.ts │ │ │ │ ├── models │ │ │ │ ├── alibaba.models.ts │ │ │ │ ├── azure.models.ts │ │ │ │ ├── chutesai.models.ts │ │ │ │ ├── deepseek.models.ts │ │ │ │ ├── fastapi.models.ts │ │ │ │ ├── fireworksai.models.ts │ │ │ │ ├── groq.models.ts │ │ │ │ ├── mistral.models.ts │ │ │ │ ├── models.data.ts │ │ │ │ ├── openai.models.ts │ │ │ │ ├── openpipe.models.ts │ │ │ │ ├── openrouter.models.ts │ │ │ │ ├── perplexity.models.ts │ │ │ │ ├── together.models.ts │ │ │ │ └── xai.models.ts │ │ │ │ ├── openai.router.ts │ │ │ │ ├── openpipe.wiretypes.ts │ │ │ │ ├── openrouter.wiretypes.ts │ │ │ │ └── togetherai.wiretypes.ts │ │ └── vendors │ │ │ ├── ApproximateCosts.tsx │ │ │ ├── IModelVendor.ts │ │ │ ├── alibaba │ │ │ ├── AlibabaServiceSetup.tsx │ │ │ └── alibaba.vendor.ts │ │ │ ├── anthropic │ │ │ ├── AnthropicServiceSetup.tsx │ │ │ └── anthropic.vendor.ts │ │ │ ├── azure │ │ │ ├── AzureServiceSetup.tsx │ │ │ └── azure.vendor.ts │ │ │ ├── deepseek │ │ │ ├── DeepseekAIServiceSetup.tsx │ │ │ └── deepseekai.vendor.ts │ │ │ ├── gemini │ │ │ ├── GeminiServiceSetup.tsx │ │ │ └── gemini.vendor.ts │ │ │ ├── groq │ │ │ ├── GroqServiceSetup.tsx │ │ │ └── groq.vendor.ts │ │ │ ├── lmstudio │ │ │ ├── LMStudioServiceSetup.tsx │ │ │ └── lmstudio.vendor.ts │ │ │ ├── localai │ │ │ ├── LocalAIAdmin.tsx │ │ │ ├── LocalAIServiceSetup.tsx │ │ │ └── localai.vendor.ts │ │ │ ├── mistral │ │ │ ├── MistralServiceSetup.tsx │ │ │ └── mistral.vendor.ts │ │ │ ├── ollama │ │ │ ├── OllamaAdministration.tsx │ │ │ ├── OllamaServiceSetup.tsx │ │ │ └── ollama.vendor.ts │ │ │ ├── openai │ │ │ ├── OpenAIServiceSetup.tsx │ │ │ └── openai.vendor.ts │ │ │ ├── openpipe │ │ │ ├── OpenPipeServiceSetup.tsx │ │ │ └── openpipe.vendor.ts │ │ │ ├── openrouter │ │ │ ├── OpenRouterServiceSetup.tsx │ │ │ └── openrouter.vendor.ts │ │ │ ├── perplexity │ │ │ ├── PerplexityServiceSetup.tsx │ │ │ └── perplexity.vendor.ts │ │ │ ├── togetherai │ │ │ ├── TogetherAIServiceSetup.tsx │ │ │ └── togetherai.vendor.ts │ │ │ ├── useServiceSetup.ts │ │ │ ├── vendor.helpers.ts │ │ │ ├── vendors.registry.ts │ │ │ └── xai │ │ │ ├── XAIServiceSetup.tsx │ │ │ └── xai.vendor.ts │ ├── persona │ │ └── pmix │ │ │ ├── pmix.parameters.ts │ │ │ └── pmix.ts │ ├── t2i │ │ ├── T2ISettings.tsx │ │ ├── dalle │ │ │ ├── DallESettings.tsx │ │ │ ├── openaiGenerateImages.ts │ │ │ └── store-module-dalle.ts │ │ ├── localai │ │ │ └── localaiGenerateImages.ts │ │ ├── prodia │ │ │ ├── ProdiaSettings.tsx │ │ │ ├── prodia.models.ts │ │ │ ├── prodia.router.ts │ │ │ ├── prodiaGenerateImages.ts │ │ │ └── store-module-prodia.ts │ │ ├── store-module-t2i.ts │ │ ├── t2i.client.ts │ │ └── t2i.server.ts │ ├── trade │ │ ├── BackupRestore.tsx │ │ ├── ExportChats.tsx │ │ ├── ImportChats.tsx │ │ ├── ImportOutcomeModal.tsx │ │ ├── TradeModal.tsx │ │ ├── link │ │ │ ├── ChatLinkDetails.tsx │ │ │ ├── ChatLinkExport.tsx │ │ │ └── store-share-link.ts │ │ ├── publish │ │ │ ├── PublishDetails.tsx │ │ │ └── PublishExport.tsx │ │ ├── server │ │ │ ├── chatgpt.ts │ │ │ ├── link.ts │ │ │ ├── pastegg.ts │ │ │ └── trade.router.ts │ │ └── trade.client.ts │ └── youtube │ │ ├── YouTubeURLInput.tsx │ │ ├── useYouTubeTranscript.tsx │ │ ├── youtube.router.ts │ │ ├── youtube.server.ts │ │ ├── youtube.types.ts │ │ └── youtube.utils.ts └── server │ ├── env.ts │ ├── prisma │ ├── prismaDb.ts │ └── schema.prisma │ ├── trpc │ ├── trpc.nanoid.ts │ ├── trpc.next-edge.ts │ ├── trpc.router-cloud.ts │ ├── trpc.router-edge.ts │ ├── trpc.router.fetchers.ts │ ├── trpc.server.ts │ └── trpc.transformer.ts │ └── wire.ts ├── tools ├── .gitignore └── ai │ ├── README.md │ └── repo-structure.sh └── tsconfig.json /.claude/settings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "allow": [ 4 | "Bash(cat:*)", 5 | "Bash(git branch:*)", 6 | "Bash(grep:*)", 7 | "Bash(node:*)", 8 | "Bash(npm run:*)", 9 | "Bash(npx tsc:*)", 10 | "Bash(rg:*)", 11 | "Bash(rm:*)", 12 | "Bash(sed:*)" 13 | ], 14 | "deny": [] 15 | } 16 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # big-AGI non-code files 2 | /docs/ 3 | /dist/ 4 | README.md 5 | 6 | # Ignore build and log files 7 | Dockerfile 8 | /.dockerignore 9 | 10 | # Node build artifacts 11 | /node_modules 12 | /.pnp 13 | .pnp.js 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | 19 | # production 20 | /build 21 | 22 | # versioning 23 | .git/ 24 | .github/ 25 | 26 | # IDEs 27 | .idea/ 28 | 29 | # debug 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | .pnpm-debug.log* 34 | 35 | # local env files 36 | .env*.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # typescript 42 | *.tsbuildinfo 43 | next-env.d.ts 44 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: enricoros # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/roadmap-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Roadmap request 3 | about: Suggest a roadmap item 4 | title: "[Roadmap]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Why** 11 | (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 12 | 13 | **Description** 14 | Clear and concise description of what you want to happen. 15 | 16 | **Requirements** 17 | If you can, Please break-down the changes use cases, UX, technology, architecture, etc. 18 | - [ ] ... 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Frontend Build: ignore API files disabled for this build 4 | /app/**/*.backup 5 | 6 | # Supabase - ignored for now 7 | /supabase/ 8 | /*.sql 9 | 10 | # dependencies 11 | /node_modules 12 | /.pnp 13 | .pnp.js 14 | 15 | # testing 16 | /coverage 17 | 18 | # next.js 19 | /.next/ 20 | /dist/ 21 | /out/ 22 | 23 | # production 24 | /build 25 | 26 | # misc 27 | .DS_Store 28 | *.pem 29 | 30 | # debug 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | .pnpm-debug.log* 35 | 36 | # local env files 37 | .env 38 | .env.* 39 | 40 | # vercel 41 | .vercel 42 | 43 | # typescript 44 | *.tsbuildinfo 45 | next-env.d.ts 46 | 47 | # other 48 | .idea/ 49 | 50 | # Ingore k8s/env-secret.yaml 51 | ./k8s/env-secret.yaml 52 | /certificates 53 | .env*.local 54 | /.run/dev (ENV).run.xml 55 | /src/modules/3rdparty/aider/scratch* 56 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | overrides=@mui/material@^5.0.0: 2 | dependencies: 3 | @mui/material: replaced-by=@mui/joy 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleAttributePerLine": false, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "endOfLine": "lf", 6 | "printWidth": 160 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2024 Enrico Ros 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/api/cloud/[trpc]/route.ts: -------------------------------------------------------------------------------- 1 | import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; 2 | 3 | import { appRouterCloud } from '~/server/trpc/trpc.router-cloud'; 4 | import { createTRPCFetchContext } from '~/server/trpc/trpc.server'; 5 | 6 | const handlerNodeRoutes = (req: Request) => fetchRequestHandler({ 7 | endpoint: '/api/cloud', 8 | router: appRouterCloud, 9 | req, 10 | createContext: createTRPCFetchContext, 11 | onError: 12 | process.env.NODE_ENV === 'development' 13 | ? ({ path, error }) => console.error(`❌ tRPC-cloud failed on ${path ?? 'unk-path'}: ${error.message}`) 14 | : undefined, 15 | }); 16 | 17 | 18 | // NOTE: the following statement breaks the build on non-pro deployments, and conditionals don't work either 19 | // so we resorted to raising the timeout from 10s to 60s in the vercel.json file instead 20 | export const maxDuration = 60; 21 | export const runtime = 'nodejs'; 22 | export const dynamic = 'force-dynamic'; 23 | export { handlerNodeRoutes as GET, handlerNodeRoutes as POST }; -------------------------------------------------------------------------------- /app/api/edge/[trpc]/route.ts: -------------------------------------------------------------------------------- 1 | import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; 2 | 3 | import { appRouterEdge } from '~/server/trpc/trpc.router-edge'; 4 | import { createTRPCFetchContext } from '~/server/trpc/trpc.server'; 5 | 6 | const handlerEdgeRoutes = (req: Request) => fetchRequestHandler({ 7 | endpoint: '/api/edge', 8 | router: appRouterEdge, 9 | req, 10 | createContext: createTRPCFetchContext, 11 | onError: 12 | process.env.NODE_ENV === 'development' 13 | ? ({ path, error }) => console.error(`❌ tRPC-edge failed on ${path ?? 'unk-path'}: ${error.message}`) 14 | : undefined, 15 | }); 16 | 17 | export const runtime = 'edge'; 18 | export { handlerEdgeRoutes as GET, handlerEdgeRoutes as POST }; -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # Very simple docker-compose file to run the app on http://localhost:3000 (or http://127.0.0.1:3000). 2 | # 3 | # For more examples, such runnin big-AGI alongside a web browsing service, see the `docs/docker` folder. 4 | 5 | version: '3.9' 6 | 7 | services: 8 | big-agi: 9 | image: ghcr.io/enricoros/big-agi:latest 10 | ports: 11 | - "3000:3000" 12 | env_file: 13 | - .env 14 | command: [ "next", "start", "-p", "3000" ] -------------------------------------------------------------------------------- /docs/docker/docker-compose-browserless.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to run `big-AGI` and `browserless` with Docker Compose. 2 | # 3 | # The two containers are linked together and `big-AGI` is configured to use `browserless` 4 | # as its Puppeteer endpoint (from the containers intranet, it is available browserless:3000). 5 | # 6 | # From your host, you can access big-AGI on http://127.0.0.1:3000 and browserless on http://127.0.0.1:9222. 7 | # 8 | # To start the containers, run: 9 | # docker-compose -f docs/docker/docker-compose-browserless.yaml up 10 | 11 | version: '3.9' 12 | 13 | services: 14 | big-agi: 15 | image: ghcr.io/enricoros/big-agi:latest 16 | ports: 17 | - "3000:3000" 18 | env_file: 19 | - .env 20 | environment: 21 | - PUPPETEER_WSS_ENDPOINT=ws://browserless:3000 22 | command: [ "next", "start", "-p", "3000" ] 23 | depends_on: 24 | - browserless 25 | 26 | browserless: 27 | image: browserless/chrome:latest 28 | ports: 29 | - "9222:3000" # Map host's port 9222 to container's port 3000 30 | environment: 31 | - MAX_CONCURRENT_SESSIONS=10 -------------------------------------------------------------------------------- /docs/draft-big-agi.md: -------------------------------------------------------------------------------- 1 | # Why big-AGI? 2 | Placeholder for a document that demonstrates the productivity and unique features of Big-AGI. 3 | 4 | ## Exclusive features 5 | - [x] Call AGI 6 | - [x] Continuous Voice mode 7 | - [x] Diagram generation 8 | - [ ] ... 9 | 10 | ## Productivity Features 11 | - [x] Multi-window to never wait 12 | - [x] Multi-Chat to explore different solutions 13 | - [x] Rendering of graphs, charts, mindmaps 14 | - [ ] ... -------------------------------------------------------------------------------- /docs/help-faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | Quick answers to common questions about Big-AGI. For detailed documentation, see our [Website Docs](https://big-agi.com/docs). 4 | 5 | ### Versions 6 | 7 |
8 | How do I check my Big-AGI version? 9 | 10 | You can see the version in the _News_ section of the app, as per the image below. 11 | 12 | ![Version location in Big-AGI](https://github.com/user-attachments/assets/cd295094-0114-420f-a5b9-0d762e59b506) 13 |
14 | 15 |
16 | How do I verify my Vercel deployment version? 17 | 18 | You can go in the **deployments** section of your Vercel project, and at a quick glance see 19 | what is the latest deployment status, time, and link to the source code. 20 | 21 | ![Vercel deployments view](https://github.com/user-attachments/assets/664b8c3d-496e-4595-ad5e-898bdb82507c) 22 | 23 | Each deployment links directly to its source code commit. 24 |
25 | 26 | --- 27 | 28 | Missing something? [Open an issue](https://github.com/enricoros/big-agi/issues/new) or [join our Discord](https://discord.gg/MkH4qj2Jp9). 29 | -------------------------------------------------------------------------------- /docs/k8s/big-agi-deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: ns-big-agi 6 | --- 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | labels: 11 | app: big-agi 12 | name: deployment-big-agi 13 | namespace: ns-big-agi 14 | spec: 15 | replicas: 1 16 | selector: 17 | matchLabels: 18 | app: big-agi 19 | strategy: {} 20 | template: 21 | metadata: 22 | labels: 23 | app: big-agi 24 | spec: 25 | containers: 26 | - image: ghcr.io/enricoros/big-agi:latest 27 | name: big-agi 28 | ports: 29 | - containerPort: 3000 30 | args: 31 | - next 32 | - start 33 | - -p 34 | - "3000" 35 | envFrom: 36 | - secretRef: 37 | name: env 38 | --- 39 | apiVersion: v1 40 | kind: Service 41 | metadata: 42 | labels: 43 | app: big-agi 44 | name: svc-big-agi 45 | namespace: ns-big-agi 46 | spec: 47 | ports: 48 | - name: "http" 49 | port: 3000 50 | targetPort: 3000 51 | selector: 52 | app: big-agi 53 | -------------------------------------------------------------------------------- /docs/k8s/env-secret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: env 6 | namespace: ns-big-agi 7 | type: Opaque 8 | stringData: 9 | # IMPORTANT: This file contains sensitive information. Do not commit changes to version control. 10 | # All variables are optional. Fill in only the ones you need. 11 | # 12 | # For the latest information on all the environment variables, see /docs/environment-variables.md 13 | # 14 | 15 | # LLMs 16 | OPENAI_API_KEY: "" 17 | OPENAI_API_HOST: "" 18 | OPENAI_API_ORG_ID: "" 19 | ALIBABA_API_HOST: "" 20 | ALIBABA_API_KEY: "" 21 | AZURE_OPENAI_API_ENDPOINT: "" 22 | AZURE_OPENAI_API_KEY: "" 23 | ANTHROPIC_API_KEY: "" 24 | ANTHROPIC_API_HOST: "" 25 | DEEPSEEK_API_KEY: "" 26 | GEMINI_API_KEY: "" 27 | GROQ_API_KEY: "" 28 | LOCALAI_API_HOST: "" 29 | LOCALAI_API_KEY: "" 30 | MISTRAL_API_KEY: "" 31 | OLLAMA_API_HOST: "" 32 | OPENPIPE_API_KEY: "" 33 | OPENROUTER_API_KEY: "" 34 | PERPLEXITY_API_KEY: "" 35 | TOGETHERAI_API_KEY: "" 36 | XAI_API_KEY: "" 37 | 38 | # Browse 39 | PUPPETEER_WSS_ENDPOINT: "" 40 | 41 | # Search 42 | GOOGLE_CLOUD_API_KEY: "" 43 | GOOGLE_CSE_ID: "" 44 | 45 | # Text-To-Speech: Eleven Labs 46 | ELEVENLABS_API_KEY: "" 47 | ELEVENLABS_API_HOST: "" 48 | ELEVENLABS_VOICE_ID: "" 49 | 50 | # Text-To-Image: Prodia 51 | PRODIA_API_KEY: "" 52 | -------------------------------------------------------------------------------- /docs/pixels/big-AGI-compo-20240201_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/big-AGI-compo-20240201_small.png -------------------------------------------------------------------------------- /docs/pixels/big-AGI-compo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/big-AGI-compo1.png -------------------------------------------------------------------------------- /docs/pixels/big-AGI-compo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/big-AGI-compo2.png -------------------------------------------------------------------------------- /docs/pixels/big-AGI-compo2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/big-AGI-compo2b.png -------------------------------------------------------------------------------- /docs/pixels/config-azure-openai-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-azure-openai-create.png -------------------------------------------------------------------------------- /docs/pixels/config-azure-openai-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-azure-openai-deploy.png -------------------------------------------------------------------------------- /docs/pixels/config-deploy-cloudflare-compat2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-deploy-cloudflare-compat2.png -------------------------------------------------------------------------------- /docs/pixels/config-localai-1-models.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-localai-1-models.png -------------------------------------------------------------------------------- /docs/pixels/config-localai-2-gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-localai-2-gallery.png -------------------------------------------------------------------------------- /docs/pixels/config-ollama-0-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-ollama-0-example.png -------------------------------------------------------------------------------- /docs/pixels/config-ollama-1-models.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-ollama-1-models.png -------------------------------------------------------------------------------- /docs/pixels/config-ollama-2-admin-pull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-ollama-2-admin-pull.png -------------------------------------------------------------------------------- /docs/pixels/config-ollama-3-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-ollama-3-chat.png -------------------------------------------------------------------------------- /docs/pixels/config-ollama-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-ollama-network.png -------------------------------------------------------------------------------- /docs/pixels/config-oobabooga-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/config-oobabooga-0.png -------------------------------------------------------------------------------- /docs/pixels/data_ownership_deployed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/data_ownership_deployed.png -------------------------------------------------------------------------------- /docs/pixels/data_ownership_hosted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/data_ownership_hosted.png -------------------------------------------------------------------------------- /docs/pixels/data_ownership_local_storage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/data_ownership_local_storage.png -------------------------------------------------------------------------------- /docs/pixels/feature-openrouter-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature-openrouter-add.png -------------------------------------------------------------------------------- /docs/pixels/feature-openrouter-configure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature-openrouter-configure.png -------------------------------------------------------------------------------- /docs/pixels/feature_drop_target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_drop_target.png -------------------------------------------------------------------------------- /docs/pixels/feature_imagine_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_imagine_command.png -------------------------------------------------------------------------------- /docs/pixels/feature_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_language.png -------------------------------------------------------------------------------- /docs/pixels/feature_paste_gg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_paste_gg.png -------------------------------------------------------------------------------- /docs/pixels/feature_purpose_two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_purpose_two.png -------------------------------------------------------------------------------- /docs/pixels/feature_pwa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_pwa.png -------------------------------------------------------------------------------- /docs/pixels/feature_react_google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_react_google.png -------------------------------------------------------------------------------- /docs/pixels/feature_react_turn_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_react_turn_on.png -------------------------------------------------------------------------------- /docs/pixels/feature_speak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_speak.png -------------------------------------------------------------------------------- /docs/pixels/feature_svg_drawing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_svg_drawing.png -------------------------------------------------------------------------------- /docs/pixels/feature_token_counter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_token_counter.png -------------------------------------------------------------------------------- /docs/pixels/feature_voice_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/feature_voice_1.png -------------------------------------------------------------------------------- /docs/pixels/gif_typing_040123.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/gif_typing_040123.gif -------------------------------------------------------------------------------- /docs/pixels/gif_typing_orig.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/gif_typing_orig.gif -------------------------------------------------------------------------------- /docs/pixels/screenshot_purpose_growth_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/screenshot_purpose_growth_1.png -------------------------------------------------------------------------------- /docs/pixels/screenshot_pwa_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/screenshot_pwa_1.png -------------------------------------------------------------------------------- /docs/pixels/screenshot_pwa_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/screenshot_pwa_2.png -------------------------------------------------------------------------------- /docs/pixels/zold_feature_system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/zold_feature_system.png -------------------------------------------------------------------------------- /docs/pixels/zold_screenshot_mobile_clean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/zold_screenshot_mobile_clean.png -------------------------------------------------------------------------------- /docs/pixels/zold_screenshot_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/docs/pixels/zold_screenshot_web.png -------------------------------------------------------------------------------- /pages/call.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AppCall } from '../src/apps/call/AppCall'; 4 | 5 | import { withNextJSPerPageLayout } from '~/common/layout/withLayout'; 6 | 7 | 8 | export default withNextJSPerPageLayout({ type: 'optima' }, () => ); 9 | -------------------------------------------------------------------------------- /pages/dev/beam.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AppBeam } from '../../src/apps/beam/AppBeam'; 4 | 5 | import { withNextJSPerPageLayout } from '~/common/layout/withLayout'; 6 | 7 | 8 | export default withNextJSPerPageLayout({ type: 'optima' }, () => ); 9 | -------------------------------------------------------------------------------- /pages/diff.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AppDiff } from '../src/apps/diff/AppDiff'; 4 | 5 | import { withNextJSPerPageLayout } from '~/common/layout/withLayout'; 6 | 7 | 8 | export default withNextJSPerPageLayout({ type: 'optima' }, () => ); 9 | -------------------------------------------------------------------------------- /pages/draw.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AppDraw } from '../src/apps/draw/AppDraw'; 4 | 5 | import { withNextJSPerPageLayout } from '~/common/layout/withLayout'; 6 | 7 | 8 | export default withNextJSPerPageLayout({ type: 'optima' }, () => ); 9 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AppChat } from '../src/apps/chat/AppChat'; 4 | 5 | import { withNextJSPerPageLayout } from '~/common/layout/withLayout'; 6 | 7 | 8 | export default withNextJSPerPageLayout({ type: 'optima' }, () => { 9 | 10 | // TODO: This Index page will point to the Dashboard (or a landing page) 11 | // For now it offers the chat experience, but this will change. #299 12 | 13 | return ; 14 | }); 15 | -------------------------------------------------------------------------------- /pages/link/chat/[chatLinkId].tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AppLinkChat } from '../../../src/apps/link-chat/AppLinkChat'; 4 | 5 | import { useRouterQuery } from '~/common/app.routes'; 6 | import { withNextJSPerPageLayout } from '~/common/layout/withLayout'; 7 | 8 | 9 | export default withNextJSPerPageLayout({ type: 'optima', suspendAutoModelsSetup: true }, () => { 10 | 11 | // external state 12 | const { chatLinkId } = useRouterQuery<{ chatLinkId: string | undefined }>(); 13 | 14 | return ; 15 | 16 | }); -------------------------------------------------------------------------------- /pages/news.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AppNews } from '../src/apps/news/AppNews'; 4 | 5 | import { markNewsAsSeen } from '~/common/logic/store-logic-sherpa'; 6 | import { withNextJSPerPageLayout } from '~/common/layout/withLayout'; 7 | 8 | 9 | export default withNextJSPerPageLayout({ type: 'optima', suspendAutoModelsSetup: true }, () => { 10 | 11 | // 'touch' the last seen news version 12 | React.useEffect(() => markNewsAsSeen(), []); 13 | 14 | return ; 15 | }); -------------------------------------------------------------------------------- /pages/personas.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AppPersonas } from '../src/apps/personas/AppPersonas'; 4 | 5 | import { withNextJSPerPageLayout } from '~/common/layout/withLayout'; 6 | 7 | 8 | export default withNextJSPerPageLayout({ type: 'optima' }, () => ); 9 | -------------------------------------------------------------------------------- /pages/tokens.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AppTokens } from '../src/apps/tokens/AppTokens'; 4 | 5 | import { withNextJSPerPageLayout } from '~/common/layout/withLayout'; 6 | 7 | 8 | export default withNextJSPerPageLayout({ type: 'optima' }, () => ); 9 | -------------------------------------------------------------------------------- /pages/workspace.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AppPlaceholder } from '../src/apps/AppPlaceholder'; 4 | 5 | import { withNextJSPerPageLayout } from '~/common/layout/withLayout'; 6 | 7 | 8 | export default withNextJSPerPageLayout({ type: 'optima' }, () => ); 9 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/card-dark-1200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/icons/card-dark-1200.png -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/icon-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/icons/icon-1024x1024.png -------------------------------------------------------------------------------- /public/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/icons/icon-512x512.png -------------------------------------------------------------------------------- /public/icons/icon-call-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/icons/icon-call-96x96.png -------------------------------------------------------------------------------- /public/icons/icon-voicechat-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/icons/icon-voicechat-96x96.png -------------------------------------------------------------------------------- /public/images/covers/release-cover-v1.12.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/images/covers/release-cover-v1.12.0.png -------------------------------------------------------------------------------- /public/images/covers/release-cover-v1.13.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/images/covers/release-cover-v1.13.0.png -------------------------------------------------------------------------------- /public/images/covers/release-cover-v1.14.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/images/covers/release-cover-v1.14.0.png -------------------------------------------------------------------------------- /public/images/covers/release-cover-v1.15.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/images/covers/release-cover-v1.15.0.png -------------------------------------------------------------------------------- /public/images/covers/release-cover-v1.16.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/images/covers/release-cover-v1.16.0.png -------------------------------------------------------------------------------- /public/images/explainers/explainer-beam-gather-1600px-alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/images/explainers/explainer-beam-gather-1600px-alpha.png -------------------------------------------------------------------------------- /public/images/explainers/explainer-beam-scatter-1200px-alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/images/explainers/explainer-beam-scatter-1200px-alpha.png -------------------------------------------------------------------------------- /public/images/personas/dev_preview_icon_120x120.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/images/personas/dev_preview_icon_120x120.webp -------------------------------------------------------------------------------- /public/sounds/chat-begin.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/sounds/chat-begin.mp3 -------------------------------------------------------------------------------- /public/sounds/chat-end.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/sounds/chat-end.mp3 -------------------------------------------------------------------------------- /public/sounds/chat-ringtone.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/sounds/chat-ringtone.mp3 -------------------------------------------------------------------------------- /public/sounds/mic-off-mid.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/sounds/mic-off-mid.mp3 -------------------------------------------------------------------------------- /public/sounds/mic-off.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/public/sounds/mic-off.mp3 -------------------------------------------------------------------------------- /src/apps/AppPlaceholder.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Box, Typography } from '@mui/joy'; 4 | 5 | import { capitalizeFirstLetter } from '~/common/util/textUtils'; 6 | import { useRouterRoute } from '~/common/app.routes'; 7 | 8 | 9 | /** 10 | * https://github.com/enricoros/big-AGI/issues/299 11 | */ 12 | export function AppPlaceholder(props: { 13 | title?: string | null, 14 | text?: React.ReactNode, 15 | children?: React.ReactNode, 16 | }) { 17 | 18 | // external state 19 | const route = useRouterRoute(); 20 | 21 | // derived state 22 | const placeholderAppName = props.title || capitalizeFirstLetter(route.replace('/', '') || 'Home'); 23 | 24 | return ( 25 | 31 | 32 | {(props.title !== null || !!props.text) && ( 33 | 39 | 40 | 41 | {placeholderAppName} 42 | 43 | {!!props.text && ( 44 | 45 | {props.text} 46 | 47 | )} 48 | 49 | 50 | )} 51 | 52 | {props.children} 53 | 54 | 55 | ); 56 | } -------------------------------------------------------------------------------- /src/apps/AppSmallContainer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Box, Container, Typography } from '@mui/joy'; 4 | 5 | 6 | export function AppSmallContainer({ title, description, children }: { 7 | title: string; 8 | description: React.ReactNode; 9 | children: React.ReactNode; 10 | }) { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | {title} 18 | {description} 19 | 20 | 21 | {children} 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/apps/call/components/CallAvatar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Avatar, Box } from '@mui/joy'; 4 | 5 | import { animationScalePulse } from '~/common/util/animUtils'; 6 | 7 | 8 | export function CallAvatar(props: { symbol: string, imageUrl?: string, isRinging?: boolean, onClick: () => void }) { 9 | return ( 10 | 20 | 21 | {/* As fallback, show the large Persona Symbol */} 22 | {!props.imageUrl && ( 23 | 30 | {props.symbol} 31 | 32 | )} 33 | 34 | 35 | ); 36 | } -------------------------------------------------------------------------------- /src/apps/call/components/CallButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { ColorPaletteProp, FormControl, IconButton, Typography, VariantProp } from '@mui/joy'; 4 | import { SxProps } from '@mui/joy/styles/types'; 5 | 6 | 7 | /** 8 | * Large button to operate the call, e.g. 9 | * -------- 10 | * | 🎤 | 11 | * | Mute | 12 | * -------- 13 | */ 14 | export function CallButton(props: { 15 | Icon: React.FC, text: string, 16 | variant?: VariantProp, color?: ColorPaletteProp, disabled?: boolean, 17 | onClick?: () => void, 18 | sx?: SxProps, 19 | }) { 20 | return ( 21 | !props.disabled && props.onClick?.()} 23 | sx={{ 24 | display: 'flex', flexDirection: 'column', alignItems: 'center', 25 | gap: { xs: 1, md: 2 }, 26 | }} 27 | > 28 | 29 | 40 | 41 | 42 | 43 | 44 | {props.text} 45 | 46 | 47 | 48 | ); 49 | } -------------------------------------------------------------------------------- /src/apps/call/components/CallMessage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Chip, ColorPaletteProp, VariantProp } from '@mui/joy'; 4 | import { SxProps } from '@mui/joy/styles/types'; 5 | 6 | import type { DMessage } from '~/common/stores/chat/chat.message'; 7 | 8 | 9 | export function CallMessage(props: { 10 | text?: string | React.JSX.Element, 11 | variant?: VariantProp, color?: ColorPaletteProp, 12 | role: DMessage['role'], 13 | sx?: SxProps, 14 | }) { 15 | const isUserMessage = props.role === 'user'; 16 | return ( 17 | 34 | 35 | {props.text} 36 | 37 | 38 | ); 39 | } -------------------------------------------------------------------------------- /src/apps/call/state/store-app-call.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | import { persist } from 'zustand/middleware'; 3 | 4 | 5 | // Call settings 6 | 7 | interface AppCallStore { 8 | 9 | grayUI: boolean; 10 | toggleGrayUI: () => void; 11 | 12 | showConversations: boolean; 13 | toggleShowConversations: () => void; 14 | 15 | showSupport: boolean; 16 | toggleShowSupport: () => void; 17 | 18 | } 19 | 20 | export const useAppCallStore = create()(persist( 21 | (_set, _get) => ({ 22 | 23 | grayUI: false, 24 | toggleGrayUI: () => _set(state => ({ grayUI: !state.grayUI })), 25 | 26 | showConversations: true, 27 | toggleShowConversations: () => _set(state => ({ showConversations: !state.showConversations })), 28 | 29 | showSupport: true, 30 | toggleShowSupport: () => _set(state => ({ showSupport: !state.showSupport })), 31 | 32 | }), { 33 | name: 'app-app-call', 34 | }, 35 | )); 36 | -------------------------------------------------------------------------------- /src/apps/call/state/useMockPersonas.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { usePurposeStore } from '../../chat/components/persona-selector/store-purposes'; 4 | 5 | import { SystemPurposeData, SystemPurposeId, SystemPurposes } from '../../../data'; 6 | 7 | 8 | /** 9 | * This is a 'mock' persona because Soon we'll have real personas definitions 10 | * and stores. Until then, we just mimic a reactive system here. 11 | */ 12 | export interface MockPersona extends SystemPurposeData { 13 | personaId: SystemPurposeId, 14 | } 15 | 16 | export function useMockPersonas(): { personas: MockPersona[], personaIDs: SystemPurposeId[] } { 17 | // only react to hiddenPurposeIDs changes 18 | const hiddenPurposeIDs = usePurposeStore(state => state.hiddenPurposeIDs); 19 | 20 | // Dependency array is empty because SystemPurposes is constant 21 | return React.useMemo(() => { 22 | const personaIDs = Object.keys(SystemPurposes) as SystemPurposeId[]; 23 | const personas = personaIDs 24 | .filter((key) => !hiddenPurposeIDs.includes(key)) 25 | .map((key) => ({ 26 | ...SystemPurposes[key as SystemPurposeId], 27 | personaId: key as SystemPurposeId, 28 | })); 29 | return { personas, personaIDs }; 30 | }, [hiddenPurposeIDs]); 31 | } -------------------------------------------------------------------------------- /src/apps/chat/commands/CommandsAlter.tsx: -------------------------------------------------------------------------------- 1 | import ClearIcon from '@mui/icons-material/Clear'; 2 | 3 | import type { ICommandsProvider } from './ICommandsProvider'; 4 | 5 | export const CommandsAlter: ICommandsProvider = { 6 | id: 'cmd-chat-alter', 7 | rank: 25, 8 | 9 | getCommands: () => [{ 10 | primary: '/assistant', 11 | alternatives: ['/a'], 12 | arguments: ['text...'], 13 | description: 'Injects assistant response', 14 | }, { 15 | primary: '/system', 16 | alternatives: ['/s'], 17 | arguments: ['text...'], 18 | description: 'Injects system message', 19 | }, { 20 | primary: '/clear', 21 | arguments: ['all'], 22 | description: 'Clears the chat (removes all messages)', 23 | Icon: ClearIcon, 24 | }], 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /src/apps/chat/commands/CommandsDraw.tsx: -------------------------------------------------------------------------------- 1 | import FormatPaintTwoToneIcon from '@mui/icons-material/FormatPaintTwoTone'; 2 | 3 | import type { ICommandsProvider } from './ICommandsProvider'; 4 | 5 | export function textToDrawCommand(text: string): string { 6 | return `/draw ${text}`; 7 | } 8 | 9 | export const CommandsDraw: ICommandsProvider = { 10 | id: 'cmd-ass-t2i', 11 | rank: 10, 12 | 13 | getCommands: () => [{ 14 | primary: '/draw', 15 | alternatives: ['/imagine', '/img'], 16 | arguments: ['prompt'], 17 | description: 'Assistant will draw the text', 18 | Icon: FormatPaintTwoToneIcon, 19 | }], 20 | 21 | }; 22 | -------------------------------------------------------------------------------- /src/apps/chat/commands/CommandsHelp.tsx: -------------------------------------------------------------------------------- 1 | import type { ICommandsProvider } from './ICommandsProvider'; 2 | 3 | export const CommandsHelp: ICommandsProvider = { 4 | id: 'cmd-help', 5 | rank: 99, 6 | 7 | getCommands: () => [{ 8 | primary: '/help', 9 | alternatives: ['/?'], 10 | description: 'Display this list of commands', 11 | }], 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /src/apps/chat/commands/CommandsReact.tsx: -------------------------------------------------------------------------------- 1 | import PsychologyIcon from '@mui/icons-material/Psychology'; 2 | 3 | import type { ICommandsProvider } from './ICommandsProvider'; 4 | 5 | export const CommandsReact: ICommandsProvider = { 6 | id: 'cmd-mode-react', 7 | rank: 15, 8 | 9 | getCommands: () => [{ 10 | primary: '/react', 11 | arguments: ['prompt'], 12 | description: 'Use the AI ReAct strategy to answer your query', 13 | Icon: PsychologyIcon, 14 | }], 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/apps/chat/commands/ICommandsProvider.ts: -------------------------------------------------------------------------------- 1 | import type { FunctionComponent } from 'react'; 2 | import type { CommandsProviderId } from './commands.registry'; 3 | 4 | 5 | export interface ChatCommand { 6 | primary: string; // The primary command 7 | alternatives?: string[]; // Alternative commands 8 | arguments?: string[]; // Arguments for the command 9 | description: string; // Description of what the command does 10 | // usage?: string; // Example of how to use the command 11 | Icon?: FunctionComponent; // Icon to display next to the command 12 | } 13 | 14 | 15 | export interface ICommandsProvider { 16 | id: CommandsProviderId; // Unique identifier for the command provider 17 | rank: number; // Rank of the provider, used to sort the providers in the UI 18 | 19 | // Function to get commands with their alternatives and details 20 | getCommands: () => ChatCommand[]; 21 | 22 | // Function to execute a command with optional parameters 23 | // executeCommand: (command: string, params?: string[]) => Promise; 24 | } 25 | -------------------------------------------------------------------------------- /src/apps/chat/commands/commands.dmessage.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/src/apps/chat/commands/commands.dmessage.ts -------------------------------------------------------------------------------- /src/apps/chat/components/composer/actile/ActileProvider.tsx: -------------------------------------------------------------------------------- 1 | import type { FunctionComponent } from 'react'; 2 | 3 | export interface ActileProvider { 4 | 5 | // Unique key for the provider 6 | readonly key: 'pcmd' | 'pstrmsg' | 'pattlbl'; 7 | 8 | // Label for display 9 | get label(): string; 10 | 11 | // Interface for the provider 12 | fastCheckTriggerText: (trailingText: string) => boolean; 13 | fetchItems: () => ActileProviderItems; 14 | onItemSelect: (item: ActileItem) => void; 15 | 16 | } 17 | 18 | export type ActileProviderItems = Promise<{ searchPrefix: string, items: TItem[] }>; 19 | 20 | export interface ActileItem { 21 | key: string; 22 | providerKey: ActileProvider['key']; 23 | label: string; 24 | argument?: string; 25 | description?: string; 26 | Icon?: FunctionComponent; 27 | } 28 | -------------------------------------------------------------------------------- /src/apps/chat/components/composer/actile/providerAttachmentLabels.tsx: -------------------------------------------------------------------------------- 1 | import type { ActileItem, ActileProvider, ActileProviderItems } from './ActileProvider'; 2 | 3 | import type { AttachmentDraftsStoreApi } from '~/common/attachment-drafts/store-attachment-drafts_slice'; 4 | 5 | export interface AttachmentLabelItem extends ActileItem { 6 | // nothing to do do here, this is really just a label 7 | } 8 | 9 | export const providerAttachmentLabels = ( 10 | attachmentsStoreApi: AttachmentDraftsStoreApi | null, 11 | onLabelSelect: (item: ActileItem, searchPrefix: string) => void, 12 | ): ActileProvider => ({ 13 | 14 | key: 'pattlbl', 15 | 16 | get label() { 17 | return 'Attachment Labels'; 18 | }, 19 | 20 | // Uses '@' as the trigger 21 | fastCheckTriggerText: (trailingText: string) => trailingText === '@' || trailingText.endsWith(' @'), 22 | 23 | fetchItems: async (): ActileProviderItems => ({ 24 | searchPrefix: '', 25 | items: attachmentsStoreApi?.getState()?.attachmentDrafts.map(draft => ({ 26 | key: draft.id, 27 | providerKey: 'pattlbl', 28 | label: draft.label, 29 | argument: undefined, 30 | description: 'name', 31 | Icon: undefined, 32 | } as AttachmentLabelItem)) ?? [], 33 | }), 34 | 35 | onItemSelect: item => onLabelSelect(item as AttachmentLabelItem, '@'), 36 | 37 | }); -------------------------------------------------------------------------------- /src/apps/chat/components/composer/actile/providerCommands.tsx: -------------------------------------------------------------------------------- 1 | import { findAllChatCommands } from '../../../commands/commands.registry'; 2 | 3 | import type { ActileItem, ActileProvider, ActileProviderItems } from './ActileProvider'; 4 | 5 | 6 | export const providerCommands = ( 7 | onCommandSelect: (item: ActileItem, searchPrefix: string) => void, 8 | ): ActileProvider => ({ 9 | 10 | key: 'pcmd', 11 | 12 | get label() { 13 | return 'Chat Commands'; 14 | }, 15 | 16 | fastCheckTriggerText: (trailingText: string) => { 17 | // only the literal '/' is a trigger 18 | return trailingText === '/'; 19 | }, 20 | 21 | fetchItems: async (): ActileProviderItems => ({ 22 | searchPrefix: '/', 23 | items: findAllChatCommands().map((cmd) => ({ 24 | key: cmd.primary, 25 | providerKey: 'pcmd', 26 | label: cmd.primary, 27 | argument: cmd.arguments?.join(' ') ?? undefined, 28 | description: cmd.description, 29 | Icon: cmd.Icon, 30 | } satisfies ActileItem)), 31 | }), 32 | 33 | onItemSelect: (item) => onCommandSelect(item as ActileItem, '/'), 34 | 35 | }); -------------------------------------------------------------------------------- /src/apps/chat/components/composer/buttons/ButtonCall.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Box, Button, IconButton, Tooltip } from '@mui/joy'; 4 | import { SxProps } from '@mui/joy/styles/types'; 5 | import CallIcon from '@mui/icons-material/Call'; 6 | 7 | 8 | const callConversationLegend = 9 | 10 | Quick call regarding this chat 11 | ; 12 | 13 | const mobileSx: SxProps = { 14 | mr: { xs: 1, md: 2 }, 15 | } as const; 16 | 17 | const desktopSx: SxProps = { 18 | '--Button-gap': '1rem', 19 | } as const; 20 | 21 | 22 | export const ButtonCallMemo = React.memo(ButtonCall); 23 | 24 | function ButtonCall(props: { isMobile?: boolean, disabled?: boolean, onClick: () => void }) { 25 | return props.isMobile ? ( 26 | 27 | 28 | 29 | ) : ( 30 | 31 | 34 | 35 | ); 36 | } -------------------------------------------------------------------------------- /src/apps/chat/components/composer/buttons/ButtonMicContinuation.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Box, IconButton, Tooltip } from '@mui/joy'; 4 | import { ColorPaletteProp, SxProps, VariantProp } from '@mui/joy/styles/types'; 5 | import RepeatIcon from '@mui/icons-material/Repeat'; 6 | import RepeatOnIcon from '@mui/icons-material/RepeatOn'; 7 | 8 | const micContinuationLegend = 9 | 10 | Voice Continuation 11 | ; 12 | 13 | 14 | export const ButtonMicContinuationMemo = React.memo(ButtonMicContinuation); 15 | 16 | function ButtonMicContinuation(props: { isActive: boolean, variant: VariantProp, color: ColorPaletteProp, onClick: () => void, sx?: SxProps }) { 17 | return 18 | 19 | {props.isActive ? : } 20 | 21 | ; 22 | } -------------------------------------------------------------------------------- /src/apps/chat/components/composer/buttons/ButtonOptionsDraw.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Button, IconButton } from '@mui/joy'; 4 | import { SxProps } from '@mui/joy/styles/types'; 5 | import FormatPaintTwoToneIcon from '@mui/icons-material/FormatPaintTwoTone'; 6 | 7 | import { PhSlidersHorizontalIcon } from '~/common/components/icons/phosphor/PhSlidersHorizontalIcon'; 8 | 9 | 10 | export function ButtonOptionsDraw(props: { isMobile?: boolean, onClick: () => void, sx?: SxProps }) { 11 | return props.isMobile ? ( 12 | 13 | 14 | 15 | ) : ( 16 | 19 | ); 20 | } -------------------------------------------------------------------------------- /src/apps/chat/components/layout-bar/ChatBarDropdowns.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { DConversationId } from '~/common/stores/chat/chat.conversation'; 4 | import type { OptimaBarControlMethods } from '~/common/layout/optima/bar/OptimaBarDropdown'; 5 | 6 | import { useChatLLMDropdown } from './useLLMDropdown'; 7 | import { usePersonaIdDropdown } from './usePersonaDropdown'; 8 | import { useFolderDropdown } from './useFolderDropdown'; 9 | 10 | 11 | export function ChatBarDropdowns(props: { 12 | conversationId: DConversationId | null; 13 | llmDropdownRef: React.Ref; 14 | personaDropdownRef: React.Ref; 15 | }) { 16 | 17 | // state 18 | const { chatLLMDropdown } = useChatLLMDropdown(props.llmDropdownRef); 19 | const { personaDropdown } = usePersonaIdDropdown(props.conversationId, props.personaDropdownRef); 20 | const { folderDropdown } = useFolderDropdown(props.conversationId); 21 | 22 | return <> 23 | 24 | {/* Persona selector */} 25 | {personaDropdown} 26 | 27 | {/* Model selector */} 28 | {chatLLMDropdown} 29 | 30 | {/* Folder selector */} 31 | {folderDropdown} 32 | 33 | ; 34 | } 35 | -------------------------------------------------------------------------------- /src/apps/chat/components/message/ChatMessage.styles.ts: -------------------------------------------------------------------------------- 1 | import type { SxProps } from '@mui/joy/styles/types'; 2 | 3 | import { animationColorRainbow } from '~/common/util/animUtils'; 4 | 5 | 6 | export const messageAsideColumnSx: SxProps = { 7 | // make this stick to the top of the screen 8 | position: 'sticky', 9 | top: '0.25rem', 10 | 11 | // style 12 | // filter: 'url(#agi-holographic)', 13 | 14 | // flexBasis: 0, // this won't let the item grow 15 | minWidth: { xs: 50, md: 64 }, 16 | maxWidth: 80, 17 | textAlign: 'center', 18 | // layout 19 | display: 'flex', 20 | flexDirection: 'column', 21 | alignItems: 'center', 22 | gap: 0.25, // 2024-08-24: added, space the avatar icon from the label 23 | 24 | // when with the 'edit-button' class 25 | '&.msg-edit-button': { 26 | gap: 0.25, 27 | }, 28 | }; 29 | 30 | export const messageZenAsideColumnSx: SxProps = { 31 | ...messageAsideColumnSx, 32 | minWidth: undefined, 33 | maxWidth: undefined, 34 | mx: -1, 35 | }; 36 | 37 | export const messageAvatarLabelSx: SxProps = { 38 | overflowWrap: 'anywhere', 39 | }; 40 | 41 | export const messageAvatarLabelAnimatedSx: SxProps = { 42 | animation: `${animationColorRainbow} 5s linear infinite`, 43 | // Extra hinting... but looks weird 44 | // fontStyle: 'italic', 45 | }; 46 | -------------------------------------------------------------------------------- /src/apps/chat/components/message/fragments-content/ViewImageRefPartModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { SxProps } from '@mui/joy/styles/types'; 4 | import { Box } from '@mui/joy'; 5 | 6 | import type { DMessageImageRefPart } from '~/common/stores/chat/chat.fragments'; 7 | import { GoodModal } from '~/common/components/modals/GoodModal'; 8 | 9 | import { BlockPartImageRef } from './BlockPartImageRef'; 10 | 11 | 12 | const imageViewerModalSx: SxProps = { 13 | maxWidth: '90vw', 14 | backgroundColor: 'background.level2', 15 | }; 16 | 17 | const imageViewerContainerSx: SxProps = { 18 | // display: 'flex', 19 | // alignItems: 'center', 20 | // justifyContent: 'center', 21 | maxHeight: '80vh', 22 | overflow: 'auto', 23 | 24 | // pre-compensate the Block > Render Items 1.5 margin 25 | m: -1.5, 26 | '& > div': { 27 | pt: 1.5, 28 | }, 29 | }; 30 | 31 | 32 | export function ViewImageRefPartModal(props: { imageRefPart: DMessageImageRefPart, onClose: () => void }) { 33 | const title = props.imageRefPart.altText || 'Attachment Image'; 34 | return ( 35 | 42 | 43 | 44 | 45 | 46 | ); 47 | } -------------------------------------------------------------------------------- /src/apps/chat/components/message/in-reference-to/InReferenceToList.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { SxProps } from '@mui/joy/styles/types'; 4 | import { Box } from '@mui/joy'; 5 | 6 | import type { DMetaReferenceItem } from '~/common/stores/chat/chat.message'; 7 | 8 | import { InReferenceToBubble } from './InReferenceToBubble'; 9 | 10 | 11 | const inReferenceToGroupSx: SxProps = { 12 | display: 'flex', 13 | flexDirection: 'column', 14 | gap: 1, 15 | }; 16 | 17 | 18 | export function InReferenceToList(props: { items: DMetaReferenceItem[] }) { 19 | return ( 20 | 21 | {props.items.map((item, index) => ( 22 | 27 | ))} 28 | 29 | ); 30 | } -------------------------------------------------------------------------------- /src/apps/chat/execute-mode/execute-mode.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Mode: how to treat the input from the Composer 3 | * Was: ChatModeId 4 | */ 5 | export type ChatExecuteMode = 6 | | 'append-user' 7 | | 'beam-content' 8 | | 'generate-content' 9 | | 'generate-image' 10 | | 'react-content' 11 | ; 12 | -------------------------------------------------------------------------------- /src/apps/draw/create/ButtonPromptFromX.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Button } from '@mui/joy'; 4 | import InsertPhotoOutlinedIcon from '@mui/icons-material/InsertPhotoOutlined'; 5 | import ChatOutlinedIcon from '@mui/icons-material/ChatOutlined'; 6 | 7 | export function ButtonPromptFromX(props: { isMobile?: boolean, name: string, disabled?: boolean }) { 8 | return props.isMobile ? null : ( 9 | 20 | ); 21 | } -------------------------------------------------------------------------------- /src/apps/draw/create/ZeroDrawConfig.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Button, Card, CardActions, CardContent, Typography } from '@mui/joy'; 4 | 5 | import { optimaOpenPreferences } from '~/common/layout/optima/useOptima'; 6 | 7 | 8 | export function ZeroDrawConfig() { 9 | 10 | const handleConfigureDrawing = React.useCallback(() => { 11 | optimaOpenPreferences('draw'); 12 | }, []); 13 | 14 | return ( 15 | 20 | 21 | 22 | AI Text-to-Image does not seem available.
23 | Please configure one service, such as an OpenAI LLM service, or the Prodia service. 24 |
25 |
26 | 27 | 30 | 31 |
32 | ); 33 | } -------------------------------------------------------------------------------- /src/apps/draw/create/ZeroGenerations.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Card, Typography } from '@mui/joy'; 4 | 5 | 6 | export function ZeroGenerations() { 7 | return ( 8 | 20 | {/**/} 21 | {/* {Brand.Title.Base} Draw*/} 22 | {/**/} 23 | 24 | Generate stunning images from text. 25 | Simply type in an image, drawing, or photo description, and the AI will bring your vision to life. 26 | {/*To get started enter your prompt and hit "Draw".*/} 27 | 28 | 29 | ); 30 | } -------------------------------------------------------------------------------- /src/apps/draw/promptfx/PromptFX.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | THIS FILE IS A PLACEHOLDER for the DRAW App 4 | 5 | import { DConversationId, DMessage } from '~/common/state/store-chats'; 6 | import { Sheet } from '@mui/joy'; 7 | 8 | 9 | export type PromptFXInput = { 10 | origin: { 11 | type: 'app-draw', 12 | singleGenRequestId: SingleGenRequest['id'], 13 | } | { 14 | type: 'chat', 15 | conversationId: DConversationId, 16 | messageId: DMessage['id'], 17 | }, 18 | prompt: string, 19 | } 20 | 21 | interface SingleGenRequest { 22 | id: string, 23 | 24 | } 25 | 26 | interface MultiGenRequest { 27 | requests: SingleGenRequest[], 28 | requestIdx: number | null, 29 | } 30 | 31 | 32 | export type PromptFXOutput = { 33 | input: PromptFXInput, 34 | output: { 35 | promptMatrix: MultiGenRequest, 36 | } 37 | } 38 | 39 | interface IPromptFX { 40 | 41 | onCancel: () => void, 42 | onDone: (output: PromptFXOutput) => void, 43 | 44 | } 45 | 46 | function PromptFX(props: {}) { 47 | 48 | return <> 49 | 50 | 51 | a 52 | 53 | 54 | ; 55 | } 56 | 57 | 58 | const usePromptFX = (input: PromptFXInput) => { 59 | 60 | 61 | 62 | return { 63 | test: 3, 64 | PromptFX, 65 | }; 66 | }; 67 | */ -------------------------------------------------------------------------------- /src/apps/draw/queue-draw-create.tsx: -------------------------------------------------------------------------------- 1 | import { ItemAsyncWorker, ProcessingQueue } from '~/common/logic/ProcessingQueue'; 2 | 3 | import type { DesignerPrompt } from './create/PromptComposer'; 4 | import { t2iGenerateImageContentFragments } from '~/modules/t2i/t2i.client'; 5 | 6 | /** 7 | * This function needs to create a new image (saved as an DBlob asset) based on the inputs. 8 | */ 9 | const drawCreateWorker: ItemAsyncWorker = async (item, _update, signal) => { 10 | await t2iGenerateImageContentFragments(null, item.prompt, [], item._repeatCount, 'global', 'app-draw').catch(console.error); 11 | return item; 12 | }; 13 | 14 | export class DrawCreateQueue extends ProcessingQueue { 15 | constructor() { 16 | super(4, 10, drawCreateWorker); 17 | } 18 | } 19 | 20 | /** 21 | * The single drawing queue for the draw app: keeps running background jobs until done or canceled 22 | */ 23 | export const drawCreateQueue = new DrawCreateQueue(); 24 | -------------------------------------------------------------------------------- /src/apps/personas/AppPersonas.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Box, Container, ListDivider, Typography } from '@mui/joy'; 4 | 5 | import { OptimaDrawerIn } from '~/common/layout/optima/portals/OptimaPortalsIn'; 6 | 7 | import { Creator } from './creator/Creator'; 8 | import { CreatorDrawer } from './creator/CreatorDrawer'; 9 | import { Viewer } from './creator/Viewer'; 10 | 11 | 12 | export function AppPersonas() { 13 | 14 | // state 15 | const [selectedSimplePersonaId, setSelectedSimplePersonaId] = React.useState(null); 16 | 17 | return <> 18 | 19 | {/* -> Drawer */} 20 | 21 | 25 | 26 | 27 | 32 | 33 | 34 | 35 | 36 | AI Personas Creator 37 | 38 | 39 | 40 | 41 | {!!selectedSimplePersonaId && } 42 | 43 | 44 | 45 | 46 | 47 | 48 | ; 49 | } -------------------------------------------------------------------------------- /src/apps/settings-modal/settings-ui/SettingUIComplexity.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useShallow } from 'zustand/react/shallow'; 3 | 4 | import type { UIComplexityMode } from '~/common/app.theme'; 5 | import { FormSelectControl, FormSelectOption } from '~/common/components/forms/FormSelectControl'; 6 | import { useUIPreferencesStore } from '~/common/stores/store-ui'; 7 | 8 | 9 | const AppearanceOptions: FormSelectOption[] = [ 10 | { value: 'minimal', label: 'Minimal', description: 'Clean' }, 11 | { value: 'pro', label: 'Pro (default)', description: 'Perfect' }, 12 | { value: 'extra', label: 'Extra', description: 'GIFs & more.' }, 13 | ]; 14 | 15 | export function SettingUIComplexity(props: { noLabel?: boolean }) { 16 | 17 | // external state 18 | const [complexityMode, setComplexityMode] = useUIPreferencesStore(useShallow(state => [state.complexityMode, state.setComplexityMode])); 19 | 20 | return ( 21 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/apps/settings-modal/settings-ui/SettingUIComposerQuickButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useShallow } from 'zustand/react/shallow'; 3 | 4 | import { FormSelectControl, FormSelectOption } from '~/common/components/forms/FormSelectControl'; 5 | import { useUIPreferencesStore } from '~/common/stores/store-ui'; 6 | 7 | 8 | const QuickOptions: FormSelectOption<'off' | 'call' | 'beam'>[] = [ 9 | { value: 'beam', label: 'Beam', description: 'Beam it' }, 10 | { value: 'call', label: 'Call', description: 'Call Persona' }, 11 | { value: 'off', label: 'Off', description: 'Hide' }, 12 | ]; 13 | 14 | export function SettingUIComposerQuickButton(props: { noLabel?: boolean }) { 15 | 16 | // external state 17 | const [composerQuickButton, setComposerQuickButton] = useUIPreferencesStore(useShallow(state => [state.composerQuickButton, state.setComposerQuickButton])); 18 | 19 | return ( 20 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/common/app.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Application Identity (Brand) 3 | * 4 | * Also note that the 'Brand' is used in the following places: 5 | * - README.md all over 6 | * - package.json app-slug and version 7 | * - [public/manifest.json] name, short_name, description, theme_color, background_color 8 | */ 9 | export const Brand = { 10 | Title: { 11 | Base: 'big-AGI', 12 | Common: (process.env.NODE_ENV === 'development' ? '[DEV] ' : '') + 'big-AGI', 13 | }, 14 | Meta: { 15 | Description: 'Launch big-AGI to unlock the full potential of AI, with precise control over your data and models. Voice interface, AI personas, advanced features, and fun UX.', 16 | SiteName: 'big-AGI | Precision AI for You', 17 | ThemeColor: '#32383E', 18 | TwitterSite: '@enricoros', 19 | }, 20 | URIs: { 21 | Home: 'https://big-agi.com', 22 | // App: 'https://get.big-agi.com', 23 | CardImage: 'https://big-agi.com/icons/card-dark-1200.png', 24 | OpenRepo: 'https://github.com/enricoros/big-agi', 25 | OpenProject: 'https://github.com/users/enricoros/projects/4', 26 | SupportInvite: 'https://discord.gg/MkH4qj2Jp9', 27 | // Twitter: 'https://www.twitter.com/enricoros', 28 | PrivacyPolicy: 'https://big-agi.com/privacy', 29 | TermsOfService: 'https://big-agi.com/terms', 30 | }, 31 | Docs: { 32 | Public: (docPage: string) => `https://big-agi.com/docs/${docPage}`, 33 | } 34 | } as const; -------------------------------------------------------------------------------- /src/common/app.queryclient.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from '@tanstack/react-query'; 2 | 3 | 4 | let queryClient: QueryClient | null = null; 5 | 6 | export function reactQueryClientSingleton(): QueryClient { 7 | if (!queryClient) { 8 | queryClient = new QueryClient({ 9 | defaultOptions: { 10 | queries: { 11 | retry: false, 12 | // call functions even when the network is disconnected; this makes 127.0.0.1 work, while probably not causing other issues 13 | networkMode: 'always', 14 | refetchOnWindowFocus: false, 15 | }, 16 | mutations: { 17 | retry: false, 18 | networkMode: 'always', 19 | }, 20 | }, 21 | }); 22 | } 23 | return queryClient; 24 | } 25 | -------------------------------------------------------------------------------- /src/common/attachment-drafts/attachment.livefile.ts: -------------------------------------------------------------------------------- 1 | import { liveFileCreateOrThrow } from '~/common/livefile/store-live-file'; 2 | 3 | import type { AttachmentDraftSource } from './attachment.types'; 4 | 5 | 6 | /** Checks if the source supports LiveFile (usually attached Files with drag/drop) */ 7 | export function attachmentSourceSupportsLiveFile(source: AttachmentDraftSource): boolean { 8 | return source.media === 'file' && !!source.fileWithHandle.handle && typeof source.fileWithHandle.handle.getFile === 'function'; 9 | } 10 | 11 | /** Get the ID to a LiveFile (create one if needed) */ 12 | export async function attachmentGetLiveFileId(source: AttachmentDraftSource) { 13 | // only files that came with a FileSystemFileHandle are supported 14 | if (!attachmentSourceSupportsLiveFile(source) || source.media !== 'file' || !source.fileWithHandle.handle) 15 | return undefined; 16 | 17 | // new or recycled 18 | return await liveFileCreateOrThrow(source.fileWithHandle.handle).catch(console.error) || undefined; 19 | } 20 | -------------------------------------------------------------------------------- /src/common/attachment-drafts/file-converters/DocxToMarkdown.ts: -------------------------------------------------------------------------------- 1 | import { convertToHtml, images } from 'mammoth'; 2 | 3 | 4 | export async function convertDocxToHTML(input: ArrayBuffer): Promise<{ html: string }> { 5 | try { 6 | // Dynamically import mammoth 7 | const result = await convertToHtml({ arrayBuffer: input }, { 8 | convertImage: images.imgElement(function ignoreImage(image) { 9 | throw new Error('Images are not supported in DOCX to Markdown conversion'); 10 | }), 11 | }); 12 | if (result.messages?.length) { 13 | console.log('Messages from DOCX to Markdown conversion:', result.messages); 14 | } 15 | return { 16 | html: result.value, 17 | }; 18 | } catch (error) { 19 | console.error('Error converting DOCX to Markdown:', error); 20 | throw error; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/common/attachment-drafts/store-attachment-drafts_vanilla.ts: -------------------------------------------------------------------------------- 1 | import { createStore as createVanillaStore } from 'zustand/vanilla'; 2 | 3 | import { AttachmentDraftsStoreApi, AttachmentsDraftsStore, createAttachmentDraftsStoreSlice } from './store-attachment-drafts_slice'; 4 | 5 | 6 | export const createAttachmentDraftsVanillaStore = (): AttachmentDraftsStoreApi => createVanillaStore()((...a) => ({ 7 | 8 | // Attachments: attachment drafts 9 | ...createAttachmentDraftsStoreSlice(...a), 10 | 11 | })); 12 | 13 | 14 | // const _fallbackStoreApi = createPerChatVanillaStore(); 15 | 16 | // // usages: useAttachmentDrafts 17 | // export const useChatAttachmentsStore = (vanillaStore: Readonly | null, selector: (store: AttachmentsDraftsStore) => T): T => 18 | // useStore(vanillaStore || fallbackStoreApi, selector); 19 | -------------------------------------------------------------------------------- /src/common/chat-overlay/store-perchat-variform_slice.ts: -------------------------------------------------------------------------------- 1 | import type { StateCreator } from 'zustand/vanilla'; 2 | 3 | 4 | /// Chat Overlay Store: per-chat overlay state /// 5 | 6 | interface VariformOverlayState { 7 | 8 | variformValues: Record; 9 | 10 | } 11 | 12 | export interface VariformOverlayStore extends VariformOverlayState { 13 | 14 | setVariformValue: (key: string, value: string) => void; 15 | clearVariformValue: (key: string) => void; 16 | 17 | } 18 | 19 | 20 | export const createVariformOverlayStoreSlice: StateCreator = (_set, _get) => ({ 21 | 22 | // init state 23 | variformValues: {}, 24 | 25 | // actions 26 | setVariformValue: (key, value) => _set(state => ({ 27 | variformValues: { ...state.variformValues, [key]: value }, 28 | })), 29 | clearVariformValue: (key) => _set(state => { 30 | const { [key]: _, ...rest } = state.variformValues; 31 | return { variformValues: rest }; 32 | }), 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /src/common/components/AlreadySet.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Typography } from '@mui/joy'; 4 | 5 | import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; 6 | 7 | 8 | export function AlreadySet(props: { required?: boolean }) { 9 | return ( 10 | }> 11 | {/*Installed Already*/} 12 | {props.required ? 'required' : 'Already set on server'} 13 | 14 | ); 15 | } -------------------------------------------------------------------------------- /src/common/components/AvatarDomainFavicon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Avatar } from '@mui/joy'; 4 | 5 | import { urlExtractDomain } from '~/common/util/urlUtils'; 6 | 7 | 8 | export function AvatarDomainFavicon(props: { 9 | url: string; 10 | size?: number; 11 | iconRes?: number; 12 | noHover?: boolean; 13 | noShadow?: boolean; 14 | }) { 15 | const { url, size = 16, iconRes = 32, noShadow } = props; 16 | 17 | const domain = !url ? '' : urlExtractDomain(url); 18 | 19 | // using Google's favicon service 20 | const faviconUrl = !domain?.length ? undefined : `https://www.google.com/s2/favicons?domain=${domain}&sz=${iconRes}`; 21 | 22 | return ( 23 | 45 | {(domain || '').charAt(0).toUpperCase()} 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/common/components/ChipExpander.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Chip, chipClasses } from '@mui/joy'; 4 | import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; 5 | 6 | 7 | export const chipExpanderSx = { 8 | px: 1.5, 9 | [`& .${chipClasses.endDecorator}`]: { 10 | transition: 'transform 0.2s', 11 | } as const, 12 | [`&[aria-expanded='true'] .${chipClasses.endDecorator}`]: { 13 | transform: 'rotate(-180deg)', 14 | } as const, 15 | } as const; 16 | 17 | 18 | export function ChipExpander(props: { 19 | text: React.ReactNode, 20 | expanded: boolean, 21 | size?: 'sm' | 'md' | 'lg', 22 | onToggleExpanded?: () => void 23 | }) { 24 | return ( 25 | } 30 | aria-expanded={props.expanded} 31 | sx={chipExpanderSx} 32 | > 33 | {props.text} 34 | 35 | ); 36 | } -------------------------------------------------------------------------------- /src/common/components/ChipToggleButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { VariantProp } from '@mui/joy/styles/types'; 4 | import { Chip } from '@mui/joy'; 5 | import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; 6 | 7 | 8 | const _chipTBSx = { 9 | px: 1.5, 10 | // [`&[aria-checked='true'] .${chipClasses.endDecorator}`]: { 11 | // transform: 'rotate(-180deg)', 12 | // } as const, 13 | } as const; 14 | 15 | 16 | export function ChipToggleButton(props: { 17 | text: React.ReactNode, 18 | active?: boolean, 19 | disabled?: boolean, 20 | size?: 'sm' | 'md' | 'lg', 21 | showCollapseCaret?: boolean, 22 | variant?: VariantProp, 23 | onClick?: () => void 24 | }) { 25 | return ( 26 | : undefined} 32 | aria-checked={props.active} 33 | sx={_chipTBSx} 34 | > 35 | {props.text} 36 | 37 | ); 38 | } -------------------------------------------------------------------------------- /src/common/components/DarkModeToggleButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Button, IconButton, useColorScheme } from '@mui/joy'; 4 | import DarkModeIcon from '@mui/icons-material/DarkMode'; 5 | import LightModeIcon from '@mui/icons-material/LightMode'; 6 | 7 | export const darkModeToggleButtonSx = { 8 | boxShadow: 'sm', 9 | backgroundColor: 'background.surface', 10 | '&:hover': { 11 | backgroundColor: 'background.popup', 12 | }, 13 | } as const; 14 | 15 | export function DarkModeToggleButton(props: { hasText?: boolean }) { 16 | 17 | // external state 18 | const { mode: colorMode, setMode: setColorMode } = useColorScheme(); 19 | 20 | const handleToggleDarkMode = (event: React.MouseEvent) => { 21 | event.stopPropagation(); 22 | setColorMode(colorMode === 'dark' ? 'light' : 'dark'); 23 | }; 24 | 25 | return props.hasText ? ( 26 | 35 | ) : ( 36 | 37 | {colorMode !== 'dark' ? : } 38 | 39 | ); 40 | } -------------------------------------------------------------------------------- /src/common/components/ExpanderControlledBox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { SxProps } from '@mui/joy/styles/types'; 4 | import { Box, styled } from '@mui/joy'; 5 | 6 | 7 | const BoxCollapser = styled(Box)({ 8 | display: 'grid', 9 | transition: 'grid-template-rows 0.2s cubic-bezier(.17,.84,.44,1)', 10 | gridTemplateRows: '0fr', 11 | '&[aria-expanded="true"]': { 12 | gridTemplateRows: '1fr', 13 | }, 14 | }); 15 | 16 | const BoxCollapsee = styled(Box)({ 17 | overflow: 'hidden', 18 | }); 19 | 20 | 21 | export function ExpanderControlledBox(props: { expanded: boolean, children: React.ReactNode, sx?: SxProps }) { 22 | return ( 23 | 24 | 25 | {props.children} 26 | 27 | 28 | ); 29 | } -------------------------------------------------------------------------------- /src/common/components/ExternalDocsLink.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { ColorPaletteProp, TypographySystem } from '@mui/joy/styles/types'; 4 | import { Brand } from '~/common/app.config'; 5 | import { ExternalLink } from '~/common/components/ExternalLink'; 6 | 7 | 8 | export function ExternalDocsLink(props: { 9 | docPage: string; 10 | color?: ColorPaletteProp, 11 | level?: keyof TypographySystem | 'inherit', 12 | highlight?: boolean; 13 | children: React.ReactNode, 14 | }) { 15 | return ( 16 | 23 | {props.children} 24 | 25 | ); 26 | } -------------------------------------------------------------------------------- /src/common/components/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { ColorPaletteProp, SxProps, TypographySystem } from '@mui/joy/styles/types'; 4 | import AutoStoriesOutlinedIcon from '@mui/icons-material/AutoStoriesOutlined'; 5 | import LaunchIcon from '@mui/icons-material/Launch'; 6 | 7 | import { Link } from './Link'; 8 | 9 | 10 | const wowStyle: SxProps = { 11 | textDecoration: 'underline', 12 | textDecorationThickness: '0.4em', 13 | textDecorationColor: 'rgba(var(--joy-palette-primary-lightChannel) / 1)', 14 | // textDecorationColor: 'rgba(0 255 0 / 0.5)', 15 | textDecorationSkipInk: 'none', 16 | // textUnderlineOffset: '-0.5em', 17 | }; 18 | 19 | 20 | export function ExternalLink(props: { 21 | href: string, 22 | color?: ColorPaletteProp, 23 | level?: keyof TypographySystem | 'inherit', 24 | highlight?: boolean, 25 | icon?: 'issue' | 'public-docs', 26 | children: React.ReactNode, 27 | }) { 28 | return ( 29 | 30 | {props.children} {(props.icon === 'issue' || props.icon === 'public-docs') 31 | ? 32 | : 33 | } 34 | 35 | ); 36 | } -------------------------------------------------------------------------------- /src/common/components/GitHubProjectIssueCard.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { SxProps } from '@mui/joy/styles/types'; 4 | import { Box, Card, Link as MuiLink, Typography } from '@mui/joy'; 5 | import GitHubIcon from '@mui/icons-material/GitHub'; 6 | 7 | 8 | export const GitHubProjectIssueCard = (props: { 9 | issue: number, 10 | text: string, 11 | note?: string | React.ReactNode, 12 | note2?: string | React.ReactNode, 13 | sx?: SxProps 14 | }) => 15 | 16 | 17 | 18 | 19 | 20 | big-AGI #{props.issue} 21 | 22 | {' · '}{props.text}. 23 | 24 | 25 | {!!props.note && ( 26 | 27 | {props.note} 28 | 29 | )} 30 | {!!props.note2 && ( 31 | 32 | {props.note2} 33 | 34 | )} 35 | ; -------------------------------------------------------------------------------- /src/common/components/GoodTooltip.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { SxProps } from '@mui/joy/styles/types'; 4 | import { Tooltip } from '@mui/joy'; 5 | 6 | 7 | const defaultSx: SxProps = { 8 | maxWidth: { sm: '50vw', md: '25vw' }, 9 | whiteSpace: 'break-spaces', 10 | }; 11 | 12 | /** 13 | * Tooltip with text that wraps to multiple lines (doesn't go too long) 14 | */ 15 | export const GoodTooltip = (props: { 16 | title: React.ReactNode, 17 | placement?: 'top' | 'bottom' | 'top-start', 18 | isError?: boolean, isWarning?: boolean, 19 | enableInteractive?: boolean, 20 | arrow?: boolean, 21 | variantOutlined?: boolean, 22 | children: React.JSX.Element, 23 | sx?: SxProps 24 | }) => 25 | 34 | {props.children} 35 | ; 36 | -------------------------------------------------------------------------------- /src/common/components/InlineError.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { SxProps } from '@mui/joy/styles/types'; 4 | import { Alert, Typography } from '@mui/joy'; 5 | 6 | export function InlineError(props: { error: Error | React.JSX.Element | null | any, severity?: 'warning' | 'danger' | 'info', sx?: SxProps }) { 7 | const color = props.severity === 'info' ? 'primary' : props.severity || 'warning'; 8 | return ( 9 | 10 | 11 | {props.error?.message || props.error || 'Unknown error'} 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/common/components/LogoProgress.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Image from 'next/image'; 3 | 4 | import { Box, CircularProgress } from '@mui/joy'; 5 | 6 | 7 | /** 8 | * 64x64 logo with a circular progress indicator around it 9 | */ 10 | export function LogoProgress(props: { showProgress: boolean }) { 11 | return 19 | 20 | App Logo 21 | 22 | {props.showProgress && } 23 | ; 24 | } -------------------------------------------------------------------------------- /src/common/components/TooltipOutlined.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { SxProps } from '@mui/joy/styles/types'; 4 | import { Tooltip, TooltipProps } from '@mui/joy'; 5 | 6 | 7 | const largePaneSx: SxProps = { 8 | backgroundColor: 'background.popup', 9 | boxShadow: 'lg', 10 | }; 11 | 12 | 13 | export function TooltipOutlined(props: { 14 | title: React.ReactNode; 15 | color?: TooltipProps['color']; 16 | variant?: TooltipProps['variant']; 17 | placement?: TooltipProps['placement']; 18 | slowEnter?: boolean; 19 | asLargePane?: boolean; 20 | enableInteractive?: boolean; 21 | children: React.JSX.Element; 22 | }) { 23 | return ( 24 | 34 | {props.children} 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/common/components/VideoPlayerVimeo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { VimeoPlayerProps as ReactPlayerVimeoProps } from 'react-player/vimeo'; 4 | 5 | 6 | type VideoPlayerProps = ReactPlayerVimeoProps & { 7 | // make the player responsive 8 | responsive?: boolean; 9 | // set this to not set the full URL 10 | vimeoVideoId?: string; 11 | }; 12 | 13 | const DynamicVimeoPlayer = React.lazy(async () => { 14 | 15 | // dynamically import react-player (saves 7kb but still..) 16 | const { default: ReactPlayerVimeo } = await import('react-player/vimeo'); 17 | 18 | return { 19 | default: (props: ReactPlayerVimeoProps) => { 20 | 21 | const { responsive, vimeoVideoId, ...baseProps } = props; 22 | 23 | // responsive patch 24 | if (responsive) { 25 | baseProps.width = '100%'; 26 | baseProps.height = '100%'; 27 | } 28 | 29 | // Vimeo Video ID 30 | if (vimeoVideoId) 31 | baseProps.url = `https://vimeo.com/${vimeoVideoId}`; 32 | 33 | return ; 34 | }, 35 | }; 36 | }); 37 | 38 | 39 | export function VideoPlayerVimeo(props: VideoPlayerProps) { 40 | return ( 41 | Loading...}> 42 | 43 | 44 | ); 45 | } -------------------------------------------------------------------------------- /src/common/components/VideoPlayerYouTube.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { YouTubePlayerProps as ReactPlayerYouTubeProps } from 'react-player/youtube'; 4 | 5 | 6 | type VideoPlayerProps = ReactPlayerYouTubeProps & { 7 | // make the player responsive 8 | responsive?: boolean; 9 | // set this to not set the full URL 10 | youTubeVideoId?: string; 11 | }; 12 | 13 | const DynamicYouTubePlayer = React.lazy(async () => { 14 | 15 | // dynamically import react-player (saves 7kb but still..) 16 | const { default: ReactPlayerYouTube } = await import('react-player/youtube'); 17 | 18 | return { 19 | default: (props: ReactPlayerYouTubeProps) => { 20 | 21 | const { responsive, youTubeVideoId, ...baseProps } = props; 22 | 23 | // responsive patch 24 | if (responsive) { 25 | baseProps.width = '100%'; 26 | baseProps.height = '100%'; 27 | } 28 | 29 | // YouTube Video ID 30 | if (youTubeVideoId) 31 | baseProps.url = `https://www.youtube.com/watch?v=${youTubeVideoId}`; 32 | 33 | return ; 34 | }, 35 | }; 36 | }); 37 | 38 | 39 | export function VideoPlayerYouTube(props: VideoPlayerProps) { 40 | return ( 41 | Loading...}> 42 | 43 | 44 | ); 45 | } -------------------------------------------------------------------------------- /src/common/components/dnd-dt/volstore-drag-global.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | 4 | interface GlobalDragState { 5 | 6 | // is something dragged over the window 7 | isWindowDragActive: boolean; 8 | 9 | // for potential filtering 10 | dragHasFiles: boolean; 11 | 12 | } 13 | 14 | export const useGlobalDragStore = create((_set) => ({ 15 | 16 | // initial state 17 | 18 | isWindowDragActive: false, 19 | dragHasFiles: false, 20 | 21 | })); 22 | -------------------------------------------------------------------------------- /src/common/components/forms/FormSwitchControl.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { FormControl, Switch } from '@mui/joy'; 4 | 5 | import { FormLabelStart } from './FormLabelStart'; 6 | 7 | 8 | /** 9 | * Switch control 10 | */ 11 | export function FormSwitchControl(props: { 12 | title: string | React.JSX.Element, description?: string | React.JSX.Element, 13 | on?: React.ReactNode, off?: string, fullWidth?: boolean, 14 | checked: boolean, onChange: (on: boolean) => void, 15 | disabled?: boolean, 16 | tooltip?: React.ReactNode, 17 | }) { 18 | return ( 19 | 20 | 21 | props.onChange(event.target.checked)} 24 | endDecorator={props.checked ? props.on || 'On' : props.off || 'Off'} 25 | sx={props.fullWidth ? { flexGrow: 1 } : undefined} 26 | slotProps={{ endDecorator: { sx: { minWidth: 26 } } }} 27 | /> 28 | 29 | ); 30 | } -------------------------------------------------------------------------------- /src/common/components/forms/FormTextField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { SxProps } from '@mui/joy/styles/types'; 4 | import { FormControl, Input } from '@mui/joy'; 5 | 6 | import { FormLabelStart } from './FormLabelStart'; 7 | 8 | 9 | const formControlSx: SxProps = { 10 | flexWrap: 'wrap', 11 | justifyContent: 'space-between', 12 | alignItems: 'center', 13 | }; 14 | 15 | 16 | /** 17 | * Text form field (e.g. enter a host) 18 | */ 19 | export function FormTextField(props: { 20 | autoCompleteId: string, 21 | title: string | React.JSX.Element, 22 | description?: string | React.JSX.Element, 23 | tooltip?: string | React.JSX.Element, 24 | placeholder?: string, isError?: boolean, disabled?: boolean, 25 | value: string | undefined, onChange: (text: string) => void, 26 | }) { 27 | const acId = 'text-' + props.autoCompleteId; 28 | return ( 29 | 35 | 36 | props.onChange(event.target.value)} 42 | sx={{ flexGrow: 1 }} 43 | /> 44 | 45 | ); 46 | } -------------------------------------------------------------------------------- /src/common/components/forms/SetupFormRefetchButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Box, Button, FormLabel } from '@mui/joy'; 4 | import SyncIcon from '@mui/icons-material/Sync'; 5 | 6 | import type { ToggleableBoolean } from '~/common/util/hooks/useToggleableBoolean'; 7 | 8 | 9 | /** 10 | * Bottom row: model reload and optional 'advanced' toggle 11 | */ 12 | export function SetupFormRefetchButton(props: { 13 | refetch: () => void, 14 | disabled: boolean, 15 | loading: boolean, 16 | error: boolean, 17 | leftButton?: React.ReactNode, 18 | advanced?: ToggleableBoolean 19 | }) { 20 | return ( 21 | 22 | 23 | {props.leftButton} 24 | 25 | {!!props.advanced && ( 26 | 27 | {props.advanced.on ? 'Hide Advanced' : 'Advanced'} 28 | 29 | )} 30 | 31 | 41 | 42 | 43 | ); 44 | } -------------------------------------------------------------------------------- /src/common/components/forms/useFormRadioLlmType.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { DLLM, DLLMId } from '~/common/stores/llms/llms.types'; 4 | import { useLLMs } from '~/common/stores/llms/llms.hooks'; 5 | 6 | import type { FormRadioOption } from './FormRadioControl'; 7 | import { useFormRadio } from './useFormRadio'; 8 | import { useModelDomain } from '~/common/stores/llms/hooks/useModelDomain'; 9 | 10 | 11 | type LlmType = 'run' | 'util'; 12 | 13 | export function useFormRadioLlmType(label: string, runModelId: DLLMId | null, initialModelType: LlmType): [DLLM | undefined, React.JSX.Element | null] { 14 | 15 | // external state 16 | const { domainModelId: utilModelId } = useModelDomain('fastUtil'); 17 | const [runLLM, utilLLM] = useLLMs([runModelId ?? '', utilModelId ?? '']); 18 | 19 | 20 | const hidden = !runLLM || !utilLLM || runLLM === utilLLM; 21 | 22 | const options = React.useMemo((): FormRadioOption[] => [ 23 | { label: runLLM?.label ?? '[missing llm]', value: 'run' }, 24 | { label: utilLLM?.label ?? '[missing util llm]', value: 'util' }, 25 | ], [runLLM, utilLLM]); 26 | 27 | const [llmType, component] = useFormRadio(initialModelType, options, label, hidden); 28 | const value = (llmType === 'run' || !utilLLM) ? runLLM : utilLLM; 29 | 30 | return [value, component]; 31 | } -------------------------------------------------------------------------------- /src/common/components/icons/3rdparty/AuthGitHubIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function AuthGitHubIcon(props: SvgIconProps) { 6 | return 7 | 8 | ; 9 | } -------------------------------------------------------------------------------- /src/common/components/icons/3rdparty/AuthGoogleIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function AuthGoogleIcon(props: SvgIconProps) { 6 | return 7 | 8 | 9 | 10 | 11 | ; 12 | } -------------------------------------------------------------------------------- /src/common/components/icons/3rdparty/CodePenIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /** 6 | * This is taken from the Codepen website - all rights reserved to them. 7 | * This is the code of the public facing website, we embed it to send traffic to them. 8 | */ 9 | export function CodePenIcon(props: SvgIconProps) { 10 | return 11 | 12 | {/**/} 13 | ; 14 | } -------------------------------------------------------------------------------- /src/common/components/icons/3rdparty/DiscordIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | // missing from MUI, using Tabler for Discord 6 | export function DiscordIcon(props: SvgIconProps) { 7 | return 8 | 9 | 10 | ; 11 | } -------------------------------------------------------------------------------- /src/common/components/icons/3rdparty/ExternalTickIcon.tsx: -------------------------------------------------------------------------------- 1 | // import * as React from 'react'; 2 | // 3 | // import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | // 5 | // export function ExternalTickIcon(props: SvgIconProps) { 6 | // return 7 | // 8 | // ; 9 | // } -------------------------------------------------------------------------------- /src/common/components/icons/3rdparty/GoogleColabIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /** 6 | * This is taken from wikipedia: https://upload.wikimedia.org/wikipedia/commons/d/d0/Google_Colaboratory_SVG_Logo.svg 7 | */ 8 | export function GoogleColabIcon(props: SvgIconProps) { 9 | return 10 | 11 | 12 | 13 | 14 | 15 | ; 16 | } -------------------------------------------------------------------------------- /src/common/components/icons/3rdparty/StackBlitzIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /** 6 | * This is taken from the StackBlitz website - all rights reserved to them. 7 | * This is the code of the public facing website, we embed it to send traffic to them. 8 | */ 9 | export function StackBlitzIcon(props: SvgIconProps) { 10 | return 11 | 12 | {/**/} 13 | ; 14 | } -------------------------------------------------------------------------------- /src/common/components/icons/CalloutTopRightIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function CalloutTopRightIcon(props: SvgIconProps) { 6 | return 7 | 8 | ; 9 | } -------------------------------------------------------------------------------- /src/common/components/icons/ChatBeamIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function ChatBeamIcon(props: SvgIconProps) { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } -------------------------------------------------------------------------------- /src/common/components/icons/ChatMulticastOffIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /* 6 | * Source: the PodcastsIcon from '@mui/icons-material/Podcasts'; 7 | */ 8 | export function ChatMulticastOffIcon(props: SvgIconProps) { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } -------------------------------------------------------------------------------- /src/common/components/icons/ChatMulticastOnIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /* 6 | * Source: the PodcastsIcon from '@mui/icons-material/Podcasts'; 7 | */ 8 | export function ChatMulticastOnIcon(props: SvgIconProps) { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } -------------------------------------------------------------------------------- /src/common/components/icons/CodiconSplitHorizontal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function CodiconSplitHorizontal(props: SvgIconProps) { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } -------------------------------------------------------------------------------- /src/common/components/icons/CodiconSplitHorizontalRemove.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function CodiconSplitHorizontalRemove(props: SvgIconProps) { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } -------------------------------------------------------------------------------- /src/common/components/icons/CodiconSplitVertical.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function CodiconSplitVertical(props: SvgIconProps) { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } -------------------------------------------------------------------------------- /src/common/components/icons/CodiconSplitVerticalRemove.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function CodiconSplitVerticalRemove(props: SvgIconProps) { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } -------------------------------------------------------------------------------- /src/common/components/icons/CodiconUnsplit.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function CodiconUnsplit(props: SvgIconProps) { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } -------------------------------------------------------------------------------- /src/common/components/icons/FoldersToggleOff.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function FoldersToggleOff(props: SvgIconProps) { 6 | return 7 | 8 | 9 | ; 10 | } -------------------------------------------------------------------------------- /src/common/components/icons/FoldersToggleOn.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function FoldersToggleOn(props: SvgIconProps) { 6 | return 7 | 8 | 9 | ; 10 | } -------------------------------------------------------------------------------- /src/common/components/icons/LayoutSidebarRight.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function LayoutSidebarRight(props: SvgIconProps) { 6 | return ( 7 | 14 | 15 | 16 | ); 17 | } -------------------------------------------------------------------------------- /src/common/components/icons/LiveFilePatchIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /* 6 | * Source: the MultipleStopIcon from '@mui/icons-material/Podcasts'; 7 | */ 8 | export function LiveFilePatchIcon(props: SvgIconProps) { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } -------------------------------------------------------------------------------- /src/common/components/icons/MarkHighlightIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /* 6 | * Source: '@mui/icons-material/DriveFileRenameOutline'; 7 | */ 8 | export function MarkHighlightIcon(props: SvgIconProps & { hcolor?: string }) { 9 | return ( 10 | 11 | 12 | 13 | {props.hcolor && } 14 | 15 | ); 16 | } -------------------------------------------------------------------------------- /src/common/components/icons/WindowPaneRightOpen.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function WindowPaneRightOpen(props: SvgIconProps) { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } -------------------------------------------------------------------------------- /src/common/components/icons/big-agi/BigAgiCircleIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | 6 | export function BigAgiCircleIcon(props: { innerColor?: string, compensateThinLine?: boolean } & SvgIconProps) { 7 | const { innerColor, compensateThinLine, ...rest } = props; 8 | return ( 9 | 10 | {props.innerColor && } 11 | 12 | 13 | ); 14 | } -------------------------------------------------------------------------------- /src/common/components/icons/big-agi/BigAgiCircleInnerIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | 6 | export function BigAgiCircleInnerIcon(props: { outerColor: string, compensateThinline?: boolean } & SvgIconProps) { 7 | const { outerColor, compensateThinline, ...rest } = props; 8 | return ( 9 | 10 | {props.outerColor && } 11 | 12 | 13 | ); 14 | } -------------------------------------------------------------------------------- /src/common/components/icons/phosphor/PhPaintBrush.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /* 6 | * Source: 'https://phosphoricons.com/' - paint-brush-household 7 | */ 8 | export function PhPaintBrush(props: SvgIconProps) { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/common/components/icons/phosphor/PhPaintBrushHousehold.tsx: -------------------------------------------------------------------------------- 1 | // import * as React from 'react'; 2 | // 3 | // import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | // 5 | // /* 6 | // * Source: 'https://phosphoricons.com/' - paint-brush-household 7 | // */ 8 | // export function PhPaintBrushHousehold(props: SvgIconProps) { 9 | // return ( 10 | // 11 | // 12 | // 13 | // ); 14 | // } 15 | -------------------------------------------------------------------------------- /src/common/components/icons/phosphor/PhSlidersHorizontalIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /* 6 | * Source: 'https://phosphoricons.com/?q=%22sliders-horizontal%22'; 7 | */ 8 | export function PhSlidersHorizontalIcon(props: SvgIconProps) { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/common/components/icons/phosphor/PhSlidersIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /* 6 | * Source: 'https://phosphoricons.com/?q=%22sliders%22'; 7 | */ 8 | export function PhSlidersIcon(props: SvgIconProps) { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/common/components/icons/phosphor/PhUsers.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | /* 6 | * Source: 'https://phosphoricons.com/' - users 7 | */ 8 | export function PhUsers(props: SvgIconProps) { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/common/components/icons/vendors/AlibabaCloudIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | // This icon has been converted from the official SVG of the Alibaba Cloud logo 6 | export function AlibabaCloudIcon(props: SvgIconProps) { 7 | return 8 | 9 | 10 | ; 11 | } -------------------------------------------------------------------------------- /src/common/components/icons/vendors/AnthropicIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function AnthropicIcon(props: SvgIconProps) { 6 | return 7 | 8 | ; 9 | } -------------------------------------------------------------------------------- /src/common/components/icons/vendors/AzureIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function AzureIcon(props: SvgIconProps) { 6 | return 7 | {/**/} 8 | 9 | 10 | 11 | ; 12 | } -------------------------------------------------------------------------------- /src/common/components/icons/vendors/LMStudioIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function LMStudioIcon(props: SvgIconProps) { 6 | return 7 | 8 | 9 | 10 | 11 | 12 | 13 | ; 14 | } -------------------------------------------------------------------------------- /src/common/components/icons/vendors/MistralIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function MistralIcon(props: SvgIconProps) { 6 | return 7 | 8 | ; 9 | } -------------------------------------------------------------------------------- /src/common/components/icons/vendors/OpenAIIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function OpenAIIcon(props: SvgIconProps) { 6 | return 7 | {/**/} 8 | 9 | 10 | 11 | 12 | 13 | 14 | ; 15 | } -------------------------------------------------------------------------------- /src/common/components/icons/vendors/OpenRouterIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function OpenRouterIcon(props: SvgIconProps) { 6 | return 7 | 8 | 9 | 10 | 11 | ; 12 | } -------------------------------------------------------------------------------- /src/common/components/icons/vendors/PerplexityIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function PerplexityIcon(props: SvgIconProps) { 6 | return 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ; 15 | } -------------------------------------------------------------------------------- /src/common/components/icons/vendors/XAIIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SvgIcon, SvgIconProps } from '@mui/joy'; 4 | 5 | export function XAIIcon(props: SvgIconProps) { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } -------------------------------------------------------------------------------- /src/common/components/modals/SherpaModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { ColorPaletteProp, Modal, ModalClose, ModalOverflow } from '@mui/joy'; 4 | 5 | import { noBackdropSlotProps } from './GoodModal'; 6 | 7 | 8 | /** 9 | * A simple modal that centers content and uses the 'sherpa' theme. 10 | */ 11 | export function SherpaModal(props: { 12 | themedColor?: ColorPaletteProp, 13 | unfilterBackdrop?: boolean, 14 | onClose: () => void, 15 | children: React.ReactNode, 16 | }) { 17 | 18 | const backdropSx = React.useMemo(() => props.themedColor ? { 19 | backdrop: { 20 | sx: { 21 | backgroundColor: `rgba(var(--joy-palette-${props.themedColor}-darkChannel) / 0.3)`, 22 | backdropFilter: props.unfilterBackdrop ? 'none' : 'blur(32px)', 23 | }, 24 | }, 25 | } : props.unfilterBackdrop ? noBackdropSlotProps : undefined, [props.themedColor, props.unfilterBackdrop]); 26 | 27 | return ( 28 | 29 | 30 | 31 | {props.children} 32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/common/components/panes/GoodPanelResizeHandler.tsx: -------------------------------------------------------------------------------- 1 | import { Box, styled } from '@mui/joy'; 2 | 3 | 4 | export const PanelResizeInset = styled(Box)({ 5 | width: '100%', 6 | height: '100%', 7 | 8 | // 4px 9 | minWidth: '0.25rem', 10 | minHeight: '0.25rem', 11 | 12 | // 0px 13 | // minWidth: 0, 14 | // minHeight: 0, 15 | 16 | // backgroundColor: 'var(--joy-palette-divider)', 17 | transition: 'background-color 0.1s', 18 | '&:hover': { 19 | backgroundColor: 'var(--joy-palette-primary-solidBg)', 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /src/common/components/shortcuts/store-global-shortcuts.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | import type { ShortcutObject } from './useGlobalShortcuts'; 4 | 5 | 6 | type ShortcutGroupId = string; 7 | 8 | interface ShortcutsStore { 9 | // state 10 | shortcutGroups: Record; 11 | hasShortcuts: boolean; 12 | // actions 13 | setGroupShortcuts: (groupId: ShortcutGroupId, shortcuts: ShortcutObject[]) => void; 14 | removeGroup: (groupId: ShortcutGroupId) => void; 15 | getAllShortcuts: () => ShortcutObject[]; 16 | } 17 | 18 | 19 | export const useGlobalShortcutsStore = create((set, get) => ({ 20 | 21 | shortcutGroups: {}, 22 | hasShortcuts: false, 23 | 24 | setGroupShortcuts: (groupId: ShortcutGroupId, shortcuts: ShortcutObject[]) => 25 | set((state) => ({ 26 | shortcutGroups: { ...state.shortcutGroups, [groupId]: shortcuts }, 27 | hasShortcuts: true, 28 | })), 29 | 30 | removeGroup: (groupId) => 31 | set((state) => { 32 | const { [groupId]: _, ...rest } = state.shortcutGroups; 33 | return { 34 | shortcutGroups: rest, 35 | hasShortcuts: Object.keys(rest).length > 0, 36 | }; 37 | }), 38 | 39 | /** 40 | * Returns all shortcuts, priritized by level (descending). 41 | */ 42 | getAllShortcuts: () => Object.values(get().shortcutGroups) 43 | .flat() 44 | .sort((a, b) => (b.level ?? 0) - (a.level ?? 0)), 45 | 46 | })); 47 | -------------------------------------------------------------------------------- /src/common/components/useChipBoolean.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { ChipExpander } from './ChipExpander'; 4 | 5 | 6 | export function useChipBoolean(text: React.ReactNode, initialExpand = false): [boolean, React.JSX.Element] { 7 | 8 | // state 9 | const [expanded, setExpanded] = React.useState(initialExpand); 10 | 11 | const component = React.useMemo(() => ( 12 | setExpanded(on => !on)} /> 13 | ), [expanded, text]); 14 | 15 | return [expanded, component]; 16 | } -------------------------------------------------------------------------------- /src/common/components/useDontBlurTextarea.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | 4 | export function useDontBlurTextarea() { 5 | return React.useCallback((event: React.MouseEvent) => { 6 | const isTextAreaFocused = document.activeElement?.tagName === 'TEXTAREA'; 7 | // If a textarea is focused, prevent the default blur behavior 8 | if (isTextAreaFocused) 9 | event.preventDefault(); 10 | }, []); 11 | } -------------------------------------------------------------------------------- /src/common/components/useFullscreenElement.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2024 Enrico Ros 3 | * 4 | * Hook to manage fullscreen mode for a given element. 5 | */ 6 | 7 | import * as React from 'react'; 8 | 9 | export function useFullscreenElement(elementRef: React.RefObject) { 10 | 11 | // state 12 | const [isFullscreen, setIsFullscreen] = React.useState(false); 13 | 14 | // methods 15 | const enterFullscreen = React.useCallback(async () => { 16 | if (!elementRef.current) return; 17 | try { 18 | await elementRef.current.requestFullscreen(); 19 | } catch (error) { 20 | console.error('Error attempting to enable fullscreen mode:', error); 21 | } 22 | }, [elementRef]); 23 | 24 | const exitFullscreen = React.useCallback(async () => { 25 | if (document.fullscreenElement) { 26 | try { 27 | await document.exitFullscreen(); 28 | } catch (error) { 29 | console.error('Error attempting to exit fullscreen mode:', error); 30 | } 31 | } 32 | }, []); 33 | 34 | // monitor fullscreen changes 35 | React.useEffect(() => { 36 | const onFullscreenChange = () => { 37 | setIsFullscreen(document.fullscreenElement === elementRef.current); 38 | } 39 | document.addEventListener('fullscreenchange', onFullscreenChange); 40 | return () => document.removeEventListener('fullscreenchange', onFullscreenChange); 41 | }, [elementRef]); 42 | 43 | return { isFullscreen, enterFullscreen, exitFullscreen }; 44 | } 45 | -------------------------------------------------------------------------------- /src/common/events/events.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base event payload structure 3 | */ 4 | export interface EventPayload { 5 | id: string; // unique ID 6 | timestamp: number; // when the event was emitted 7 | source?: string; // optional component/module that emitted the event 8 | correlationId?: string; // optional for tracking related events 9 | data: T; // event-specific data 10 | } 11 | 12 | /** 13 | * !!! Augmentation Target for Events (domains, events, data) extension !!! 14 | * Domain registry - Extended via module augmentation by each domain 15 | */ 16 | export interface EventDomains { 17 | // default: nothing 18 | } 19 | 20 | 21 | export type EventDomainName = keyof EventDomains; 22 | 23 | export type EventName = keyof EventDomains[D]; 24 | export type EventData> = EventDomains[D][E]; 25 | 26 | export type FullEventName> = 27 | `${string & D}:${string & E}`; 28 | 29 | 30 | export type EventListener> = 31 | (event: EventPayload>) => void; 32 | 33 | export type WildcardEventListener = 34 | (domain: string, name: string, event: EventPayload) => void; 35 | 36 | export type EventUnsubscribe = () => void; 37 | -------------------------------------------------------------------------------- /src/common/layout/container/ContainerLayout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Box, Container } from '@mui/joy'; 4 | 5 | import { themeBgApp } from '~/common/app.theme'; 6 | 7 | 8 | export function ContainerLayout(props: { children?: React.ReactNode }) { 9 | return <> 10 | 11 | {/* Headers as needed */} 12 | 13 | 14 | 19 | 20 | {props.children} 21 | 22 | 23 | 24 | 25 | {/* Footers as needed */} 26 | 27 | ; 28 | } -------------------------------------------------------------------------------- /src/common/layout/optima/nav/MobileNavIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Box, IconButton, styled } from '@mui/joy'; 2 | 3 | 4 | export const MobileNavGroupBox = styled(Box)({ 5 | // layout 6 | flex: 1, 7 | minHeight: 'var(--Bar)', 8 | 9 | // contents 10 | display: 'flex', 11 | flexDirection: 'row', 12 | flexWrap: 'wrap', 13 | justifyContent: 'space-evenly', 14 | alignItems: 'center', 15 | 16 | // style 17 | // backgroundColor: 'rgba(0 0 0 / 0.5)', // darken bg 18 | 19 | // debug 20 | // '& > *': { border: '1px solid red' }, 21 | }); 22 | 23 | export const mobileNavItemClasses = { 24 | typeApp: 'NavButton-typeApp', 25 | active: 'NavButton-active', 26 | }; 27 | 28 | export const MobileNavIcon = styled(IconButton)(({ theme }) => ({ 29 | 30 | // custom vars 31 | '--MarginY': '0.5rem', 32 | '--ExtraPadX': '1rem', 33 | 34 | // IconButton customization 35 | '--Icon-fontSize': '1.25rem', 36 | '--IconButton-size': 'calc(var(--Bar) - 2 * var(--MarginY))', 37 | paddingInline: 'var(--ExtraPadX)', 38 | border: 'none', 39 | 40 | [`&.${mobileNavItemClasses.typeApp}:hover`]: { 41 | backgroundColor: 'var(--variant-solidHoverBg)', 42 | // backgroundColor: theme.palette.neutral.softHoverBg, 43 | color: theme.palette.neutral.softColor, 44 | }, 45 | 46 | // app active (non hover) 47 | // [`&.${mobileNavItemClasses.typeApp}.${mobileNavItemClasses.active}`]: { 48 | // backgroundColor: ... 49 | // }, 50 | 51 | })) as typeof IconButton; -------------------------------------------------------------------------------- /src/common/layout/optima/optima.config.ts: -------------------------------------------------------------------------------- 1 | // configuration 2 | export const OPTIMA_DRAWER_BACKGROUND = 'var(--joy-palette-background-popup)'; 3 | export const OPTIMA_DRAWER_MOBILE_RADIUS = 'var(--joy-radius-lg)'; 4 | export const OPTIMA_NAV_RADIUS = 'sm'; 5 | export const OPTIMA_PANEL_GROUPS_SPACING = 2.5; 6 | 7 | // debug 8 | export const OPTIMA_DEBUG_PORTALS = false; 9 | -------------------------------------------------------------------------------- /src/common/layout/optima/panel/MobilePreferencesListItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { ListItem, ListItemButton, ListItemDecorator } from '@mui/joy'; 4 | import SettingsIcon from '@mui/icons-material/Settings'; 5 | 6 | import { DarkModeToggleButton } from '~/common/components/DarkModeToggleButton'; 7 | 8 | import { optimaClosePanel, optimaOpenPreferences } from '../useOptima'; 9 | 10 | 11 | export function MobilePreferencesListItem(props: { autoClosePanel?: boolean }) { 12 | 13 | const handleShowPreferences = React.useCallback((event: React.MouseEvent) => { 14 | event.stopPropagation(); 15 | optimaOpenPreferences(); 16 | if (props.autoClosePanel) 17 | optimaClosePanel(); 18 | }, [props.autoClosePanel]); 19 | 20 | return ( 21 | }> 22 | 23 | 24 | Big-AGI Preferences{/**/} 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/common/layout/optima/panel/PanelContentPortal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { SxProps } from '@mui/joy/styles/types'; 4 | import { Box } from '@mui/joy'; 5 | 6 | import { OPTIMA_PANEL_GROUPS_SPACING } from '../optima.config'; 7 | import { useOptimaPortalOutRef } from '../portals/useOptimaPortalOutRef'; 8 | 9 | 10 | const portalContentSx: SxProps = { 11 | flex: 1, 12 | display: 'flex', 13 | flexDirection: 'column', 14 | gap: OPTIMA_PANEL_GROUPS_SPACING, 15 | // gap: 'var(--ListDivider-gap)' 16 | }; 17 | 18 | 19 | export function PanelContentPortal() { 20 | 21 | // external state 22 | const panelPortalRef = useOptimaPortalOutRef('optima-portal-panel', 'PanelPortal'); 23 | 24 | return ; 25 | } -------------------------------------------------------------------------------- /src/common/layout/optima/panel/PopupPanel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { CloseablePopup } from '~/common/components/CloseablePopup'; 4 | 5 | import { PanelContentPortal } from './PanelContentPortal'; 6 | import { optimaClosePanel } from '../useOptima'; 7 | 8 | 9 | export function PopupPanel(props: { anchorEl: HTMLElement | null }) { 10 | return ( 11 | 18 | 19 | {/* [Desktop] Portal within the Popup - [Mobile] always use the panel */} 20 | 21 | 22 | 23 | ); 24 | } -------------------------------------------------------------------------------- /src/common/layout/optima/portals/useOptimaPortalHasInputs.ts: -------------------------------------------------------------------------------- 1 | import { OptimaPortalId, useLayoutPortalsStore } from './store-layout-portals'; 2 | 3 | export function useOptimaPortalHasInputs(portalTargetId: OptimaPortalId) { 4 | return useLayoutPortalsStore(state => state.portals[portalTargetId]?.inputs >= 1); 5 | } 6 | -------------------------------------------------------------------------------- /src/common/layout/optima/portals/useOptimaPortalOutRef.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { OptimaPortalId, useLayoutPortalsStore } from './store-layout-portals'; 4 | 5 | 6 | /** 7 | * Defines a React output portal for a given target id. Will return a ref that 8 | * must be attached to the target element. 9 | * 10 | * Note: this hook assumes that the ref is created by when useLayoutEffect is called, 11 | * and will warn otherwise. 12 | * 13 | * If the ref is created after the layout effect, the portal will not be added. In 14 | * that case, consider returning a Callback instead, with: 15 | * `const setRef = React.useCallback((node: HTMLElement | null) => { ... }, [portalTargetId]);` 16 | */ 17 | export function useOptimaPortalOutRef(portalTargetId: OptimaPortalId, debugCallerName: string) { 18 | 19 | // state 20 | const ref = React.useRef(null); 21 | 22 | React.useLayoutEffect(() => { 23 | const { setElement } = useLayoutPortalsStore.getState(); 24 | if (!ref.current) { 25 | console.warn(`useOptimaPortalOut: ref.current is null for type ${portalTargetId} (called by ${debugCallerName})`); 26 | } else { 27 | setElement(portalTargetId, ref.current); 28 | ref.current.dataset['optimaOutId'] = portalTargetId.replace('optima-portal-', ''); 29 | } 30 | return () => setElement(portalTargetId, null); 31 | }, [debugCallerName, portalTargetId]); 32 | 33 | return ref; 34 | } 35 | -------------------------------------------------------------------------------- /src/common/layout/overlays/OverlaysInsert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { useLayoutOverlaysStore } from './store-layout-overlays'; 4 | 5 | 6 | export const OverlaysInsert: React.FC = () => { 7 | 8 | // external state 9 | const overlays = useLayoutOverlaysStore(state => state.overlays); 10 | 11 | // Transient Overlays / Modals 12 | return overlays.map(({ id, component }) => ( 13 | {component} 14 | )); 15 | }; 16 | -------------------------------------------------------------------------------- /src/common/livefile/liveFile.icons.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/joy'; 2 | 3 | // import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; 4 | // import SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt'; 5 | // import UploadFileIcon from '@mui/icons-material/UploadFile'; 6 | import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined'; 7 | import FileOpenOutlinedIcon from '@mui/icons-material/FileOpenOutlined'; 8 | import MobiledataOffIcon from '@mui/icons-material/MobiledataOff'; 9 | import MultipleStopIcon from '@mui/icons-material/MultipleStop'; 10 | import SaveIcon from '@mui/icons-material/SaveOutlined'; 11 | 12 | import { LiveFilePatchIcon } from '~/common/components/icons/LiveFilePatchIcon'; 13 | 14 | export const LiveFileIcon = styled(MultipleStopIcon)({ 15 | rotate: '90deg', 16 | }); 17 | 18 | export { FileOpenOutlinedIcon as LiveFileChooseIcon }; 19 | export { LiveFilePatchIcon as LiveFilePatchIcon }; 20 | export { MobiledataOffIcon as LiveFileCloseIcon }; 21 | export { SaveIcon as LiveFileSaveIcon }; 22 | export { FileDownloadOutlinedIcon as LiveFileReloadIcon }; 23 | -------------------------------------------------------------------------------- /src/common/livefile/livefile.theme.ts: -------------------------------------------------------------------------------- 1 | import type { SxProps } from '@mui/joy/styles/types'; 2 | 3 | export const liveFileSheetSx: SxProps = { 4 | p: 1, 5 | backgroundColor: 'rgb(var(--joy-palette-neutral-lightChannel) / 20%)', 6 | border: '1px solid', 7 | borderRadius: 'sm', 8 | borderColor: 'neutral.outlinedBorder', 9 | boxShadow: `inset 0 4px 6px -6px rgb(var(--joy-palette-neutral-darkChannel) / 40%)`, 10 | fontSize: 'sm', 11 | display: 'flex', 12 | flexWrap: 'wrap', 13 | alignItems: 'center', 14 | gap: 1, 15 | 'button': { 16 | backgroundColor: 'background.surface', 17 | }, 18 | 'button:hover': { 19 | backgroundColor: 'background.popup', 20 | boxShadow: 'xs', 21 | }, 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /src/common/livefile/useLiveFileMetadata.tsx: -------------------------------------------------------------------------------- 1 | import type { LiveFileId, LiveFileMetadata } from '~/common/livefile/liveFile.types'; 2 | import { useLiveFileStore } from '~/common/livefile/store-live-file'; 3 | import { useShallow } from 'zustand/react/shallow'; 4 | 5 | export function useLiveFileMetadata(liveFileId: LiveFileId | undefined): LiveFileMetadata | null { 6 | return useLiveFileStore(useShallow((store) => 7 | !liveFileId ? null : store.metadataGet(liveFileId), 8 | )); 9 | } -------------------------------------------------------------------------------- /src/common/logger/hooks/useClientLoggerInterception.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { setupClientFetchErrorsLogging, setupClientUncaughtErrorsLogging } from '~/common/logger'; 4 | 5 | 6 | /** 7 | * Custom React hook that sets up client-side logging interceptors exactly once, 8 | * even under Strict Mode, without double initialization. 9 | */ 10 | export function useClientLoggerInterception(captureUnhandledErrors: boolean, captureFetchErrors: boolean) { 11 | React.useEffect(() => { 12 | // mount 13 | const cleanupFetch = !captureFetchErrors ? undefined : setupClientFetchErrorsLogging(); 14 | const cleanupUncaught = !captureUnhandledErrors ? undefined : setupClientUncaughtErrorsLogging(); 15 | 16 | // unmount 17 | return () => { 18 | cleanupFetch?.(); 19 | cleanupUncaught?.(); 20 | }; 21 | }, [captureFetchErrors, captureUnhandledErrors]); 22 | } 23 | -------------------------------------------------------------------------------- /src/common/logger/index.ts: -------------------------------------------------------------------------------- 1 | // re-export the core functionality 2 | export type { LogEntry, LogLevel, LogSource } from './logger.types'; 3 | 4 | // re-export the global handlers 5 | export { setupClientFetchErrorsLogging } from './interceptors/logger.network'; 6 | export { setupClientUncaughtErrorsLogging } from './interceptors/logger.unhandled'; 7 | 8 | // re-export the core functionality 9 | export { logger } from './logger.client'; 10 | 11 | // re-export the module logger factory 12 | export type { ClientLogger } from './logger.types'; 13 | export { createModuleLogger } from './logger.factory'; 14 | -------------------------------------------------------------------------------- /src/common/logger/interceptors/logger.unhandled.ts: -------------------------------------------------------------------------------- 1 | import { isBrowser } from '~/common/util/pwaUtils'; 2 | import { logger } from '~/common/logger'; 3 | 4 | 5 | /** 6 | * Intercept & log global uncaught client errors 7 | */ 8 | export function setupClientUncaughtErrorsLogging(): () => void { 9 | if (!isBrowser) return () => { /* no-op */ 10 | }; 11 | 12 | // Handle uncaught exceptions 13 | const handleError = (event: ErrorEvent) => { 14 | logger.error('Uncaught error', { 15 | message: event.error?.message || event.message, 16 | stack: event.error?.stack, 17 | filename: event.filename, 18 | lineno: event.lineno, 19 | colno: event.colno, 20 | }, 'unhandled'); 21 | }; 22 | 23 | // Handle unhandled promise rejections 24 | const handleRejection = (event: PromiseRejectionEvent) => { 25 | logger.error('Unhandled promise rejection', { 26 | reason: event.reason, 27 | message: event.reason?.message, 28 | stack: event.reason?.stack, 29 | }, 'unhandled'); 30 | }; 31 | 32 | // install 33 | window.addEventListener('error', handleError); 34 | window.addEventListener('unhandledrejection', handleRejection); 35 | 36 | // cleanup function 37 | return () => { 38 | window.removeEventListener('error', handleError); 39 | window.removeEventListener('unhandledrejection', handleRejection); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/common/providers/ProviderSingleTab.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Button, Sheet, Typography } from '@mui/joy'; 4 | 5 | import { reloadPage } from '../app.routes'; 6 | import { useSingleTabEnforcer } from '../components/useSingleTabEnforcer'; 7 | 8 | 9 | export const ProviderSingleTab = (props: { disabled?: boolean, children: React.ReactNode }) => { 10 | 11 | // state 12 | const isSingleTab = useSingleTabEnforcer('big-agi-tabs'); 13 | 14 | // pass-through until we know for sure that other tabs are open 15 | if (props.disabled || isSingleTab === null || isSingleTab) 16 | return props.children; 17 | 18 | 19 | return ( 20 | 29 | 30 | 31 | It looks like this app is already running in another browser Tab or Window.
32 | To continue here, please close the other instance first. 33 |
34 | 35 | 38 | 39 |
40 | ); 41 | }; -------------------------------------------------------------------------------- /src/common/stores/chat/hooks/useChatsCount.ts: -------------------------------------------------------------------------------- 1 | import { useChatStore } from '../store-chats'; 2 | 3 | 4 | export function useChatsCount() { 5 | return useChatStore(({ conversations }) => conversations.length); 6 | } -------------------------------------------------------------------------------- /src/common/stores/chat/hooks/useConversationTitle.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useShallow } from 'zustand/react/shallow'; 3 | 4 | import { conversationTitle, DConversationId } from '../chat.conversation'; 5 | import { useChatStore } from '../store-chats'; 6 | 7 | 8 | export function useConversationTitle(conversationId: DConversationId | null, fallbackTitle?: string) { 9 | 10 | // react to the title 11 | const { title, setUserTitle: storeSetUserTitle } = useChatStore(useShallow(({ conversations, setUserTitle }) => { 12 | const conversation = conversationId ? conversations.find(_c => _c.id === conversationId) : null; 13 | return { 14 | title: conversation ? conversationTitle(conversation, fallbackTitle) : null, 15 | setUserTitle, 16 | }; 17 | })); 18 | 19 | // closure to set the title 20 | const setUserTitle = React.useCallback((newTitle: string) => { 21 | conversationId && storeSetUserTitle(conversationId, newTitle); 22 | }, [conversationId, storeSetUserTitle]); 23 | 24 | return { title, setUserTitle }; 25 | } 26 | -------------------------------------------------------------------------------- /src/common/stores/llms/hooks/useAllLLMs.ts: -------------------------------------------------------------------------------- 1 | import type { DLLM } from '../llms.types'; 2 | import { useModelsStore } from '../store-llms'; 3 | 4 | 5 | export function useAllLLMs(): ReadonlyArray { 6 | return useModelsStore(state => state.llms); 7 | } 8 | -------------------------------------------------------------------------------- /src/common/stores/llms/hooks/useModelDomains.ts: -------------------------------------------------------------------------------- 1 | import { useModelsStore } from '~/common/stores/llms/store-llms'; 2 | 3 | /** 4 | * Single hooks to access per-domain LLM configurations. 5 | */ 6 | export function useModelDomains() { 7 | return useModelsStore(state => state.modelAssignments); 8 | } 9 | -------------------------------------------------------------------------------- /src/common/stores/llms/llms.service.types.ts: -------------------------------------------------------------------------------- 1 | // 2 | // WARNING: Everything here is data at rest. Know what you're doing. 3 | // 4 | 5 | import type { ModelVendorId } from '~/modules/llms/vendors/vendors.registry'; 6 | 7 | /** 8 | * Models Service - configured to be a unique origin of models (data object, stored) 9 | */ 10 | export interface DModelsService { 11 | id: DModelsServiceId; 12 | label: string; 13 | 14 | // service -> vendor of that service 15 | vId: ModelVendorId; 16 | 17 | // service-specific 18 | setup: Partial; 19 | } 20 | 21 | export type DModelsServiceId = string; -------------------------------------------------------------------------------- /src/common/stores/llms/model.domains.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Stored type - edit with care 3 | */ 4 | export type DModelDomainId = 5 | | 6 | /** 7 | * Primary Chat model - used in the Chat window, inferred from .messages[].generator, or set on the Persona 8 | */ 9 | 'primaryChat' 10 | | 11 | /** 12 | * Code Editor - used in the Code Editor, for applying code changes -- currently, only Sonnet 3.5 is recommended 13 | */ 14 | 'codeApply' 15 | | 16 | /** 17 | * Fast Utility model; must have function calling, but we won't enforce in the code for now until all LLMs are correctly identified as FC or not - used for quick responses and simple tasks 18 | */ 19 | 'fastUtil' 20 | ; -------------------------------------------------------------------------------- /src/common/stores/metrics/store-metrics.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | import { persist } from 'zustand/middleware'; 3 | 4 | import type { DLLM } from '~/common/stores/llms/llms.types'; 5 | import type { DModelsServiceId } from '~/common/stores/llms/llms.service.types'; 6 | 7 | import type { MetricsChatGenerateCost_Md } from './metrics.chatgenerate'; 8 | import { createServiceMetricsSlice, fallbackEmptyServiceMetricsAggregate, ServiceMetricsSlice } from './metrics.modelservice'; 9 | 10 | 11 | // Store: single per-app, using the slices pattern for aggregations 12 | 13 | const useMetricsStore = create()(persist((...a) => ({ 14 | ...createServiceMetricsSlice(...a), 15 | }), { 16 | name: 'app-metrics', 17 | })); 18 | 19 | 20 | export function metricsStoreAddChatGenerate(costs: MetricsChatGenerateCost_Md, inputTokens: number, outputTokens: number, llm: DLLM, debugCostSource: string) { 21 | useMetricsStore.getState().addChatGenerateCostEntry(costs, inputTokens, outputTokens, llm.sId || null, debugCostSource); 22 | } 23 | 24 | export function useCostMetricsForLLMService(serviceId?: DModelsServiceId) { 25 | return useMetricsStore((state) => 26 | serviceId ? state.getAggregateMetricsForService(serviceId) ?? fallbackEmptyServiceMetricsAggregate 27 | : fallbackEmptyServiceMetricsAggregate, 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/common/stores/workspace/WorkspaceIdProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { DConversationId } from '~/common/stores/chat/chat.conversation'; 4 | 5 | import { DWorkspaceId, workspaceForConversationIdentity } from './workspace.types'; 6 | 7 | 8 | // The Context and the data it will prop-drill 9 | const WorkspaceContext = React.createContext(null as any as WorkspaceContextData); 10 | 11 | interface WorkspaceContextData { 12 | workspaceId: DWorkspaceId | null; 13 | } 14 | 15 | 16 | /** 17 | * Provides the workspaceId for its children 18 | */ 19 | export function WorkspaceIdProvider(props: { 20 | conversationId: DConversationId | null, 21 | children?: React.ReactNode, 22 | }) { 23 | 24 | // workspace data 25 | const workspaceData = React.useMemo(() => ({ 26 | workspaceId: workspaceForConversationIdentity(props.conversationId), 27 | }), [props.conversationId]); 28 | 29 | return ( 30 | 31 | {props.children} 32 | 33 | ); 34 | } 35 | 36 | /** 37 | * Access the workspaceId from within a WorkspaceIdProvider subtree 38 | */ 39 | export function useContextWorkspaceId() { 40 | const value = React.useContext(WorkspaceContext); 41 | if (!value) 42 | throw new Error('Missing WorkspaceProvider'); 43 | return value.workspaceId; 44 | } 45 | -------------------------------------------------------------------------------- /src/common/stores/workspace/workspace.types.ts: -------------------------------------------------------------------------------- 1 | import type { DConversationId } from '~/common/stores/chat/chat.conversation'; 2 | 3 | /** 4 | * Not well defined for now, and mapping to DConversations, but we'll define this properly in the future 5 | * Makes searching for conversations easier 6 | */ 7 | export type DWorkspaceId = string; 8 | 9 | 10 | /** 11 | * Make the V2 shortcut of having conversation=workspace evident and searachable. 12 | */ 13 | export function workspaceForConversationIdentity(conversationId: T): T { 14 | return conversationId; 15 | } 16 | -------------------------------------------------------------------------------- /src/common/styles/NProgress.css: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the Big-AGI customized nprogress.css file. 3 | * We only change the height and color of the bar. 4 | */ 5 | 6 | /* Make clicks pass-through */ 7 | #nprogress { 8 | --barColor: #32383E; 9 | --barHeight: 4px; 10 | pointer-events: none; 11 | } 12 | 13 | #nprogress .bar { 14 | background: var(--barColor); 15 | 16 | position: fixed; 17 | z-index: 1031; 18 | top: 0; 19 | left: 0; 20 | 21 | width: 100%; 22 | height: var(--barHeight); 23 | } 24 | 25 | /* Fancy blur effect */ 26 | #nprogress .peg { 27 | display: block; 28 | position: absolute; 29 | right: 0; 30 | width: 100px; 31 | height: 100%; 32 | box-shadow: 0 0 10px var(--barColor), 0 0 5px var(--barColor); 33 | opacity: 1.0; 34 | 35 | -ms-transform: rotate(3deg) translate(0px, -4px); 36 | transform: rotate(3deg) translate(0px, -4px); 37 | } 38 | -------------------------------------------------------------------------------- /src/common/styles/app.styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --AGI-Nav-width: 52px; 3 | --AGI-Drawer-width: 320px; 4 | --AGI-Panel-width: 320px; 5 | --AGI-Desktop-Drawer-width: clamp(260px, 18vw, 350px); 6 | --AGI-Desktop-Panel-width: clamp(260px, 18vw, 350px); 7 | --AGI-overlay-start-opacity: 0; 8 | } 9 | 10 | .agi-ellipsize { 11 | white-space: nowrap; 12 | overflow: hidden; 13 | text-overflow: ellipsis; 14 | } 15 | 16 | /* Prevents pull-to-refresh on mobile, so it's not triggered while scrolling the chat inadvertently */ 17 | body { 18 | overscroll-behavior-y: none; 19 | } -------------------------------------------------------------------------------- /src/common/types/immutable.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Deep immutable type. Usage example: Immutable 3 | */ 4 | export type Immutable = 5 | T extends (...args: any[]) => any ? T // function types as-is 6 | : T extends Array ? ReadonlyArray> // convert arrays into ReadonlyArray 7 | : T extends Map ? ReadonlyMap, Immutable> 8 | : T extends Set ? ReadonlySet> 9 | : T extends object ? { readonly [K in keyof T]: Immutable } // objects: recursively mark properties as readonly 10 | : T; // Primitives remain the same 11 | -------------------------------------------------------------------------------- /src/common/types/next.page.d.ts: -------------------------------------------------------------------------------- 1 | import type { ReactElement, ReactNode } from 'react'; 2 | import type { NextPage } from 'next'; 3 | import type { EmotionCache } from '@emotion/react'; 4 | 5 | 6 | export type NextPageWithLayout

= NextPage & { 7 | // definition of the per-page layout function, as per: 8 | // https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts#per-page-layouts 9 | getLayout?: (page: ReactElement) => ReactNode; 10 | } 11 | 12 | // Extend the AppProps type with the custom page component type 13 | declare module 'next/app' { 14 | import { AppProps } from 'next/app'; 15 | 16 | type MyAppProps = AppProps & { 17 | Component: NextPageWithLayout 18 | emotionCache?: EmotionCache; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/common/types/useful.types.ts: -------------------------------------------------------------------------------- 1 | // Useful types that are not specific to us 2 | 3 | /** 4 | * Could be a value (or void) or a promise of it 5 | */ 6 | export type MaybePromise = T | Promise; 7 | 8 | /** 9 | * In our app 'undefined' and 'null' must be different, so we don't use 'Maybe' type 10 | * - undefined: missing value, never read, etc. 11 | * - null: means 'force no value', 'force empty', etc. 12 | */ 13 | // export type Maybe = T | null | undefined; 14 | -------------------------------------------------------------------------------- /src/common/util/audio/AudioPlayer.ts: -------------------------------------------------------------------------------- 1 | export namespace AudioPlayer { 2 | 3 | /** 4 | * Plays an audio file from a URL (e.g. an MP3 file). 5 | */ 6 | export async function playUrl(url: string): Promise { 7 | return new Promise((resolve, reject) => { 8 | const audio = new Audio(url); 9 | audio.onended = () => resolve(); 10 | audio.onerror = (e) => reject(new Error(`Error playing audio: ${e}`)); 11 | audio.play().catch(reject); 12 | }); 13 | } 14 | 15 | /** 16 | * Plays an audio buffer (e.g. from an ArrayBuffer). 17 | */ 18 | export async function playBuffer(audioBuffer: ArrayBuffer): Promise { 19 | const audioContext = new AudioContext(); 20 | const bufferSource = audioContext.createBufferSource(); 21 | bufferSource.buffer = await audioContext.decodeAudioData(audioBuffer); 22 | bufferSource.connect(audioContext.destination); 23 | bufferSource.start(); 24 | return new Promise((resolve) => { 25 | bufferSource.onended = () => resolve(); 26 | }); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/common/util/clientFetchers.ts: -------------------------------------------------------------------------------- 1 | export const frontendSideFetch = fetch; -------------------------------------------------------------------------------- /src/common/util/costUtils.ts: -------------------------------------------------------------------------------- 1 | export function formatModelsCost(cost: number) { 2 | return cost < 1 3 | ? `${(cost * 100).toFixed(cost < 0.010 ? 2 : 2)} ¢` 4 | : `$ ${cost.toFixed(2)}`; 5 | } -------------------------------------------------------------------------------- /src/common/util/hooks/useToggleableBoolean.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | // in-memory map to remember the last state 4 | const toggleStates = new Map(); 5 | 6 | export function useToggleableBoolean(initialValue: boolean = false, key?: string) { 7 | 8 | // Retrieve the initial value from memory if a key is provided and exists in the map 9 | const memoryValue = key ? toggleStates.get(key) : undefined; 10 | 11 | // state 12 | const [value, setValue] = React.useState(memoryValue ?? initialValue); 13 | 14 | // Define the toggle function 15 | const toggle = React.useCallback(() => { 16 | setValue(state => { 17 | const newValue = !state; 18 | // If a key is provided, update the value in the map 19 | if (key) 20 | toggleStates.set(key, newValue); 21 | return newValue; 22 | }); 23 | }, [key]); 24 | 25 | return { on: value, toggle }; 26 | } 27 | 28 | export type ToggleableBoolean = ReturnType; -------------------------------------------------------------------------------- /src/common/util/jsonUtils.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import { maybeDebuggerBreak } from '~/common/util/errorUtils'; 4 | 5 | 6 | // configuration 7 | const ENABLE_NON_STANDARD = false; // if true, it will enable 'undefined' as a valid value in JSON objects 8 | 9 | 10 | // 11 | // JSON validation - used before saving to DB/sync transmission - to ensure valid structure 12 | // 13 | 14 | const literalSchema = z.union([ 15 | z.string(), 16 | z.number(), 17 | z.boolean(), 18 | z.null(), 19 | // NON-STANDARD, but adding this because we do have 'undefined' in in-mem objects 20 | ...(ENABLE_NON_STANDARD ? [z.undefined()] : []), 21 | ]); 22 | 23 | type Literal = z.infer; 24 | type Json = Literal | Json[] | { [key: string]: Json }; 25 | 26 | const jsonSchema: z.ZodType = z.lazy(() => 27 | z.union([ 28 | literalSchema, 29 | z.array(jsonSchema), 30 | z.record(z.string(), jsonSchema), 31 | ]), 32 | ); 33 | 34 | /** 35 | * Checks if the given value is a valid JSON object, which will be serialized 36 | * without errors for storage or transmission. 37 | */ 38 | export function isValidJson(value: unknown, debugLocation: string): value is Json { 39 | const result = jsonSchema.safeParse(value); 40 | if (result.success) 41 | return true; 42 | 43 | console.log(`[DEV] ${debugLocation}: Invalid JSON:`, { error: result.error }); 44 | maybeDebuggerBreak(); 45 | return result.success; 46 | } 47 | -------------------------------------------------------------------------------- /src/common/util/mediasession/useMediaSessionCallbacks.ts: -------------------------------------------------------------------------------- 1 | // import * as React from 'react'; 2 | // 3 | // import { useShallowStable } from '../hooks/useShallowObject'; 4 | // 5 | // import { MediaSessionCallbacks, MediaSessionManager } from './MediaSessionManager'; 6 | // 7 | // 8 | // // noinspection JSUnusedGlobalSymbols 9 | // /** 10 | // * Note: this does not seem to be working as of now. 11 | // * The reason is possibly related to us not having an

20 | 30 |
31 | ); 32 | } -------------------------------------------------------------------------------- /src/modules/blocks/blocks.types.ts: -------------------------------------------------------------------------------- 1 | import type { WordsDiff } from './wordsdiff/RenderWordsDiff'; 2 | 3 | 4 | export type RenderBlockInputs = BlockInput[]; 5 | 6 | 7 | // In order of priority from the most frequent to the least 8 | type BlockInput = { 9 | bkId?: string; 10 | /* Other fields remain the same */ 11 | } & ({ 12 | /* Rendered as markdown or plain text */ 13 | bkt: 'md-bk'; 14 | content: string; 15 | } | { 16 | /* Rendered as Code (can be copied, LiveFile'd, etc) */ 17 | // NOTE: this should actually be called 'Fenced' block? 18 | bkt: 'code-bk'; 19 | title: string; 20 | code: string; 21 | lines: number; 22 | isPartial: boolean; 23 | } | { 24 | /* Rendered as HTML (dangerous) */ 25 | bkt: 'dang-html-bk'; 26 | html: string; 27 | } | { 28 | /* (Markdown Image) Rendered as an image */ 29 | bkt: 'img-url-bk'; 30 | url: string; 31 | alt?: string; 32 | } | { 33 | /* Rendered as red/green text diffs */ 34 | bkt: 'txt-diffs-bk'; 35 | wordsDiff: WordsDiff; 36 | }); -------------------------------------------------------------------------------- /src/modules/blocks/code/RenderCode.css: -------------------------------------------------------------------------------- 1 | /* Used for rendering line numbers in code blocks */ 2 | /* adapted from: prismjs/plugins/line-numbers/prism-line-numbers.css */ 3 | 4 | code.line-numbers { 5 | --ln-padding: 4.5em; 6 | padding-left: var(--ln-padding); 7 | 8 | > .code-container { 9 | position: relative; 10 | counter-reset: linenumber; 11 | /*white-space: inherit;*/ 12 | } 13 | 14 | .line-numbers-rows { 15 | position: absolute; 16 | top: 0; 17 | left: calc(-1 * var(--ln-padding)); 18 | width: calc(var(--ln-padding) - 0.8em); 19 | font-size: 100%; 20 | letter-spacing: -1px; 21 | /*noinspection CssUnresolvedCustomProperty*/ 22 | border-right: 1px solid var(--joy-palette-primary-solidDisabledColor); 23 | 24 | -webkit-user-select: none; 25 | -moz-user-select: none; 26 | -ms-user-select: none; 27 | user-select: none; 28 | pointer-events: none; 29 | 30 | > span { 31 | display: block; 32 | counter-increment: linenumber; 33 | } 34 | 35 | > span:before { 36 | content: counter(linenumber); 37 | /*noinspection CssUnresolvedCustomProperty*/ 38 | color: var(--joy-palette-primary-solidDisabledColor); 39 | display: block; 40 | padding-right: 0.8em; 41 | font-weight: 400; 42 | text-align: right; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/modules/blocks/code/code-buttons/openInGoogleColab.tsx: -------------------------------------------------------------------------------- 1 | import { copyToClipboard } from '~/common/util/clipboardUtils'; 2 | 3 | 4 | export function isGoogleColabSupported(language: string | null) { 5 | return !!language && language === 'python'; 6 | } 7 | 8 | 9 | export function openInGoogleColab(code: string) { 10 | // Copy the code to the clipboard 11 | copyToClipboard(code, 'Python code'); 12 | 13 | // Open a new Google Colab notebook 14 | window.open('https://colab.research.google.com/#create=true', '_blank'); 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/blocks/code/code-renderers/RenderCodeSyntax.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Box } from '@mui/joy'; 4 | 5 | 6 | export function RenderCodeSyntax(props: { 7 | highlightedSyntaxAsHtml: string | null; 8 | presenterMode?: boolean; 9 | }) { 10 | return ( 11 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/blocks/enhanced-code/codeCollapseManager.ts: -------------------------------------------------------------------------------- 1 | class CodeCollapseManager extends EventTarget { 2 | private static instance: CodeCollapseManager | null = null; 3 | 4 | private constructor() { 5 | super(); 6 | } 7 | 8 | static getInstance(): CodeCollapseManager { 9 | if (!CodeCollapseManager.instance) { 10 | CodeCollapseManager.instance = new CodeCollapseManager(); 11 | } 12 | return CodeCollapseManager.instance; 13 | } 14 | 15 | // called by the menu 16 | triggerCollapseAll(collapse: boolean) { 17 | this.dispatchEvent(new CustomEvent('codeCollapseAll', { detail: collapse })); 18 | } 19 | 20 | // useEffect'd by the EhancedRenderCode component 21 | addCollapseAllListener(onCodeCollapse: (collapse: boolean) => void) { 22 | const handleCollapse = (event: Event) => { 23 | const customEvent = event as CustomEvent; 24 | onCodeCollapse(customEvent.detail); 25 | }; 26 | this.addEventListener('codeCollapseAll', handleCollapse); 27 | return () => this.removeEventListener('codeCollapseAll', handleCollapse); 28 | } 29 | 30 | } 31 | 32 | export function getCodeCollapseManager(): CodeCollapseManager { 33 | return CodeCollapseManager.getInstance(); 34 | } -------------------------------------------------------------------------------- /src/modules/blocks/markdown/markdown.wrapper.ts: -------------------------------------------------------------------------------- 1 | export function wrapWithMarkdownSyntax(text: string, marker: '~~' | '**' | '=='): string { 2 | // Extract leading and trailing spaces 3 | const startMatch = text.match(/^\s*/); 4 | const endMatch = text.match(/\s*$/); 5 | 6 | const startSpaces = startMatch ? startMatch[0] : ''; 7 | const endSpaces = endMatch ? endMatch[0] : ''; 8 | 9 | // Trim the inner content and escape special characters 10 | const innerContent = text.trim(); 11 | const escapedContent = innerContent.replace(/([\\`*_\[\]{}()#+\-.!])/g, '\\$1'); 12 | 13 | // Wrap the inner content with the specified markers 14 | return `${startSpaces}${marker}${escapedContent}${marker}${endSpaces}`; 15 | } -------------------------------------------------------------------------------- /src/modules/blocks/plaintext/RenderPlainText.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { SxProps } from '@mui/joy/styles/types'; 4 | import { Chip, Typography } from '@mui/joy'; 5 | 6 | import { extractChatCommand } from '../../../apps/chat/commands/commands.registry'; 7 | 8 | 9 | const _style = { 10 | mx: 1.5, 11 | // display: 'flex', // Commented on 2023-12-29: the commands were drawn as columns 12 | alignItems: 'baseline', 13 | overflowWrap: 'anywhere', 14 | whiteSpace: 'break-spaces', 15 | } as const; 16 | 17 | 18 | /** 19 | * Renders a text block with chat commands. 20 | * NOTE: should remove the commands parsing dependency. 21 | */ 22 | export const RenderPlainText = (props: { content: string; sx?: SxProps; }) => { 23 | 24 | const elements = extractChatCommand(props.content); 25 | 26 | const memoSx = React.useMemo(() => ({ ..._style, ...props.sx }), [props.sx]); 27 | 28 | return ( 29 | 30 | {elements.map((element, index) => 31 | 32 | {element.type === 'cmd' 33 | ? <> 34 | 35 | {element.command} 36 | 37 | {element.params} 38 | 39 | : {element.value} 40 | } 41 | , 42 | )} 43 | 44 | ); 45 | }; -------------------------------------------------------------------------------- /src/modules/google/search.client.ts: -------------------------------------------------------------------------------- 1 | import { apiAsync } from '~/common/util/trpc.client'; 2 | 3 | import { Search } from './search.types'; 4 | import { useGoogleSearchStore } from './store-module-google'; 5 | 6 | 7 | export const isValidGoogleCloudApiKey = (apiKey?: string) => !!apiKey && apiKey.trim()?.length >= 39; 8 | export const isValidGoogleCseId = (cseId?: string) => !!cseId && cseId.trim()?.length >= 17; 9 | 10 | 11 | /** 12 | * This function either returns the Search JSON response, or throws a descriptive error string 13 | */ 14 | export async function callApiSearchGoogle(query: string, items: number, restrictToDomain?: string): Promise<{ pages: Search.API.BriefResult[] }> { 15 | 16 | // get the keys (empty if they're on server) 17 | const { googleCloudApiKey, googleCSEId, restrictToDomain: defaultRestrictToDomain } = useGoogleSearchStore.getState(); 18 | 19 | try { 20 | return await apiAsync.googleSearch.search.query({ 21 | query, 22 | items, 23 | key: googleCloudApiKey, 24 | cx: googleCSEId, 25 | restrictToDomain: restrictToDomain || defaultRestrictToDomain || null, 26 | }); 27 | } catch (error: any) { 28 | const errorMessage = error?.message || error?.toString() || 'Unknown error'; 29 | console.error(`callApiSearchGoogle: ${errorMessage}`); 30 | throw new Error(errorMessage); 31 | } 32 | } -------------------------------------------------------------------------------- /src/modules/google/store-module-google.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | import { persist } from 'zustand/middleware'; 3 | 4 | 5 | interface ModuleGoogleSearchStore { 6 | 7 | // Google Custom Search settings 8 | 9 | googleCloudApiKey: string; 10 | setGoogleCloudApiKey: (googleApiKey: string) => void; 11 | 12 | googleCSEId: string; 13 | setGoogleCSEId: (cseId: string) => void; 14 | 15 | restrictToDomain: string; 16 | setRestrictToDomain: (domain: string) => void; 17 | 18 | } 19 | 20 | export const useGoogleSearchStore = create()( 21 | persist( 22 | (set) => ({ 23 | 24 | // Google Custom Search settings 25 | 26 | googleCloudApiKey: '', 27 | setGoogleCloudApiKey: (googleApiKey: string) => set({ googleCloudApiKey: googleApiKey }), 28 | 29 | googleCSEId: '', 30 | setGoogleCSEId: (cseId: string) => set({ googleCSEId: cseId }), 31 | 32 | restrictToDomain: '', 33 | setRestrictToDomain: (domain: string) => set({ restrictToDomain: domain }), 34 | 35 | }), 36 | { 37 | name: 'app-module-google-search', 38 | }), 39 | ); -------------------------------------------------------------------------------- /src/modules/llms/llm.client.hooks.ts: -------------------------------------------------------------------------------- 1 | import type { TRPCClientErrorBase } from '@trpc/client'; 2 | import { useQuery } from '@tanstack/react-query'; 3 | 4 | import type { DModelsService } from '~/common/stores/llms/llms.service.types'; 5 | 6 | import type { ModelDescriptionSchema } from './server/llm.server.types'; 7 | import { llmsUpdateModelsForServiceOrThrow } from './llm.client'; 8 | 9 | 10 | /** 11 | * Hook that fetches the list of models from the vendor and updates the store, 12 | * while returning the fetch state. 13 | */ 14 | export function useLlmUpdateModels( 15 | enabled: boolean, 16 | service: DModelsService | null, 17 | // discardUserEdits?: boolean, 18 | ): { 19 | isFetching: boolean, 20 | refetch: () => void, 21 | isError: boolean, 22 | error: TRPCClientErrorBase | null 23 | } { 24 | return useQuery<{ models: ModelDescriptionSchema[] }, TRPCClientErrorBase | null>({ 25 | enabled: enabled && !!service, 26 | queryKey: ['list-models', service?.id || 'missing-service'], 27 | queryFn: async () => { 28 | if (!service) 29 | throw new Error('No service provided to fetch models for'); 30 | return await llmsUpdateModelsForServiceOrThrow(service.id, true /*!discardUserEdits*/); 31 | }, 32 | staleTime: Infinity, 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/llms/server/openai/fireworksai.wiretypes.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | 4 | // [Fireworks AI] Models List API - Response 5 | 6 | export const wireFireworksAIListOutputSchema = z.array(z.object({ 7 | 8 | id: z.string(), 9 | object: z.literal('model'), 10 | owned_by: z.union([ 11 | z.literal('fireworks'), 12 | z.literal('yi-01-ai'), 13 | z.string(), 14 | ]), 15 | created: z.number(), 16 | kind: z.union([ 17 | z.literal('HF_BASE_MODEL'), 18 | z.literal('HF_PEFT_ADDON'), 19 | z.literal('FLUMINA_BASE_MODEL'), 20 | z.string(), 21 | ]).optional(), 22 | // these seem to be there all the time, but just in case make them optional 23 | supports_chat: z.boolean().optional(), 24 | supports_image_input: z.boolean().optional(), 25 | supports_tools: z.boolean().optional(), 26 | // Not all models have this, so make it optional 27 | context_length: z.number().optional(), 28 | })); 29 | -------------------------------------------------------------------------------- /src/modules/llms/server/openai/groq.wiretypes.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | 4 | // [Groq] Models List API - Response 5 | 6 | export const wireGroqModelsListOutputSchema = z.object({ 7 | id: z.string(), 8 | object: z.literal('model'), 9 | created: z.number(), 10 | owned_by: z.string(), 11 | // Groq-specific 12 | active: z.boolean(), 13 | context_window: z.number(), 14 | // public_apps: z.any(), 15 | max_completion_tokens: z.number(), // first found on 2025-04-16 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /src/modules/llms/server/openai/localai.wiretypes.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | 4 | export const wireLocalAIModelsAvailableOutputSchema = z.array(z.object({ 5 | name: z.string(), // (e.g.) tinydream 6 | url: z.string(), // (e.g.) github:go-skynet/model-gallery/tinydream.yaml 7 | license: z.string().optional(), // (e.g.) other 8 | gallery: z.object({ 9 | url: z.string(), // (e.g.) github:go-skynet/model-gallery/index.yaml 10 | name: z.string(), // (e.g.) model-gallery 11 | }), 12 | urls: z.array(z.string()).optional(), 13 | files: z.array(z.object({ 14 | filename: z.string(), // voice-en-us-amy-low.tar.gz 15 | uri: z.string(), // https://github.com/rhasspy/piper/releases/download/v0.0.2/voice-en-us-amy-low.tar.gz 16 | sha256: z.string().optional(), // often empty 17 | })).optional(), 18 | })).nullable(); // null if galleries are not served 19 | 20 | export const wilreLocalAIModelsApplyOutputSchema = z.object({ 21 | uuid: z.string().uuid(), 22 | status: z.string().url(), 23 | }); 24 | 25 | export const wireLocalAIModelsListOutputSchema = z.object({ 26 | file_name: z.string(), 27 | error: z.string().nullable(), 28 | processed: z.boolean(), 29 | message: z.string().nullable(), 30 | progress: z.number(), 31 | file_size: z.string(), 32 | downloaded_size: z.string(), 33 | }); -------------------------------------------------------------------------------- /src/modules/llms/server/openai/mistral.wiretypes.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | 4 | // [Mistral] Models List API - Response 5 | 6 | export const wireMistralModelsListOutputSchema = z.object({ 7 | id: z.string(), 8 | object: z.literal('model'), 9 | created: z.number(), 10 | owned_by: z.string(), 11 | root: z.null().optional(), 12 | parent: z.null().optional(), 13 | // permission: z.array(wireMistralModelsListPermissionsSchema) 14 | }); 15 | 16 | // export type WireMistralModelsListOutput = z.infer; 17 | 18 | /* 19 | const wireMistralModelsListPermissionsSchema = z.object({ 20 | id: z.string(), 21 | object: z.literal('model_permission'), 22 | created: z.number(), 23 | allow_create_engine: z.boolean(), 24 | allow_sampling: z.boolean(), 25 | allow_logprobs: z.boolean(), 26 | allow_search_indices: z.boolean(), 27 | allow_view: z.boolean(), 28 | allow_fine_tuning: z.boolean(), 29 | organization: z.string(), 30 | group: z.null().optional(), 31 | is_blocking: z.boolean() 32 | }); 33 | */ -------------------------------------------------------------------------------- /src/modules/llms/server/openai/openpipe.wiretypes.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | 4 | // @upstream: https://docs.openpipe.ai/api-reference/get-listModels 5 | 6 | const openpipeSchema = z.object({ 7 | baseModel: z.string(), 8 | status: z.enum(['PENDING', 'TRAINING', 'DEPLOYED', 'ERROR', 'DEPRECATED']), 9 | datasetId: z.string(), 10 | errorMessage: z.string().nullable(), 11 | }); 12 | 13 | 14 | export const wireOpenPipeModelOutputSchema = z.object({ 15 | id: z.string(), 16 | name: z.string(), 17 | description: z.string().nullable(), 18 | created: z.string(), // ISO string 19 | updated: z.string(), // ISO string 20 | openpipe: openpipeSchema, 21 | contextWindow: z.number(), 22 | maxCompletionTokens: z.number(), 23 | capabilities: z.array(z.enum(['chat', 'tools', 'json'])), 24 | pricing: z.object({ 25 | chatIn: z.number(), 26 | chatOut: z.number(), 27 | }).optional(), 28 | }); -------------------------------------------------------------------------------- /src/modules/llms/server/openai/openrouter.wiretypes.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | 4 | export const wireOpenrouterModelsListOutputSchema = z.object({ 5 | id: z.string(), 6 | name: z.string(), 7 | description: z.string(), 8 | // NOTE: for 'openrouter/auto', this is: { 9 | // "prompt": "-1", 10 | // "completion": "-1" 11 | // } 12 | pricing: z.object({ 13 | prompt: z.string(), 14 | completion: z.string(), 15 | image: z.string().optional(), 16 | request: z.string().optional(), 17 | }), 18 | context_length: z.number(), 19 | architecture: z.object({ 20 | modality: z.string(), // z.enum(['text', 'multimodal', 'text+image->text]), 21 | tokenizer: z.string(), // e.g. 'Mistral', 'Claude' 22 | instruct_type: z.string().nullable(), 23 | }), 24 | top_provider: z.object({ 25 | max_completion_tokens: z.number().nullable(), 26 | is_moderated: z.boolean(), // false means that the user will need to do moderation, and likely this has lower latency 27 | }), 28 | 29 | // when logged in 30 | per_request_limits: z.object({ 31 | prompt_tokens: z.string(), 32 | completion_tokens: z.string(), 33 | }).nullable(), // null on 'openrouter/auto' 34 | }); -------------------------------------------------------------------------------- /src/modules/llms/server/openai/togetherai.wiretypes.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | 4 | // [Together AI] Models List API - Response 5 | 6 | export const wireTogetherAIListOutputSchema = z.array(z.object({ 7 | id: z.string(), 8 | object: z.literal('model'), 9 | created: z.number(), 10 | type: z.string(), // e.g., 'chat', 'language', 'image', 'embedding' 11 | running: z.boolean(), 12 | display_name: z.string(), 13 | 14 | organization: z.string().nullable().optional(), 15 | link: z.string().nullable().optional(), 16 | license: z.string().nullable().optional(), 17 | context_length: z.number().optional(), 18 | 19 | // Configuration object 20 | // config: z.object({ 21 | // chat_template: z.string().nullable(), 22 | // stop: z.array(z.string()), 23 | // bos_token: z.string().nullable(), 24 | // eos_token: z.string().nullable(), 25 | // }).optional(), 26 | 27 | // Pricing information 28 | pricing: z.object({ 29 | hourly: z.number(), 30 | input: z.number(), 31 | output: z.number(), 32 | base: z.number(), 33 | finetune: z.number(), 34 | }), 35 | })); 36 | 37 | // export type WireTogetherAIListOutput = z.infer; 38 | -------------------------------------------------------------------------------- /src/modules/llms/vendors/alibaba/alibaba.vendor.ts: -------------------------------------------------------------------------------- 1 | import { AlibabaCloudIcon } from '~/common/components/icons/vendors/AlibabaCloudIcon'; 2 | 3 | import type { IModelVendor } from '../IModelVendor'; 4 | import type { OpenAIAccessSchema } from '../../server/openai/openai.router'; 5 | import { ModelVendorOpenAI } from '../openai/openai.vendor'; 6 | 7 | import { AlibabaServiceSetup } from './AlibabaServiceSetup'; 8 | 9 | 10 | interface DAlibabaServiceSettings { 11 | alibabaOaiKey: string; 12 | alibabaOaiHost: string; 13 | } 14 | 15 | export const ModelVendorAlibaba: IModelVendor = { 16 | id: 'alibaba', 17 | name: 'Alibaba Cloud', 18 | displayRank: 35, 19 | location: 'cloud', 20 | instanceLimit: 1, 21 | hasServerConfigKey: 'hasLlmAlibaba', 22 | 23 | // components 24 | Icon: AlibabaCloudIcon, 25 | ServiceSetupComponent: AlibabaServiceSetup, 26 | 27 | // functions 28 | initializeSetup: () => ({ 29 | alibabaOaiKey: '', 30 | alibabaOaiHost: '', 31 | }), 32 | validateSetup: (setup) => { 33 | return setup.alibabaOaiKey?.length >= 32; 34 | }, 35 | getTransportAccess: (partialSetup) => ({ 36 | dialect: 'alibaba', 37 | oaiKey: partialSetup?.alibabaOaiKey || '', 38 | oaiOrg: '', 39 | oaiHost: partialSetup?.alibabaOaiHost || '', 40 | heliKey: '', 41 | moderationCheck: false, 42 | }), 43 | 44 | // OpenAI transport ('alibaba' dialect in 'access') 45 | rpcUpdateModelsOrThrow: ModelVendorOpenAI.rpcUpdateModelsOrThrow, 46 | }; 47 | -------------------------------------------------------------------------------- /src/modules/llms/vendors/deepseek/deepseekai.vendor.ts: -------------------------------------------------------------------------------- 1 | import { DeepseekIcon } from '~/common/components/icons/vendors/DeepseekIcon'; 2 | 3 | import type { IModelVendor } from '../IModelVendor'; 4 | import type { OpenAIAccessSchema } from '../../server/openai/openai.router'; 5 | 6 | import { ModelVendorOpenAI } from '../openai/openai.vendor'; 7 | 8 | import { DeepseekAIServiceSetup } from './DeepseekAIServiceSetup'; 9 | 10 | 11 | export interface DDeepseekServiceSettings { 12 | deepseekKey: string; 13 | } 14 | 15 | export const ModelVendorDeepseek: IModelVendor = { 16 | id: 'deepseek', 17 | name: 'Deepseek', 18 | displayRank: 16, 19 | location: 'cloud', 20 | instanceLimit: 1, 21 | hasServerConfigKey: 'hasLlmDeepseek', 22 | 23 | // components 24 | Icon: DeepseekIcon, 25 | ServiceSetupComponent: DeepseekAIServiceSetup, 26 | 27 | // functions 28 | initializeSetup: () => ({ 29 | deepseekKey: '', 30 | }), 31 | validateSetup: (setup) => { 32 | return setup.deepseekKey?.length >= 35; 33 | }, 34 | getTransportAccess: (partialSetup) => ({ 35 | dialect: 'deepseek', 36 | oaiKey: partialSetup?.deepseekKey || '', 37 | oaiOrg: '', 38 | oaiHost: '', 39 | heliKey: '', 40 | moderationCheck: false, 41 | }), 42 | 43 | // OpenAI transport ('Deepseek' dialect in 'access') 44 | rpcUpdateModelsOrThrow: ModelVendorOpenAI.rpcUpdateModelsOrThrow, 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /src/modules/llms/vendors/groq/groq.vendor.ts: -------------------------------------------------------------------------------- 1 | import { GroqIcon } from '~/common/components/icons/vendors/GroqIcon'; 2 | 3 | import type { IModelVendor } from '../IModelVendor'; 4 | import type { OpenAIAccessSchema } from '../../server/openai/openai.router'; 5 | 6 | import { ModelVendorOpenAI } from '../openai/openai.vendor'; 7 | 8 | import { GroqServiceSetup } from './GroqServiceSetup'; 9 | 10 | 11 | interface DGroqServiceSettings { 12 | groqKey: string; 13 | } 14 | 15 | export const ModelVendorGroq: IModelVendor = { 16 | id: 'groq', 17 | name: 'Groq', 18 | displayRank: 32, 19 | location: 'cloud', 20 | instanceLimit: 1, 21 | hasServerConfigKey: 'hasLlmGroq', 22 | 23 | // components 24 | Icon: GroqIcon, 25 | ServiceSetupComponent: GroqServiceSetup, 26 | 27 | // functions 28 | initializeSetup: () => ({ 29 | groqKey: '', 30 | }), 31 | validateSetup: (setup) => { 32 | return setup.groqKey?.length >= 50; 33 | }, 34 | getTransportAccess: (partialSetup) => ({ 35 | dialect: 'groq', 36 | oaiKey: partialSetup?.groqKey || '', 37 | oaiOrg: '', 38 | oaiHost: '', 39 | heliKey: '', 40 | moderationCheck: false, 41 | }), 42 | 43 | // OpenAI transport ('Groq' dialect in 'access') 44 | rpcUpdateModelsOrThrow: ModelVendorOpenAI.rpcUpdateModelsOrThrow, 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /src/modules/llms/vendors/lmstudio/lmstudio.vendor.ts: -------------------------------------------------------------------------------- 1 | import type { IModelVendor } from '../IModelVendor'; 2 | import type { OpenAIAccessSchema } from '../../server/openai/openai.router'; 3 | 4 | import { ModelVendorOpenAI } from '../openai/openai.vendor'; 5 | 6 | import { LMStudioServiceSetup } from './LMStudioServiceSetup'; 7 | import { LMStudioIcon } from '~/common/components/icons/vendors/LMStudioIcon'; 8 | 9 | 10 | interface DLMStudioServiceSettings { 11 | oaiHost: string; // use OpenAI-compatible non-default hosts (full origin path) 12 | } 13 | 14 | export const ModelVendorLMStudio: IModelVendor = { 15 | id: 'lmstudio', 16 | name: 'LM Studio', 17 | displayRank: 52, 18 | location: 'local', 19 | instanceLimit: 1, 20 | 21 | // components 22 | Icon: LMStudioIcon, 23 | ServiceSetupComponent: LMStudioServiceSetup, 24 | 25 | // functions 26 | initializeSetup: () => ({ 27 | oaiHost: 'http://localhost:1234', 28 | }), 29 | getTransportAccess: (partialSetup) => ({ 30 | dialect: 'lmstudio', 31 | oaiKey: '', 32 | oaiOrg: '', 33 | oaiHost: partialSetup?.oaiHost || '', 34 | heliKey: '', 35 | moderationCheck: false, 36 | }), 37 | 38 | // OpenAI transport ('lmstudio' dialect in 'access') 39 | rpcUpdateModelsOrThrow: ModelVendorOpenAI.rpcUpdateModelsOrThrow, 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /src/modules/llms/vendors/ollama/ollama.vendor.ts: -------------------------------------------------------------------------------- 1 | import { OllamaIcon } from '~/common/components/icons/vendors/OllamaIcon'; 2 | import { apiAsync } from '~/common/util/trpc.client'; 3 | 4 | import type { IModelVendor } from '../IModelVendor'; 5 | import type { OllamaAccessSchema } from '../../server/ollama/ollama.router'; 6 | 7 | import { OllamaServiceSetup } from './OllamaServiceSetup'; 8 | 9 | 10 | interface DOllamaServiceSettings { 11 | ollamaHost: string; 12 | ollamaJson: boolean; 13 | } 14 | 15 | 16 | export const ModelVendorOllama: IModelVendor = { 17 | id: 'ollama', 18 | name: 'Ollama', 19 | displayRank: 54, 20 | location: 'local', 21 | instanceLimit: 2, 22 | hasServerConfigKey: 'hasLlmOllama', 23 | 24 | // components 25 | Icon: OllamaIcon, 26 | ServiceSetupComponent: OllamaServiceSetup, 27 | 28 | // functions 29 | getTransportAccess: (partialSetup): OllamaAccessSchema => ({ 30 | dialect: 'ollama', 31 | ollamaHost: partialSetup?.ollamaHost || '', 32 | ollamaJson: partialSetup?.ollamaJson || false, 33 | }), 34 | 35 | // List Models 36 | rpcUpdateModelsOrThrow: async (access) => await apiAsync.llmOllama.listModels.query({ access }), 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /src/modules/llms/vendors/perplexity/perplexity.vendor.ts: -------------------------------------------------------------------------------- 1 | import { PerplexityIcon } from '~/common/components/icons/vendors/PerplexityIcon'; 2 | 3 | import type { IModelVendor } from '../IModelVendor'; 4 | import type { OpenAIAccessSchema } from '../../server/openai/openai.router'; 5 | 6 | import { ModelVendorOpenAI } from '../openai/openai.vendor'; 7 | 8 | import { PerplexityServiceSetup } from './PerplexityServiceSetup'; 9 | 10 | 11 | interface DPerpexityServiceSettings { 12 | perplexityKey: string; 13 | } 14 | 15 | export const ModelVendorPerplexity: IModelVendor = { 16 | id: 'perplexity', 17 | name: 'Perplexity', 18 | displayRank: 20, 19 | location: 'cloud', 20 | instanceLimit: 1, 21 | hasServerConfigKey: 'hasLlmPerplexity', 22 | 23 | // components 24 | Icon: PerplexityIcon, 25 | ServiceSetupComponent: PerplexityServiceSetup, 26 | 27 | // functions 28 | initializeSetup: () => ({ 29 | perplexityKey: '', 30 | }), 31 | validateSetup: (setup) => { 32 | return setup.perplexityKey?.length >= 50; 33 | }, 34 | getTransportAccess: (partialSetup) => ({ 35 | dialect: 'perplexity', 36 | oaiKey: partialSetup?.perplexityKey || '', 37 | oaiOrg: '', 38 | oaiHost: '', 39 | heliKey: '', 40 | moderationCheck: false, 41 | }), 42 | 43 | // OpenAI transport ('perplexity' dialect in 'access') 44 | rpcUpdateModelsOrThrow: ModelVendorOpenAI.rpcUpdateModelsOrThrow, 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /src/modules/llms/vendors/vendor.helpers.ts: -------------------------------------------------------------------------------- 1 | import { getBackendCapabilities } from '~/modules/backend/store-backend-capabilities'; 2 | 3 | import type { DModelsServiceId } from '~/common/stores/llms/llms.service.types'; 4 | import { findModelsServiceOrNull } from '~/common/stores/llms/store-llms'; 5 | 6 | import type { IModelVendor } from './IModelVendor'; 7 | import { findModelVendor } from './vendors.registry'; 8 | 9 | 10 | 11 | export function findServiceAccessOrThrow(serviceId: DModelsServiceId) { 12 | 13 | const service = findModelsServiceOrNull(serviceId); 14 | if (!service) 15 | throw new Error(`Models Service ${serviceId} not found`); 16 | 17 | const vendor = findModelVendor(service.vId); 18 | if (!vendor) 19 | throw new Error(`Model Service ${serviceId} has no vendor`); 20 | 21 | return { 22 | service, 23 | serviceSettings: service.setup, 24 | transportAccess: vendor.getTransportAccess(service.setup), 25 | vendor, 26 | }; 27 | } 28 | 29 | export function vendorHasBackendCap = {}, TAccess = unknown>(vendor: IModelVendor) { 30 | const backendCaps = getBackendCapabilities(); 31 | return vendor.hasServerConfigFn ? vendor.hasServerConfigFn(backendCaps) 32 | : vendor.hasServerConfigKey ? !!backendCaps[vendor.hasServerConfigKey] : false; 33 | } 34 | -------------------------------------------------------------------------------- /src/modules/llms/vendors/xai/xai.vendor.ts: -------------------------------------------------------------------------------- 1 | import { XAIIcon } from '~/common/components/icons/vendors/XAIIcon'; 2 | 3 | import type { IModelVendor } from '../IModelVendor'; 4 | import type { OpenAIAccessSchema } from '../../server/openai/openai.router'; 5 | 6 | import { ModelVendorOpenAI } from '../openai/openai.vendor'; 7 | 8 | import { XAIServiceSetup } from './XAIServiceSetup'; 9 | 10 | 11 | export interface DXAIServiceSettings { 12 | xaiKey: string; 13 | } 14 | 15 | export const ModelVendorXAI: IModelVendor = { 16 | id: 'xai', 17 | name: 'xAI', 18 | displayRank: 15, 19 | location: 'cloud', 20 | instanceLimit: 1, 21 | hasServerConfigKey: 'hasLlmXAI', 22 | 23 | // Components 24 | Icon: XAIIcon, 25 | ServiceSetupComponent: XAIServiceSetup, 26 | 27 | // functions 28 | initializeSetup: () => ({ xaiKey: '' }), 29 | validateSetup: setup => setup.xaiKey?.length >= 80, // we assume all API keys are 80 chars+ - we won't have a strict validation 30 | getTransportAccess: (partialSetup) => ({ 31 | dialect: 'xai', 32 | oaiKey: partialSetup?.xaiKey || '', 33 | oaiOrg: '', 34 | oaiHost: '', 35 | heliKey: '', 36 | moderationCheck: false, 37 | }), 38 | 39 | // OpenAI transport ('xai' dialect in 'access') 40 | rpcUpdateModelsOrThrow: ModelVendorOpenAI.rpcUpdateModelsOrThrow, 41 | 42 | }; 43 | -------------------------------------------------------------------------------- /src/modules/t2i/store-module-t2i.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | import { persist } from 'zustand/middleware'; 3 | 4 | 5 | interface TextToImageStore { 6 | 7 | activeProviderId: string | null; 8 | setActiveProviderId: (providerId: string | null) => void; 9 | 10 | } 11 | 12 | export const useTextToImageStore = create()( 13 | persist( 14 | (_set) => ({ 15 | 16 | activeProviderId: null, // null: will auto-select the first availabe provider 17 | setActiveProviderId: (activeProviderId: string | null) => _set({ activeProviderId }), 18 | 19 | }), 20 | { 21 | name: 'app-module-t2i', 22 | version: 1, 23 | }), 24 | ); -------------------------------------------------------------------------------- /src/modules/trade/TradeModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import type { DConversationId } from '~/common/stores/chat/chat.conversation'; 4 | import { GoodModal } from '~/common/components/modals/GoodModal'; 5 | 6 | import { ExportChats, ExportConfig } from './ExportChats'; 7 | import { ImportChats, ImportConfig } from './ImportChats'; 8 | 9 | export type TradeConfig = ImportConfig | ExportConfig; 10 | 11 | export function TradeModal(props: { config: TradeConfig, onConversationActivate: (conversationId: DConversationId) => void, onClose: () => void }) { 12 | return ( 13 | 17 | {props.config.dir === 'import' ? 'Import ' : props.config.dir === 'export' ? 'Export ' : ''} {(props.config.dir === 'export' && !props.config.exportAll) ? 'conversation' : 'conversations'} 18 | } 19 | > 20 | 21 | {props.config.dir === 'import' && ( 22 | 23 | )} 24 | 25 | {props.config.dir === 'export' && ( 26 | 27 | )} 28 | 29 | 30 | ); 31 | } -------------------------------------------------------------------------------- /src/modules/youtube/youtube.types.ts: -------------------------------------------------------------------------------- 1 | export interface YouTubeVideoData { 2 | videoId: string; 3 | videoTitle: string; 4 | videoDescription: string; 5 | thumbnailUrl: string; 6 | thumbnailImage: null | { 7 | imgDataUrl: string; 8 | mimeType: string; 9 | width: number; 10 | height: number; 11 | }; 12 | transcript: string; 13 | } -------------------------------------------------------------------------------- /src/server/prisma/prismaDb.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | 3 | 4 | const globalForPrisma = globalThis as unknown as { 5 | prisma: PrismaClient | undefined; 6 | }; 7 | 8 | export const prismaDb = 9 | globalForPrisma.prisma ?? 10 | new PrismaClient({ 11 | log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], 12 | }); 13 | 14 | if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prismaDb; 15 | -------------------------------------------------------------------------------- /src/server/trpc/trpc.nanoid.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid'; 2 | 3 | /** 4 | * There's a copy of this function in src/common/util/idUtils.ts, but that one is for client-side use. 5 | * This one is for server-side use. 6 | */ 7 | export function serverSideId(_serverScope: 'aix-tool-call-id' | 'aix-tool-response-id', digits?: number) { 8 | return 'aix_' + nanoid(digits); 9 | } 10 | -------------------------------------------------------------------------------- /src/server/trpc/trpc.router-cloud.ts: -------------------------------------------------------------------------------- 1 | import { createTRPCRouter } from './trpc.server'; 2 | 3 | import { browseRouter } from '~/modules/browse/browse.router'; 4 | import { tradeRouter } from '~/modules/trade/server/trade.router'; 5 | 6 | /** 7 | * Cloud rooter, which is geolocated in 1 location and separate from the other routers. 8 | * NOTE: at the time of writing, the location is aws|us-east-1 9 | */ 10 | export const appRouterCloud = createTRPCRouter({ 11 | browse: browseRouter, 12 | trade: tradeRouter, 13 | }); 14 | 15 | // export type definition of API 16 | export type AppRouterCloud = typeof appRouterCloud; -------------------------------------------------------------------------------- /src/server/trpc/trpc.router-edge.ts: -------------------------------------------------------------------------------- 1 | import { createTRPCRouter } from './trpc.server'; 2 | 3 | import { aixRouter } from '~/modules/aix/server/api/aix.router'; 4 | import { backendRouter } from '~/modules/backend/backend.router'; 5 | import { elevenlabsRouter } from '~/modules/elevenlabs/elevenlabs.router'; 6 | import { googleSearchRouter } from '~/modules/google/search.router'; 7 | import { llmAnthropicRouter } from '~/modules/llms/server/anthropic/anthropic.router'; 8 | import { llmGeminiRouter } from '~/modules/llms/server/gemini/gemini.router'; 9 | import { llmOllamaRouter } from '~/modules/llms/server/ollama/ollama.router'; 10 | import { llmOpenAIRouter } from '~/modules/llms/server/openai/openai.router'; 11 | import { prodiaRouter } from '~/modules/t2i/prodia/prodia.router'; 12 | import { youtubeRouter } from '~/modules/youtube/youtube.router'; 13 | 14 | /** 15 | * Primary rooter, and will be sitting on an Edge Runtime. 16 | */ 17 | export const appRouterEdge = createTRPCRouter({ 18 | aix: aixRouter, 19 | backend: backendRouter, 20 | elevenlabs: elevenlabsRouter, 21 | googleSearch: googleSearchRouter, 22 | llmAnthropic: llmAnthropicRouter, 23 | llmGemini: llmGeminiRouter, 24 | llmOllama: llmOllamaRouter, 25 | llmOpenAI: llmOpenAIRouter, 26 | prodia: prodiaRouter, 27 | youtube: youtubeRouter, 28 | }); 29 | 30 | // export type definition of API 31 | export type AppRouterEdge = typeof appRouterEdge; -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enricoros/big-AGI/a51d5c315fb9997df28ddaf5ee732396a40df717/tools/.gitignore -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Base Options: */ 4 | "esModuleInterop": true, 5 | "skipLibCheck": true, 6 | "target": "es2022", 7 | "allowJs": true, 8 | "resolveJsonModule": true, 9 | "moduleDetection": "force", 10 | "isolatedModules": true, 11 | 12 | /* Strictness */ 13 | "strict": true, 14 | "noUncheckedIndexedAccess": false, 15 | "checkJs": true, 16 | 17 | /* Bundled projects */ 18 | "lib": ["dom", "dom.iterable", "ESNext"], 19 | "noEmit": true, 20 | "module": "ESNext", 21 | "moduleResolution": "Bundler", 22 | "jsx": "preserve", 23 | "plugins": [{"name": "next"}], 24 | "incremental": true, 25 | 26 | /* Custom options */ 27 | "forceConsistentCasingInFileNames": true, 28 | "jsxImportSource": "@emotion/react", 29 | 30 | /* Path Aliases */ 31 | "baseUrl": ".", 32 | "paths": { 33 | "~/common/*": ["src/common/*"], 34 | "~/modules/*": ["src/modules/*"], 35 | "~/server/*": ["src/server/*"] 36 | } 37 | }, 38 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", 39 | // this is here only because otherwise, during standalone build the process would update this file 40 | "dist/types/**/*.ts" 41 | ], 42 | "exclude": ["node_modules", "dist", "electron", "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"] 43 | } 44 | --------------------------------------------------------------------------------