diff --git a/.gitignore b/.gitignore index 9c47e6892..874bf6f95 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,7 @@ yarn-error.log* next-env.d.ts # other -.idea/ \ No newline at end of file +.idea/ + +# Ingore k8s/env-secret.yaml +./k8s/env-secret.yaml \ No newline at end of file diff --git a/docs/customizations.md b/docs/customizations.md index 2bce5edf4..9aeb40097 100644 --- a/docs/customizations.md +++ b/docs/customizations.md @@ -61,6 +61,7 @@ Test your application thoroughly using local development (refer to README.md for - [deploy-cloudflare.md](deploy-cloudflare.md): for Cloudflare Workers deployment - [deploy-docker.md](deploy-docker.md): for Docker deployment instructions and examples +- [deploy-k8s.md](deploy-k8s.md): for Kubernetes deployment instructions and examples ## Debugging diff --git a/docs/deploy-k8s.md b/docs/deploy-k8s.md new file mode 100644 index 000000000..c298799d2 --- /dev/null +++ b/docs/deploy-k8s.md @@ -0,0 +1,82 @@ +# Deploy `big-AGI` with Kubernetes ☸️ + +In this tutorial, we will guide you through the process of deploying big-AGI +in a Kubernetes environment using the kubectl command-line tool. + +## First Deployment + +### Step 1: Clone the big-AGI repository + +```bash +$ git clone https://github.com/enricoros/big-agi +$ cd ./big-agi/docs/k8s +``` + +### Step 2: Create the namespace + +```bash +$ kubectl create namespace ns-big-agi +``` + +### Step 3: Fill in the key information into env-secret.yaml + +All variables are optional. By default, Kubernetes Secret uses Base64 for +encode/decode, so please don't do a git commit after filling in the keys +to avoid leaking sensitive information. + +We provide an empty `env-secret.yaml` file as a template. +You can fill in the necessary information using a text editor. + +```bash +$ nano env-secret.yaml +``` + +### Step 4: Deploying Kubernetes Resources + +```bash +$ kubectl apply -f big-agi-deployment.yaml -f env-secret.yaml +``` + +### Step 5: Verifying the Resource Statuses + +```bash +$ kubectl -n ns-big-agi get svc,pod,deployment +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/svc-big-agi ClusterIP 10.0.198.118 3000/TCP 63m + +NAME READY STATUS RESTARTS AGE +pod/deployment-big-agi-xxxxxxxx-yyyyy 1/1 Running 0 39m + +NAME READY UP-TO-DATE AVAILABLE AGE +deployment.apps/deployment-big-agi 1/1 1 1 63m +``` + +### Step 6: Testing the Service + +You can test the service by port-forwarding the service to your local machine: + +```bash +$ kubectl -n ns-big-agi port-forward service/svc-big-agi 3000 +Forwarding from 127.0.0.1:3000 -> 3000 +Forwarding from [::1]:3000 -> 3000 +``` + +Now you can access the service at `http://localhost:3000`, and you should see the big-AGI homepage. + +## Updating big-AGI + +To update big-AGI to the latest version: + +1. Pull the latest changes from the repository: + ```bash + $ git pull origin main + ``` + +2. Apply the updated deployment: + ```bash + $ kubectl apply -f big-agi-deployment.yaml + ``` + +This will trigger a rolling update of the deployment with the latest image. + +Note: For production use, consider setting up an Ingress Controller or Load Balancer instead of using port-forward. \ No newline at end of file diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 7e8130dcf..e412213c0 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -27,6 +27,7 @@ AZURE_OPENAI_API_ENDPOINT= AZURE_OPENAI_API_KEY= ANTHROPIC_API_KEY= ANTHROPIC_API_HOST= +DEEPSEEK_API_KEY= GEMINI_API_KEY= GROQ_API_KEY= LOCALAI_API_HOST= @@ -40,25 +41,28 @@ TOGETHERAI_API_KEY= # Model Observability: Helicone HELICONE_API_KEY= -# Text-To-Speech -ELEVENLABS_API_KEY= -ELEVENLABS_API_HOST= -ELEVENLABS_VOICE_ID= -# Text-To-Image -PRODIA_API_KEY= -# Google Custom Search -GOOGLE_CLOUD_API_KEY= -GOOGLE_CSE_ID= # Browse PUPPETEER_WSS_ENDPOINT= -# Backend Analytics -BACKEND_ANALYTICS= +# Search +GOOGLE_CLOUD_API_KEY= +GOOGLE_CSE_ID= + +# Text-To-Speech: ElevenLabs +ELEVENLABS_API_KEY= +ELEVENLABS_API_HOST= +ELEVENLABS_VOICE_ID= +# Text-To-Image: Prodia +PRODIA_API_KEY= # Backend HTTP Basic Authentication (see `deploy-authentication.md` for turning on authentication) HTTP_BASIC_AUTH_USERNAME= HTTP_BASIC_AUTH_PASSWORD= +# Backend Analytics Flags +BACKEND_ANALYTICS= + + # Frontend variables NEXT_PUBLIC_GA4_MEASUREMENT_ID= NEXT_PUBLIC_PLANTUML_SERVER_URL= @@ -89,6 +93,7 @@ requiring the user to enter an API key | `AZURE_OPENAI_API_KEY` | Azure OpenAI API key, see [config-azure-openai.md](config-azure-openai.md) | Optional, but if set `AZURE_OPENAI_API_ENDPOINT` must also be set | | `ANTHROPIC_API_KEY` | The API key for Anthropic | Optional | | `ANTHROPIC_API_HOST` | Changes the backend host for the Anthropic vendor, to enable platforms such as [config-aws-bedrock.md](config-aws-bedrock.md) | Optional | +| `DEEPSEEK_API_KEY` | The API key for Deepseek AI | Optional | | `GEMINI_API_KEY` | The API key for Google AI's Gemini | Optional | | `GROQ_API_KEY` | The API key for Groq Cloud | Optional | | `LOCALAI_API_HOST` | Sets the URL of the LocalAI server, or defaults to http://127.0.0.1:8080 | Optional | diff --git a/docs/installation.md b/docs/installation.md index 5d28a068d..9b687e6f2 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -99,6 +99,41 @@ or follow the steps below for a quick start. ``` Access your big-AGI instance at `http://localhost:3000`. +### Kubernetes Deployment + +Deploy big-AGI on a Kubernetes cluster for enhanced scalability and management. Follow these steps for a Kubernetes deployment: + +1. Clone the big-AGI repository: + ```bash + git clone https://github.com/enricoros/big-AGI.git + cd big-AGI + ``` + +2. Configure the environment variables: + ```bash + cp docs/k8s/env-secret.yaml env-secret.yaml + vim env-secret.yaml # Edit the file to set your environment variables + ``` + +3. Apply the Kubernetes configurations: + ```bash + kubectl create namespace ns-big-agi + kubectl apply -f docs/k8s/big-agi-deployment.yaml -f env-secret.yaml + ``` + +4. Verify the deployment: + ```bash + kubectl -n ns-big-agi get svc,pod,deployment + ``` + +5. Access the big-AGI application: + ```bash + kubectl -n ns-big-agi port-forward service/svc-big-agi 3000:3000 + ``` + Your big-AGI instance is now accessible at `http://localhost:3000`. + +For more detailed instructions on Kubernetes deployment, including updating and troubleshooting, refer to our [Kubernetes Deployment Guide](deploy-k8s.md). + ### Midori AI Subsystem for Docker Deployment Follow the instructions found on [Midori AI Subsystem Site](https://io.midori-ai.xyz/subsystem/manager/) diff --git a/docs/k8s/big-agi-deployment.yaml b/docs/k8s/big-agi-deployment.yaml new file mode 100644 index 000000000..837768091 --- /dev/null +++ b/docs/k8s/big-agi-deployment.yaml @@ -0,0 +1,52 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ns-big-agi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: big-agi + name: deployment-big-agi + namespace: ns-big-agi +spec: + replicas: 1 + selector: + matchLabels: + app: big-agi + strategy: {} + template: + metadata: + labels: + app: big-agi + spec: + containers: + - image: ghcr.io/enricoros/big-agi:latest + name: big-agi + ports: + - containerPort: 3000 + args: + - next + - start + - -p + - "3000" + envFrom: + - secretRef: + name: env +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: big-agi + name: svc-big-agi + namespace: ns-big-agi +spec: + ports: + - name: "http" + port: 3000 + targetPort: 3000 + selector: + app: big-agi diff --git a/docs/k8s/env-secret.yaml b/docs/k8s/env-secret.yaml new file mode 100644 index 000000000..f19fb160f --- /dev/null +++ b/docs/k8s/env-secret.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: env + namespace: ns-big-agi +type: Opaque +stringData: + # IMPORTANT: This file contains sensitive information. Do not commit changes to version control. + # All variables are optional. Fill in only the ones you need. + # + # For the latest information on all the environment variables, see /docs/environment-variables.md + # + + # LLMs + OPENAI_API_KEY: "" + OPENAI_API_HOST: "" + OPENAI_API_ORG_ID: "" + AZURE_OPENAI_API_ENDPOINT: "" + AZURE_OPENAI_API_KEY: "" + ANTHROPIC_API_KEY: "" + ANTHROPIC_API_HOST: "" + GEMINI_API_KEY: "" + GROQ_API_KEY: "" + LOCALAI_API_HOST: "" + LOCALAI_API_KEY: "" + MISTRAL_API_KEY: "" + OLLAMA_API_HOST: "" + OPENROUTER_API_KEY: "" + PERPLEXITY_API_KEY: "" + TOGETHERAI_API_KEY: "" + DEEPSEEK_API_KEY: "" + + # Browse + PUPPETEER_WSS_ENDPOINT: "" + + # Search + GOOGLE_CLOUD_API_KEY: "" + GOOGLE_CSE_ID: "" + + # Text-To-Speech: Eleven Labs + ELEVENLABS_API_KEY: "" + ELEVENLABS_API_HOST: "" + ELEVENLABS_VOICE_ID: "" + + # Text-To-Image: Prodia + PRODIA_API_KEY: "" diff --git a/src/apps/chat/AppChat.tsx b/src/apps/chat/AppChat.tsx index ad58caa14..3fcbcc4e9 100644 --- a/src/apps/chat/AppChat.tsx +++ b/src/apps/chat/AppChat.tsx @@ -19,7 +19,7 @@ import { ConfirmationModal } from '~/common/components/ConfirmationModal'; import { ConversationsManager } from '~/common/chats/ConversationsManager'; import { DConversation, DConversationId } from '~/common/stores/chat/chat.conversation'; import { DMessageAttachmentFragment, DMessageContentFragment, duplicateDMessageFragments } from '~/common/stores/chat/chat.fragments'; -import { GlobalShortcutDefinition, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; +import { GlobalShortcutDefinition, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; import { PanelResizeInset } from '~/common/components/panes/GoodPanelResizeHandler'; import { PreferencesTab, useOptimaLayout, usePluggableOptimaLayout } from '~/common/layout/optima/useOptimaLayout'; import { ScrollToBottom } from '~/common/scroll-to-bottom/ScrollToBottom'; @@ -397,20 +397,19 @@ export function AppChat() { const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [ // focused conversation ['b', true, true, false, handleMessageBeamLastInFocusedPane], - ['r', true, true, false, handleMessageRegenerateLastInFocusedPane], - ['n', true, false, true, handleConversationNewInFocusedPane], + ['g', true, true, false, handleMessageRegenerateLastInFocusedPane], ['o', true, false, false, handleFileOpenConversation], ['s', true, false, false, () => handleFileSaveConversation(focusedPaneConversationId)], - ['b', true, false, true, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationBranch(focusedPaneConversationId, null))], - ['x', true, false, true, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationClear(focusedPaneConversationId))], - ['d', true, false, true, () => focusedPaneConversationId && handleDeleteConversations([focusedPaneConversationId], false)], - [ShortcutKeyName.Left, true, false, true, () => handleNavigateHistoryInFocusedPane('back')], - [ShortcutKeyName.Right, true, false, true, () => handleNavigateHistoryInFocusedPane('forward')], + ['n', true, true, false, handleConversationNewInFocusedPane], + ['x', true, true, false, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationClear(focusedPaneConversationId))], + ['d', true, true, false, () => focusedPaneConversationId && handleDeleteConversations([focusedPaneConversationId], false)], + ['[', true, false, false, () => handleNavigateHistoryInFocusedPane('back')], + [']', true, false, false, () => handleNavigateHistoryInFocusedPane('forward')], // global ['o', true, true, false, handleOpenChatLlmOptions], ['+', true, true, false, useUIPreferencesStore.getState().increaseContentScaling], ['-', true, true, false, useUIPreferencesStore.getState().decreaseContentScaling], - ], [focusedPaneConversationId, handleConversationBranch, handleConversationClear, handleConversationNewInFocusedPane, handleFileOpenConversation, handleFileSaveConversation, handleDeleteConversations, handleMessageBeamLastInFocusedPane, handleMessageRegenerateLastInFocusedPane, handleNavigateHistoryInFocusedPane, handleOpenChatLlmOptions, isFocusedChatEmpty]); + ], [focusedPaneConversationId, handleConversationClear, handleConversationNewInFocusedPane, handleFileOpenConversation, handleFileSaveConversation, handleDeleteConversations, handleMessageBeamLastInFocusedPane, handleMessageRegenerateLastInFocusedPane, handleNavigateHistoryInFocusedPane, handleOpenChatLlmOptions, isFocusedChatEmpty]); useGlobalShortcuts(shortcuts); diff --git a/src/apps/chat/components/layout-menu/ChatPageMenuItems.tsx b/src/apps/chat/components/layout-menu/ChatPageMenuItems.tsx index fc0ab226b..d3508e377 100644 --- a/src/apps/chat/components/layout-menu/ChatPageMenuItems.tsx +++ b/src/apps/chat/components/layout-menu/ChatPageMenuItems.tsx @@ -143,7 +143,7 @@ export function ChatPageMenuItems(props: { Reset Chat - {!props.disableItems && } + {!props.disableItems && } diff --git a/src/apps/chat/components/message/ChatMessage.tsx b/src/apps/chat/components/message/ChatMessage.tsx index 88418626b..4ae0e1223 100644 --- a/src/apps/chat/components/message/ChatMessage.tsx +++ b/src/apps/chat/components/message/ChatMessage.tsx @@ -756,7 +756,7 @@ export function ChatMessage(props: { ? <>Restart from here : !props.isBottom ? <>Retry from here - : Retry} + : Retry} )} {!!props.onMessageBeam && ( diff --git a/src/apps/settings-modal/ShortcutsModal.tsx b/src/apps/settings-modal/ShortcutsModal.tsx index 78572aecd..427e9d408 100644 --- a/src/apps/settings-modal/ShortcutsModal.tsx +++ b/src/apps/settings-modal/ShortcutsModal.tsx @@ -3,38 +3,36 @@ import * as React from 'react'; import { AutoBlocksRenderer } from '~/modules/blocks/AutoBlocksRenderer'; import { GoodModal } from '~/common/components/GoodModal'; -import { isMacUser } from '~/common/util/pwaUtils'; import { platformAwareKeystrokes } from '~/common/components/KeyStroke'; import { useIsMobile } from '~/common/components/useMatchMedia'; const shortcutsMd = platformAwareKeystrokes(` -| Shortcut | Description | -|-----------------------------------------|-------------------------------------------------| -| **Edit** | | -| Shift + Enter | Newline | -| Alt + Enter | Append (no response) | -| Ctrl + Shift + B | **Beam** last message | -| Ctrl + Shift + R | **Regenerate** last message | -| Ctrl + Shift + V | Attach clipboard (better than Ctrl + V) | -| Ctrl + M | Microphone (voice typing) | -| **Chats** | | -| Ctrl + O | Open Chat ... | -| Ctrl + S | Save Chat ... | -| Ctrl + ${isMacUser ? '' : 'Alt +'} N | **New** chat | -| Ctrl + ${isMacUser ? '' : 'Alt +'} X | **Reset** chat | -| Ctrl + ${isMacUser ? '' : 'Alt +'} D | **Delete** chat | -| Ctrl + ${isMacUser ? '' : 'Alt +'} B | **Branch** chat | -| Ctrl + Alt + Left | **Previous** chat (in history) | -| Ctrl + Alt + Right | **Next** chat (in history) | -| **Settings** | | -| Ctrl + Shift + P | ⚙️ Preferences | -| Ctrl + Shift + M | 🧠 Models | -| Ctrl + Shift + O | 💬 Options (current Chat Model) | -| Ctrl + Shift + + | Increase Text Size | -| Ctrl + Shift + - | Decrease Text Size | -| Ctrl + Shift + ${isMacUser ? '/' : '?'} | Shortcuts | +| Shortcut | Description | +|------------------|-----------------------------------------| +| **Edit** | | +| Shift + Enter | Newline | +| Alt + Enter | Append (no response) | +| Ctrl + Shift + B | **Beam** last message | +| Ctrl + Shift + G | Re**generate** last message | +| Ctrl + Shift + V | Attach clipboard (better than Ctrl + V) | +| Ctrl + M | Microphone (voice typing) | +| **Chats** | | +| Ctrl + O | Open Chat ... | +| Ctrl + S | Save Chat ... | +| Ctrl + Shift + N | **New** chat | +| Ctrl + Shift + X | **Reset** chat | +| Ctrl + Shift + D | **Delete** chat | +| Ctrl + [ | **Previous** chat (in history) | +| Ctrl + ] | **Next** chat (in history) | +| **Settings** | | +| Ctrl + , | ⚙️ Preferences | +| Ctrl + Shift + M | 🧠 Models | +| Ctrl + Shift + O | 💬 Options (current Chat Model) | +| Ctrl + Shift + + | Increase Text Size | +| Ctrl + Shift + - | Decrease Text Size | +| Ctrl + Shift + / | Shortcuts | `).trim(); diff --git a/src/common/layout/optima/PageBar.tsx b/src/common/layout/optima/PageBar.tsx index 3438f7c85..5c88b5d84 100644 --- a/src/common/layout/optima/PageBar.tsx +++ b/src/common/layout/optima/PageBar.tsx @@ -64,7 +64,7 @@ function CommonPageMenuItems(props: { onClose: () => void }) { {/**/} {/* Preferences |...| Dark Mode Toggle */} - {/*}>*/} + {/*}>*/} Preferences diff --git a/src/common/layout/optima/useOptimaLayout.tsx b/src/common/layout/optima/useOptimaLayout.tsx index d2802a5f6..3cd52a59a 100644 --- a/src/common/layout/optima/useOptimaLayout.tsx +++ b/src/common/layout/optima/useOptimaLayout.tsx @@ -117,8 +117,8 @@ export function OptimaLayoutProvider(props: { children: React.ReactNode }) { // global shortcuts for Optima const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [ [isMacUser ? '/' : '?', true, true, false, actions.openShortcuts], + [',', true, false, false, actions.openPreferencesTab], ['m', true, true, false, actions.openModelsSetup], - ['p', true, true, false, actions.openPreferencesTab], ], [actions]); useGlobalShortcuts(shortcuts); diff --git a/src/server/env.mjs b/src/server/env.mjs index 00fd86132..fdf8bf111 100644 --- a/src/server/env.mjs +++ b/src/server/env.mjs @@ -15,6 +15,7 @@ export const env = createEnv({ // Backend MongoDB, for a more complete developer data platform. MDB_URI: z.string().optional(), + // LLM: OpenAI OPENAI_API_KEY: z.string().optional(), OPENAI_API_HOST: z.string().url().optional(), @@ -28,6 +29,9 @@ export const env = createEnv({ ANTHROPIC_API_KEY: z.string().optional(), ANTHROPIC_API_HOST: z.string().url().optional(), + // LLM: Deepseek AI + DEEPSEEK_API_KEY: z.string().optional(), + // LLM: Google AI's Gemini GEMINI_API_KEY: z.string().optional(), @@ -50,38 +54,39 @@ export const env = createEnv({ // LLM: Perplexity PERPLEXITY_API_KEY: z.string().optional(), - // LLM: Toghether AI + // LLM: Together AI TOGETHERAI_API_KEY: z.string().optional(), - // LLM: Deepseek AI - DEEPSEEK_API_KEY: z.string().optional(), // Helicone - works on both OpenAI and Anthropic vendors HELICONE_API_KEY: z.string().optional(), - // ElevenLabs - speech.ts - ELEVENLABS_API_KEY: z.string().optional(), - ELEVENLABS_API_HOST: z.string().url().optional(), - ELEVENLABS_VOICE_ID: z.string().optional(), - // Prodia - PRODIA_API_KEY: z.string().optional(), + // Browsing Service + PUPPETEER_WSS_ENDPOINT: z.string().url().optional(), // Google Custom Search GOOGLE_CLOUD_API_KEY: z.string().optional(), GOOGLE_CSE_ID: z.string().optional(), - // Browsing Service - PUPPETEER_WSS_ENDPOINT: z.string().url().optional(), - // Backend: Analytics flags (e.g. which hostname responds) for managed installs - BACKEND_ANALYTICS: z.string().optional().transform(list => (list || '').split(';').filter(flag => !!flag)), + // Text-To-Speech: ElevenLabs - speech.ts + ELEVENLABS_API_KEY: z.string().optional(), + ELEVENLABS_API_HOST: z.string().url().optional(), + ELEVENLABS_VOICE_ID: z.string().optional(), + + // Text-To-Image: Prodia + PRODIA_API_KEY: z.string().optional(), + // Backend: HTTP Basic Authentication HTTP_BASIC_AUTH_USERNAME: z.string().optional(), HTTP_BASIC_AUTH_PASSWORD: z.string().optional(), - // Build-time configuration + // Backend: Analytics flags (e.g. which hostname responds) for managed installs + BACKEND_ANALYTICS: z.string().optional().transform(list => (list || '').split(';').filter(flag => !!flag)), + + // Build-time configuration (ignore) BIG_AGI_BUILD: z.enum(['standalone', 'static']).optional(), },