diff --git a/pages/info/debug.tsx b/pages/info/debug.tsx
new file mode 100644
index 000000000..3c8c06bfb
--- /dev/null
+++ b/pages/info/debug.tsx
@@ -0,0 +1,164 @@
+import * as React from 'react';
+import { fileSave } from 'browser-fs-access';
+
+import { Box, Button, Card, CardContent, Typography } from '@mui/joy';
+import DownloadIcon from '@mui/icons-material/Download';
+
+import { AppPlaceholder } from '../../src/apps/AppPlaceholder';
+
+import { backendCaps } from '~/modules/backend/state-backend';
+
+import { withLayout } from '~/common/layout/withLayout';
+
+
+// app config
+import { Brand } from '~/common/app.config';
+import { ROUTE_APP_CHAT, ROUTE_INDEX } from '~/common/app.routes';
+
+// apps access
+import { incrementalNewsVersion } from '../../src/apps/news/news.version';
+
+// capabilities access
+import { useCapabilityBrowserSpeechRecognition, useCapabilityElevenLabs, useCapabilityTextToImage } from '~/common/components/useCapabilities';
+
+// stores access
+import { getLLMsDebugInfo } from '~/modules/llms/store-llms';
+import { useAppStateStore } from '~/common/state/store-appstate';
+import { useChatStore } from '~/common/state/store-chats';
+import { useFolderStore } from '~/common/state/store-folders';
+import { useUXLabsStore } from '~/common/state/store-ux-labs';
+
+// utils access
+import { clientHostName, isChromeDesktop, isFirefox, isIPhoneUser, isMacUser, isPwa, isVercelFromBackend, isVercelFromFrontend } from '~/common/util/pwaUtils';
+import { supportsClipboardRead } from '~/common/util/clipboardUtils';
+import { supportsScreenCapture } from '~/common/util/screenCaptureUtils';
+
+
+function DebugCard(props: { title: string, children: React.ReactNode }) {
+ return (
+
+
+ {props.title}
+
+ {props.children}
+
+ );
+}
+
+function prettifyJsonString(jsonString: string, deleteChars: number, removeDoubleQuotes: boolean, removeTrailComma: boolean): string {
+ return jsonString.split('\n').map(l => {
+ if (deleteChars > 0)
+ l = l.substring(deleteChars);
+ if (removeDoubleQuotes)
+ l = l.replaceAll('\"', '');
+ if (removeTrailComma && l.endsWith(','))
+ l = l.substring(0, l.length - 1);
+ return l;
+ }).join('\n').trim();
+}
+
+function DebugJsonCard(props: { title: string, data: any }) {
+ return (
+
+
+ {prettifyJsonString(JSON.stringify(props.data, null, 2), 2, true, true)}
+
+
+ );
+}
+
+
+function AppDebug() {
+
+ // state
+ const [saved, setSaved] = React.useState(false);
+
+ // external state
+ const backendCapabilities = backendCaps();
+ const chatsCount = useChatStore.getState().conversations?.length;
+ const uxLabsExperiments = Object.entries(useUXLabsStore.getState()).filter(([_k, v]) => v === true).map(([k, _]) => k).join(', ');
+ const { folders, enableFolders } = useFolderStore.getState();
+ const { lastSeenNewsVersion, usageCount } = useAppStateStore.getState();
+
+
+ // derived state
+ const cClient = {
+ // isBrowser,
+ isChromeDesktop,
+ isFirefox,
+ isIPhone: isIPhoneUser,
+ isMac: isMacUser,
+ isPWA: isPwa(),
+ supportsClipboardPaste: supportsClipboardRead,
+ supportsScreenCapture,
+ };
+ const cProduct = {
+ capabilities: {
+ mic: useCapabilityBrowserSpeechRecognition(),
+ elevenLabs: useCapabilityElevenLabs(),
+ textToImage: useCapabilityTextToImage(),
+ },
+ models: getLLMsDebugInfo(),
+ state: {
+ chatsCount,
+ foldersCount: folders?.length,
+ foldersEnabled: enableFolders,
+ newsCurrent: incrementalNewsVersion,
+ newsSeen: lastSeenNewsVersion,
+ labsActive: uxLabsExperiments,
+ reloads: usageCount,
+ },
+ };
+ const cBackend = {
+ configuration: backendCapabilities,
+ deployment: {
+ home: Brand.URIs.Home,
+ hostName: clientHostName(),
+ isVercelFromBackend,
+ isVercelFromFrontend,
+ routeIndex: ROUTE_INDEX,
+ routeChat: ROUTE_APP_CHAT,
+ },
+ };
+
+ const handleDownload = async () => {
+ fileSave(
+ new Blob([JSON.stringify({ client: cClient, agi: cProduct, backend: cBackend }, null, 2)], { type: 'application/json' }),
+ { fileName: `big-agi-debug-${new Date().toISOString().replace(/:/g, '-')}.json`, extensions: ['.json'] },
+ )
+ .then(() => setSaved(true))
+ .catch(e => console.error('Error saving debug.json', e));
+ };
+
+ return (
+
+
+ }
+ sx={{
+ backgroundColor: saved ? undefined : 'background.surface',
+ boxShadow: 'sm',
+ placeSelf: 'start',
+ minWidth: 260,
+ }}
+ >
+ Download debug JSON
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+
+export default function DebugPage() {
+ return withLayout({ type: 'plain' }, );
+};
\ No newline at end of file
diff --git a/src/modules/llms/store-llms.ts b/src/modules/llms/store-llms.ts
index 2e6e7356e..f4b7c7535 100644
--- a/src/modules/llms/store-llms.ts
+++ b/src/modules/llms/store-llms.ts
@@ -342,4 +342,9 @@ export function useChatLLM() {
const chatLLM = chatLLMId ? state.llms.find(llm => llm.id === chatLLMId) ?? null : null;
return { chatLLM };
}, shallow);
+}
+
+export function getLLMsDebugInfo() {
+ const { llms, sources, chatLLMId, fastLLMId, funcLLMId } = useModelsStore.getState();
+ return { sources: sources.length, llmsCount: llms.length, chatId: chatLLMId, fastId: fastLLMId, funcId: funcLLMId };
}
\ No newline at end of file