@robosystems/core 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/actions/entity-actions.d.ts +8 -0
- package/actions/entity-actions.js +16 -0
- package/actions/graph-actions.d.ts +3 -0
- package/actions/graph-actions.js +13 -0
- package/auth-components/AppSwitcher.d.ts +6 -0
- package/auth-components/AppSwitcher.js +28 -0
- package/auth-components/AuthGuard.d.ts +7 -0
- package/auth-components/AuthGuard.js +31 -0
- package/auth-components/AuthProvider.d.ts +9 -0
- package/auth-components/AuthProvider.js +565 -0
- package/auth-components/SessionWarningDialog.d.ts +1 -0
- package/auth-components/SessionWarningDialog.js +51 -0
- package/auth-components/SignInForm.d.ts +20 -0
- package/auth-components/SignInForm.js +139 -0
- package/auth-components/SignUpForm.d.ts +15 -0
- package/auth-components/SignUpForm.js +167 -0
- package/auth-components/TurnstileWidget.d.ts +99 -0
- package/auth-components/TurnstileWidget.js +224 -0
- package/auth-components/index.d.ts +6 -0
- package/auth-components/index.js +6 -0
- package/auth-core/cleanup.d.ts +44 -0
- package/auth-core/cleanup.js +157 -0
- package/auth-core/client.d.ts +121 -0
- package/auth-core/client.js +767 -0
- package/auth-core/config.d.ts +19 -0
- package/auth-core/config.js +56 -0
- package/auth-core/hooks.d.ts +19 -0
- package/auth-core/hooks.js +63 -0
- package/auth-core/index.d.ts +6 -0
- package/auth-core/index.js +6 -0
- package/auth-core/sso.d.ts +42 -0
- package/auth-core/sso.js +281 -0
- package/auth-core/token-storage.d.ts +58 -0
- package/auth-core/token-storage.js +177 -0
- package/auth-core/types.d.ts +145 -0
- package/auth-core/types.js +1 -0
- package/components/EntitySelector.d.ts +44 -0
- package/components/EntitySelector.js +64 -0
- package/components/EntitySelectorCore.d.ts +45 -0
- package/components/EntitySelectorCore.js +67 -0
- package/components/GraphSelectorCore.d.ts +21 -0
- package/components/GraphSelectorCore.js +39 -0
- package/components/PageLayout.d.ts +8 -0
- package/components/PageLayout.js +5 -0
- package/components/RepositoryGuard.d.ts +68 -0
- package/components/RepositoryGuard.js +91 -0
- package/components/console/ConsoleContent.d.ts +4 -0
- package/components/console/ConsoleContent.js +612 -0
- package/components/console/ConsoleMarkdown.d.ts +11 -0
- package/components/console/ConsoleMarkdown.js +49 -0
- package/components/console/ProgressiveText.d.ts +6 -0
- package/components/console/ProgressiveText.js +21 -0
- package/components/console/graphAwareConfig.d.ts +70 -0
- package/components/console/graphAwareConfig.js +411 -0
- package/components/console/index.d.ts +6 -0
- package/components/console/index.js +4 -0
- package/components/console/types.d.ts +84 -0
- package/components/console/types.js +1 -0
- package/components/graph-filters.d.ts +75 -0
- package/components/graph-filters.js +111 -0
- package/components/index.d.ts +10 -0
- package/components/index.js +9 -0
- package/components/repositories/ActiveSubscriptions.d.ts +26 -0
- package/components/repositories/ActiveSubscriptions.js +103 -0
- package/components/repositories/BrowseRepositories.d.ts +11 -0
- package/components/repositories/BrowseRepositories.js +184 -0
- package/components/repositories/index.d.ts +2 -0
- package/components/repositories/index.js +2 -0
- package/components/search/SearchContent.d.ts +4 -0
- package/components/search/SearchContent.js +168 -0
- package/components/search/index.d.ts +2 -0
- package/components/search/index.js +1 -0
- package/components/search/types.d.ts +15 -0
- package/components/search/types.js +1 -0
- package/contexts/entity-context.d.ts +28 -0
- package/contexts/entity-context.js +89 -0
- package/contexts/graph-context.d.ts +27 -0
- package/contexts/graph-context.js +128 -0
- package/contexts/index.d.ts +5 -0
- package/contexts/index.js +5 -0
- package/contexts/org-context.d.ts +25 -0
- package/contexts/org-context.js +64 -0
- package/contexts/service-offerings-context.d.ts +45 -0
- package/contexts/service-offerings-context.js +98 -0
- package/contexts/sidebar-context.d.ts +18 -0
- package/contexts/sidebar-context.js +44 -0
- package/hooks/index.d.ts +6 -0
- package/hooks/index.js +6 -0
- package/hooks/use-api-error.d.ts +3 -0
- package/hooks/use-api-error.js +24 -0
- package/hooks/use-media-query.d.ts +1 -0
- package/hooks/use-media-query.js +21 -0
- package/hooks/use-streaming-query.d.ts +22 -0
- package/hooks/use-streaming-query.js +142 -0
- package/hooks/use-toast.d.ts +37 -0
- package/hooks/use-toast.js +117 -0
- package/hooks/use-user-limits.d.ts +12 -0
- package/hooks/use-user-limits.js +73 -0
- package/hooks/use-user.d.ts +11 -0
- package/hooks/use-user.js +39 -0
- package/index.d.ts +29 -0
- package/index.js +77 -0
- package/lib/entity-cookie.d.ts +10 -0
- package/lib/entity-cookie.js +38 -0
- package/lib/graph-cookie.d.ts +11 -0
- package/lib/graph-cookie.js +62 -0
- package/lib/graph-tiers.d.ts +41 -0
- package/lib/graph-tiers.js +107 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/sidebar-cookie.d.ts +11 -0
- package/lib/sidebar-cookie.js +42 -0
- package/library/colors.d.ts +4 -0
- package/library/colors.js +41 -0
- package/library/components/ClassificationPicker.d.ts +8 -0
- package/library/components/ClassificationPicker.js +55 -0
- package/library/components/ElementBrowser.d.ts +11 -0
- package/library/components/ElementBrowser.js +106 -0
- package/library/components/ElementDetail.d.ts +7 -0
- package/library/components/ElementDetail.js +97 -0
- package/library/components/LibraryHierarchy.d.ts +13 -0
- package/library/components/LibraryHierarchy.js +380 -0
- package/library/components/TaxonomySidebar.d.ts +6 -0
- package/library/components/TaxonomySidebar.js +45 -0
- package/library/index.d.ts +8 -0
- package/library/index.js +7 -0
- package/package.json +37 -0
- package/research/CoverageCard.d.ts +12 -0
- package/research/CoverageCard.js +15 -0
- package/research/CoverageGrid.d.ts +6 -0
- package/research/CoverageGrid.js +9 -0
- package/research/CoverageHistory.d.ts +5 -0
- package/research/CoverageHistory.js +7 -0
- package/research/ResearchArticle.d.ts +15 -0
- package/research/ResearchArticle.js +26 -0
- package/research/catalog.d.ts +14 -0
- package/research/catalog.js +42 -0
- package/research/index.d.ts +6 -0
- package/research/index.js +9 -0
- package/research/types.d.ts +38 -0
- package/research/types.js +4 -0
- package/task-monitoring/hooks.d.ts +26 -0
- package/task-monitoring/hooks.js +126 -0
- package/task-monitoring/index.d.ts +5 -0
- package/task-monitoring/index.js +7 -0
- package/task-monitoring/operationErrors.d.ts +82 -0
- package/task-monitoring/operationErrors.js +251 -0
- package/task-monitoring/operationHooks.d.ts +117 -0
- package/task-monitoring/operationHooks.js +536 -0
- package/task-monitoring/operationMonitor.d.ts +78 -0
- package/task-monitoring/operationMonitor.js +200 -0
- package/task-monitoring/operationTypes.d.ts +236 -0
- package/task-monitoring/operationTypes.js +60 -0
- package/task-monitoring/taskMonitor.d.ts +14 -0
- package/task-monitoring/taskMonitor.js +132 -0
- package/task-monitoring/types.d.ts +31 -0
- package/task-monitoring/types.js +1 -0
- package/theme/flowbite-theme.d.ts +758 -0
- package/theme/flowbite-theme.js +765 -0
- package/theme/index.d.ts +1 -0
- package/theme/index.js +1 -0
- package/types/entity.d.ts +19 -0
- package/types/index.d.ts +2 -0
- package/types/index.js +1 -0
- package/types/user.d.ts +6 -0
- package/ui-components/ConfirmModal.d.ts +41 -0
- package/ui-components/ConfirmModal.js +19 -0
- package/ui-components/EmptyState.d.ts +22 -0
- package/ui-components/EmptyState.js +10 -0
- package/ui-components/LoadingState.d.ts +27 -0
- package/ui-components/LoadingState.js +12 -0
- package/ui-components/Logo.d.ts +28 -0
- package/ui-components/Logo.js +95 -0
- package/ui-components/Spinner.d.ts +37 -0
- package/ui-components/Spinner.js +48 -0
- package/ui-components/StatCard.d.ts +21 -0
- package/ui-components/StatCard.js +10 -0
- package/ui-components/api-keys/ApiKeyDisplay.d.ts +13 -0
- package/ui-components/api-keys/ApiKeyDisplay.js +7 -0
- package/ui-components/api-keys/ApiKeyTable.d.ts +9 -0
- package/ui-components/api-keys/ApiKeyTable.js +17 -0
- package/ui-components/api-keys/ApiKeysCard.d.ts +6 -0
- package/ui-components/api-keys/ApiKeysCard.js +97 -0
- package/ui-components/api-keys/CreateApiKeyModal.d.ts +11 -0
- package/ui-components/api-keys/CreateApiKeyModal.js +47 -0
- package/ui-components/api-keys/SecureApiKeyField.d.ts +13 -0
- package/ui-components/api-keys/SecureApiKeyField.js +64 -0
- package/ui-components/api-keys/index.d.ts +5 -0
- package/ui-components/api-keys/index.js +5 -0
- package/ui-components/chat/ChatHeader.d.ts +12 -0
- package/ui-components/chat/ChatHeader.js +5 -0
- package/ui-components/chat/ChatInputArea.d.ts +16 -0
- package/ui-components/chat/ChatInputArea.js +9 -0
- package/ui-components/chat/ChatMessage.d.ts +7 -0
- package/ui-components/chat/ChatMessage.js +24 -0
- package/ui-components/chat/DeepResearchToggle.d.ts +8 -0
- package/ui-components/chat/DeepResearchToggle.js +13 -0
- package/ui-components/chat/index.d.ts +9 -0
- package/ui-components/chat/index.js +8 -0
- package/ui-components/chat/types.d.ts +23 -0
- package/ui-components/chat/types.js +51 -0
- package/ui-components/forms/PasswordRequirements.d.ts +10 -0
- package/ui-components/forms/PasswordRequirements.js +15 -0
- package/ui-components/forms/SettingsCard.d.ts +11 -0
- package/ui-components/forms/SettingsCard.js +5 -0
- package/ui-components/forms/SettingsFormField.d.ts +18 -0
- package/ui-components/forms/SettingsFormField.js +17 -0
- package/ui-components/forms/StatusAlert.d.ts +8 -0
- package/ui-components/forms/StatusAlert.js +7 -0
- package/ui-components/forms/index.d.ts +4 -0
- package/ui-components/forms/index.js +4 -0
- package/ui-components/index.d.ts +13 -0
- package/ui-components/index.js +18 -0
- package/ui-components/layout/CoreNavbar.d.ts +14 -0
- package/ui-components/layout/CoreNavbar.js +53 -0
- package/ui-components/layout/CoreSidebar.d.ts +28 -0
- package/ui-components/layout/CoreSidebar.js +62 -0
- package/ui-components/layout/LandingFooter.d.ts +28 -0
- package/ui-components/layout/LandingFooter.js +34 -0
- package/ui-components/layout/PageContainer.d.ts +8 -0
- package/ui-components/layout/PageContainer.js +17 -0
- package/ui-components/layout/PageHeader.d.ts +23 -0
- package/ui-components/layout/PageHeader.js +12 -0
- package/ui-components/layout/SettingsContainer.d.ts +10 -0
- package/ui-components/layout/SettingsContainer.js +9 -0
- package/ui-components/layout/ThemeToggle.d.ts +4 -0
- package/ui-components/layout/ThemeToggle.js +6 -0
- package/ui-components/layout/index.d.ts +7 -0
- package/ui-components/layout/index.js +7 -0
- package/ui-components/settings/GeneralInformationCard.d.ts +12 -0
- package/ui-components/settings/GeneralInformationCard.js +72 -0
- package/ui-components/settings/PasswordInformationCard.d.ts +10 -0
- package/ui-components/settings/PasswordInformationCard.js +167 -0
- package/ui-components/settings/SettingsPageHeader.d.ts +7 -0
- package/ui-components/settings/SettingsPageHeader.js +6 -0
- package/ui-components/settings/index.d.ts +3 -0
- package/ui-components/settings/index.js +3 -0
- package/ui-components/support/SupportModal.d.ts +15 -0
- package/ui-components/support/SupportModal.js +74 -0
- package/ui-components/support/index.d.ts +2 -0
- package/ui-components/support/index.js +1 -0
- package/ui-components/types/index.d.ts +39 -0
- package/ui-components/types/index.js +1 -0
- package/utils/index.d.ts +31 -0
- package/utils/index.js +38 -0
- package/utils/turnstile-config.d.ts +11 -0
- package/utils/turnstile-config.js +19 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as SDK from '@robosystems/client';
|
|
4
|
+
import { Card } from 'flowbite-react';
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
6
|
+
import { HiTerminal } from 'react-icons/hi';
|
|
7
|
+
import { useGraphContext } from '../../contexts';
|
|
8
|
+
import { useStreamingQuery } from '../../hooks';
|
|
9
|
+
import { ConsoleMarkdown } from './ConsoleMarkdown';
|
|
10
|
+
import { ProgressiveText } from './ProgressiveText';
|
|
11
|
+
function generateMessageId() {
|
|
12
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
13
|
+
}
|
|
14
|
+
// Cap rendered rows so a large result set can't bloat the DOM; the full set
|
|
15
|
+
// is always available via the CSV export.
|
|
16
|
+
const MAX_TABLE_ROWS = 200;
|
|
17
|
+
function isNumericValue(value) {
|
|
18
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
19
|
+
}
|
|
20
|
+
function formatCell(value) {
|
|
21
|
+
if (value === null || value === undefined)
|
|
22
|
+
return '—';
|
|
23
|
+
if (typeof value === 'number') {
|
|
24
|
+
return Number.isInteger(value)
|
|
25
|
+
? value.toLocaleString()
|
|
26
|
+
: value.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === 'object')
|
|
29
|
+
return JSON.stringify(value);
|
|
30
|
+
return String(value);
|
|
31
|
+
}
|
|
32
|
+
function rowsToCsv(rows) {
|
|
33
|
+
const cols = Object.keys(rows[0]);
|
|
34
|
+
const escape = (v) => {
|
|
35
|
+
const s = v === null || v === undefined
|
|
36
|
+
? ''
|
|
37
|
+
: typeof v === 'object'
|
|
38
|
+
? JSON.stringify(v)
|
|
39
|
+
: String(v);
|
|
40
|
+
return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
41
|
+
};
|
|
42
|
+
return [
|
|
43
|
+
cols.join(','),
|
|
44
|
+
...rows.map((row) => cols.map((c) => escape(row[c])).join(',')),
|
|
45
|
+
].join('\n');
|
|
46
|
+
}
|
|
47
|
+
function downloadText(filename, text, mime) {
|
|
48
|
+
const blob = new Blob([text], { type: mime });
|
|
49
|
+
const url = URL.createObjectURL(blob);
|
|
50
|
+
const anchor = document.createElement('a');
|
|
51
|
+
anchor.href = url;
|
|
52
|
+
anchor.download = filename;
|
|
53
|
+
anchor.click();
|
|
54
|
+
URL.revokeObjectURL(url);
|
|
55
|
+
}
|
|
56
|
+
function copyRowsJson(rows) {
|
|
57
|
+
var _a;
|
|
58
|
+
(_a = navigator.clipboard) === null || _a === void 0 ? void 0 : _a.writeText(JSON.stringify(rows, null, 2));
|
|
59
|
+
}
|
|
60
|
+
export function ConsoleContent({ config }) {
|
|
61
|
+
const { state: graphState } = useGraphContext();
|
|
62
|
+
const graphId = graphState.currentGraphId;
|
|
63
|
+
const streamingQuery = useStreamingQuery();
|
|
64
|
+
// Terminal state
|
|
65
|
+
const [terminalMessages, setTerminalMessages] = useState([]);
|
|
66
|
+
const [commandInput, setCommandInput] = useState('');
|
|
67
|
+
const [commandHistory, setCommandHistory] = useState([]);
|
|
68
|
+
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
69
|
+
const terminalEndRef = useRef(null);
|
|
70
|
+
const terminalScrollRef = useRef(null);
|
|
71
|
+
const [currentQueryStartTime, setCurrentQueryStartTime] = useState(null);
|
|
72
|
+
const [apiVersion, setApiVersion] = useState(null);
|
|
73
|
+
const [operatorProgress, setOperatorProgress] = useState({ isRunning: false, message: '' });
|
|
74
|
+
// Track if we've initialized and the previous graph ID
|
|
75
|
+
const hasInitialized = useRef(false);
|
|
76
|
+
const previousGraphId = useRef(null);
|
|
77
|
+
const operatorProgressMessageId = useRef(null);
|
|
78
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
79
|
+
const getWelcomeMessage = useCallback(() => {
|
|
80
|
+
var _a;
|
|
81
|
+
const { welcome } = config;
|
|
82
|
+
const nlExamples = welcome.naturalLanguageExamples
|
|
83
|
+
.map((ex) => ` "${ex}"`)
|
|
84
|
+
.join('\n');
|
|
85
|
+
const queryExamples = welcome.directQueryExamples
|
|
86
|
+
.map((ex) => ` /query ${ex}`)
|
|
87
|
+
.join('\n');
|
|
88
|
+
const builtInCommands = ` /query - Execute a Cypher query\n` +
|
|
89
|
+
` /search - Search documents\n` +
|
|
90
|
+
` /mcp - Show MCP connection setup\n` +
|
|
91
|
+
` /help - Show this help message\n` +
|
|
92
|
+
` /clear - Clear console history\n` +
|
|
93
|
+
` /examples - Show example queries`;
|
|
94
|
+
const extraLines = ((_a = config.extraCommands) !== null && _a !== void 0 ? _a : [])
|
|
95
|
+
.map((ec) => ` ${ec.command.padEnd(12)}- Custom command`)
|
|
96
|
+
.join('\n');
|
|
97
|
+
const commandsBlock = extraLines
|
|
98
|
+
? `${builtInCommands}\n${extraLines}`
|
|
99
|
+
: builtInCommands;
|
|
100
|
+
return (`${welcome.consoleName} v${apiVersion} - ${welcome.contextLabel}: ${graphId || 'Not selected'}\n` +
|
|
101
|
+
`═══════════════════════════════════════════════════════════════\n\n` +
|
|
102
|
+
`${welcome.description}\n\n` +
|
|
103
|
+
`USAGE:\n` +
|
|
104
|
+
` Natural Language (default):\n` +
|
|
105
|
+
`${nlExamples}\n\n` +
|
|
106
|
+
` Direct Cypher Queries:\n` +
|
|
107
|
+
`${queryExamples}\n\n` +
|
|
108
|
+
`COMMANDS:\n` +
|
|
109
|
+
`${commandsBlock}\n\n` +
|
|
110
|
+
`${welcome.closingMessage}`);
|
|
111
|
+
}, [config, apiVersion, graphId]);
|
|
112
|
+
const addSystemMessage = useCallback((content, animate = false) => {
|
|
113
|
+
const message = {
|
|
114
|
+
id: generateMessageId(),
|
|
115
|
+
type: 'system',
|
|
116
|
+
content,
|
|
117
|
+
timestamp: new Date(),
|
|
118
|
+
isAnimating: animate,
|
|
119
|
+
};
|
|
120
|
+
setTerminalMessages((prev) => [...prev, message]);
|
|
121
|
+
}, []);
|
|
122
|
+
const addUserMessage = useCallback((content) => {
|
|
123
|
+
const message = {
|
|
124
|
+
id: generateMessageId(),
|
|
125
|
+
type: 'user',
|
|
126
|
+
content,
|
|
127
|
+
timestamp: new Date(),
|
|
128
|
+
};
|
|
129
|
+
setTerminalMessages((prev) => [...prev, message]);
|
|
130
|
+
}, []);
|
|
131
|
+
const addResultMessage = useCallback((content, data, cypher, opts) => {
|
|
132
|
+
const message = {
|
|
133
|
+
id: generateMessageId(),
|
|
134
|
+
type: 'system',
|
|
135
|
+
content,
|
|
136
|
+
timestamp: new Date(),
|
|
137
|
+
data,
|
|
138
|
+
cypher,
|
|
139
|
+
markdown: opts === null || opts === void 0 ? void 0 : opts.markdown,
|
|
140
|
+
footer: opts === null || opts === void 0 ? void 0 : opts.footer,
|
|
141
|
+
};
|
|
142
|
+
setTerminalMessages((prev) => [...prev, message]);
|
|
143
|
+
}, []);
|
|
144
|
+
const addErrorMessage = useCallback((content) => {
|
|
145
|
+
const message = {
|
|
146
|
+
id: generateMessageId(),
|
|
147
|
+
type: 'error',
|
|
148
|
+
content,
|
|
149
|
+
timestamp: new Date(),
|
|
150
|
+
};
|
|
151
|
+
setTerminalMessages((prev) => [...prev, message]);
|
|
152
|
+
}, []);
|
|
153
|
+
const handleAnimationComplete = (messageId) => {
|
|
154
|
+
setTerminalMessages((prev) => prev.map((msg) => msg.id === messageId ? Object.assign(Object.assign({}, msg), { isAnimating: false }) : msg));
|
|
155
|
+
};
|
|
156
|
+
// ── Effects ─────────────────────────────────────────────────────────
|
|
157
|
+
// Fetch API version from status endpoint
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
const fetchApiVersion = async () => {
|
|
160
|
+
var _a;
|
|
161
|
+
try {
|
|
162
|
+
const apiUrl = process.env.NEXT_PUBLIC_ROBOSYSTEMS_API_URL ||
|
|
163
|
+
'https://api.robosystems.ai';
|
|
164
|
+
const response = await fetch(`${apiUrl}/v1/status`);
|
|
165
|
+
const data = await response.json();
|
|
166
|
+
if ((_a = data === null || data === void 0 ? void 0 : data.details) === null || _a === void 0 ? void 0 : _a.version) {
|
|
167
|
+
setApiVersion(data.details.version);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
setApiVersion('1.0.0');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
console.error('Failed to fetch API version:', err);
|
|
175
|
+
setApiVersion('1.0.0');
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
fetchApiVersion();
|
|
179
|
+
}, []);
|
|
180
|
+
// Detect graph context changes and reset console
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (!graphId)
|
|
183
|
+
return;
|
|
184
|
+
if (previousGraphId.current && previousGraphId.current !== graphId) {
|
|
185
|
+
if (streamingQuery.isStreaming) {
|
|
186
|
+
streamingQuery.cancelQuery();
|
|
187
|
+
}
|
|
188
|
+
setTerminalMessages([]);
|
|
189
|
+
setCurrentQueryStartTime(null);
|
|
190
|
+
addSystemMessage(`═══════════════════════════════════════════════════════════════\n` +
|
|
191
|
+
`${config.welcome.contextLabel} context changed: ${previousGraphId.current} → ${graphId}\n` +
|
|
192
|
+
`═══════════════════════════════════════════════════════════════\n\n` +
|
|
193
|
+
`Console has been reset for the new ${config.welcome.contextLabel.toLowerCase()} context.\n` +
|
|
194
|
+
`All queries will now execute against: ${graphId}\n\n` +
|
|
195
|
+
`Type /help to see available commands.`, true);
|
|
196
|
+
const timer = setTimeout(() => {
|
|
197
|
+
addSystemMessage(getWelcomeMessage(), true);
|
|
198
|
+
}, 500);
|
|
199
|
+
return () => clearTimeout(timer);
|
|
200
|
+
}
|
|
201
|
+
previousGraphId.current = graphId;
|
|
202
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
203
|
+
}, [graphId]);
|
|
204
|
+
// Initialize terminal with welcome message
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
const isValidGraph = graphId && graphState.graphs.some((g) => g.graphId === graphId);
|
|
207
|
+
if (!hasInitialized.current &&
|
|
208
|
+
!graphState.isLoading &&
|
|
209
|
+
apiVersion !== null) {
|
|
210
|
+
if (!graphId || isValidGraph) {
|
|
211
|
+
addSystemMessage(getWelcomeMessage(), true);
|
|
212
|
+
hasInitialized.current = true;
|
|
213
|
+
previousGraphId.current = graphId;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
217
|
+
}, [graphState.isLoading, graphId, graphState.graphs, apiVersion]);
|
|
218
|
+
// Auto-scroll terminal to bottom
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
var _a;
|
|
221
|
+
(_a = terminalEndRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' });
|
|
222
|
+
}, [terminalMessages]);
|
|
223
|
+
// Scroll handler for progressive text updates
|
|
224
|
+
const scrollToBottom = useCallback(() => {
|
|
225
|
+
var _a;
|
|
226
|
+
(_a = terminalEndRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
227
|
+
behavior: 'auto',
|
|
228
|
+
block: 'nearest',
|
|
229
|
+
});
|
|
230
|
+
}, []);
|
|
231
|
+
// Monitor streaming query status
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
if (!currentQueryStartTime)
|
|
234
|
+
return;
|
|
235
|
+
if (streamingQuery.status === 'completed') {
|
|
236
|
+
const duration = Date.now() - currentQueryStartTime;
|
|
237
|
+
let resultText = `Query completed in ${duration}ms\nRows returned: ${streamingQuery.results.length}`;
|
|
238
|
+
if (streamingQuery.cached) {
|
|
239
|
+
resultText += `\nCached - Free`;
|
|
240
|
+
}
|
|
241
|
+
else if (streamingQuery.creditsUsed && streamingQuery.creditsUsed > 0) {
|
|
242
|
+
resultText += `\nCredits used: ${streamingQuery.creditsUsed.toFixed(1)}`;
|
|
243
|
+
}
|
|
244
|
+
if (streamingQuery.results.length > 0) {
|
|
245
|
+
addResultMessage(resultText, streamingQuery.results);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
addResultMessage(resultText);
|
|
249
|
+
}
|
|
250
|
+
setCurrentQueryStartTime(null);
|
|
251
|
+
}
|
|
252
|
+
else if (streamingQuery.status === 'error') {
|
|
253
|
+
addErrorMessage(streamingQuery.error || 'Query execution failed');
|
|
254
|
+
setCurrentQueryStartTime(null);
|
|
255
|
+
}
|
|
256
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
257
|
+
}, [
|
|
258
|
+
streamingQuery.status,
|
|
259
|
+
streamingQuery.results,
|
|
260
|
+
streamingQuery.error,
|
|
261
|
+
currentQueryStartTime,
|
|
262
|
+
]);
|
|
263
|
+
// Monitor agent progress
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
if (operatorProgress.isRunning && operatorProgress.message) {
|
|
266
|
+
if (operatorProgressMessageId.current) {
|
|
267
|
+
setTerminalMessages((prev) => prev.map((msg) => msg.id === operatorProgressMessageId.current
|
|
268
|
+
? Object.assign(Object.assign({}, msg), { content: `${operatorProgress.message}${operatorProgress.percentage !== undefined ? ` (${operatorProgress.percentage}%)` : ''}` }) : msg));
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
const id = generateMessageId();
|
|
272
|
+
operatorProgressMessageId.current = id;
|
|
273
|
+
setTerminalMessages((prev) => [
|
|
274
|
+
...prev,
|
|
275
|
+
{
|
|
276
|
+
id,
|
|
277
|
+
type: 'system',
|
|
278
|
+
content: `${operatorProgress.message}${operatorProgress.percentage !== undefined ? ` (${operatorProgress.percentage}%)` : ''}`,
|
|
279
|
+
timestamp: new Date(),
|
|
280
|
+
},
|
|
281
|
+
]);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else if (!operatorProgress.isRunning &&
|
|
285
|
+
operatorProgressMessageId.current) {
|
|
286
|
+
operatorProgressMessageId.current = null;
|
|
287
|
+
}
|
|
288
|
+
}, [operatorProgress]);
|
|
289
|
+
// ── Command handling ────────────────────────────────────────────────
|
|
290
|
+
const executeCypherQuery = async (cypherQuery) => {
|
|
291
|
+
if (!graphId) {
|
|
292
|
+
addErrorMessage(config.noSelectionError);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// Reset streaming state before starting a new query to prevent the
|
|
296
|
+
// status effect from firing with stale 'completed' results
|
|
297
|
+
streamingQuery.reset();
|
|
298
|
+
setCurrentQueryStartTime(Date.now());
|
|
299
|
+
try {
|
|
300
|
+
await streamingQuery.executeQuery(graphId, cypherQuery);
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
addErrorMessage(err.message || 'Failed to execute query');
|
|
304
|
+
setCurrentQueryStartTime(null);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
const executeOperatorQuery = async (userQuery) => {
|
|
308
|
+
var _a;
|
|
309
|
+
if (!graphId) {
|
|
310
|
+
addErrorMessage(config.noSelectionError);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const startTime = Date.now();
|
|
314
|
+
try {
|
|
315
|
+
const { clients } = await import('@robosystems/client/clients');
|
|
316
|
+
const result = await clients.operator.executeQuery(graphId, {
|
|
317
|
+
message: userQuery,
|
|
318
|
+
mode: 'quick',
|
|
319
|
+
}, {
|
|
320
|
+
mode: 'auto',
|
|
321
|
+
onProgress: (message, percentage) => {
|
|
322
|
+
setOperatorProgress({
|
|
323
|
+
isRunning: true,
|
|
324
|
+
message,
|
|
325
|
+
percentage,
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
setOperatorProgress({ isRunning: false, message: '' });
|
|
330
|
+
const duration = Date.now() - startTime;
|
|
331
|
+
const metadata = (result.metadata || {});
|
|
332
|
+
const creditsUsed = metadata.credits_consumed;
|
|
333
|
+
const resultCount = metadata.result_count;
|
|
334
|
+
// Prefer the structured outputs the operator loop now returns
|
|
335
|
+
// (metadata.rows + metadata.cypher). Fall back to scraping the prose
|
|
336
|
+
// for backends that don't emit them yet, so this stays compatible
|
|
337
|
+
// during the rollout.
|
|
338
|
+
let rows = Array.isArray(metadata.rows) &&
|
|
339
|
+
metadata.rows.length > 0 &&
|
|
340
|
+
typeof metadata.rows[0] === 'object'
|
|
341
|
+
? metadata.rows
|
|
342
|
+
: undefined;
|
|
343
|
+
let cypher = typeof metadata.cypher === 'string' ? metadata.cypher : undefined;
|
|
344
|
+
let narrative = result.content;
|
|
345
|
+
if (!rows) {
|
|
346
|
+
const jsonBlockMatch = narrative.match(/```(?:json)?\s*\n(\[[\s\S]*?\])\s*\n```/);
|
|
347
|
+
if (jsonBlockMatch) {
|
|
348
|
+
try {
|
|
349
|
+
const parsed = JSON.parse(jsonBlockMatch[1]);
|
|
350
|
+
if (Array.isArray(parsed) &&
|
|
351
|
+
parsed.length > 0 &&
|
|
352
|
+
typeof parsed[0] === 'object') {
|
|
353
|
+
rows = parsed;
|
|
354
|
+
narrative = narrative.replace(jsonBlockMatch[0], '').trim();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch (_b) {
|
|
358
|
+
// Not valid JSON, keep as text
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (!cypher) {
|
|
363
|
+
const cypherMatch = narrative.match(/```cypher\s*\n([\s\S]*?)```/);
|
|
364
|
+
if (cypherMatch) {
|
|
365
|
+
cypher = cypherMatch[1].trim();
|
|
366
|
+
narrative = narrative.replace(cypherMatch[0], '').trim();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Compact status footer matching the direct-query style.
|
|
370
|
+
let footer = `Query completed in ${duration}ms`;
|
|
371
|
+
const count = resultCount !== null && resultCount !== void 0 ? resultCount : rows === null || rows === void 0 ? void 0 : rows.length;
|
|
372
|
+
if (count !== undefined) {
|
|
373
|
+
footer += `\nRows returned: ${count}`;
|
|
374
|
+
}
|
|
375
|
+
if (creditsUsed != null && Number(creditsUsed) > 0) {
|
|
376
|
+
footer += `\nCredits used: ${Number(creditsUsed).toFixed(1)}`;
|
|
377
|
+
}
|
|
378
|
+
// Natural-language answers come back as enriched markdown, so render them
|
|
379
|
+
// as such and keep the query-stats footer separate (its line breaks would
|
|
380
|
+
// otherwise collapse under markdown). With no narrative, fall back to the
|
|
381
|
+
// plain footer, matching the direct-query result style.
|
|
382
|
+
if (narrative) {
|
|
383
|
+
addResultMessage(narrative, rows, cypher, { markdown: true, footer });
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
addResultMessage(footer, rows, cypher);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
setOperatorProgress({ isRunning: false, message: '' });
|
|
391
|
+
const errorMessage = error.message ||
|
|
392
|
+
((_a = error.data) === null || _a === void 0 ? void 0 : _a.detail) ||
|
|
393
|
+
'Failed to process natural language query';
|
|
394
|
+
if (error.status === 402) {
|
|
395
|
+
addErrorMessage(`Insufficient credits to process query.\n\nPlease upgrade your subscription or wait for credits to reset.`);
|
|
396
|
+
}
|
|
397
|
+
else if (error.status === 429) {
|
|
398
|
+
addErrorMessage(`Rate limit exceeded.\n\nPlease wait a moment before trying again.`);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
addErrorMessage(`Operator error: ${errorMessage}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
const showMcpSetup = async () => {
|
|
406
|
+
addSystemMessage('Creating MCP API key...', true);
|
|
407
|
+
try {
|
|
408
|
+
const { createUserApiKey } = await import('@robosystems/client/sdk');
|
|
409
|
+
const response = await createUserApiKey({
|
|
410
|
+
body: {
|
|
411
|
+
name: `MCP - Console Generated - ${new Date().toLocaleDateString()}`,
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
if (!response.data) {
|
|
415
|
+
throw new Error('Failed to create API key');
|
|
416
|
+
}
|
|
417
|
+
const apiKey = response.data.key;
|
|
418
|
+
const apiUrl = process.env.NEXT_PUBLIC_ROBOSYSTEMS_API_URL ||
|
|
419
|
+
'https://api.robosystems.ai';
|
|
420
|
+
const { mcp } = config;
|
|
421
|
+
const exampleLines = mcp.exampleQuestions
|
|
422
|
+
.map((q) => ` • "${q}"`)
|
|
423
|
+
.join('\n');
|
|
424
|
+
addSystemMessage(`MCP Setup Instructions:\n` +
|
|
425
|
+
`═══════════════════════════════════════════════════════════════\n\n` +
|
|
426
|
+
`API Key Created Successfully!\n\n` +
|
|
427
|
+
`Add this configuration to claude_desktop_config.json:\n\n` +
|
|
428
|
+
` {\n` +
|
|
429
|
+
` "mcpServers": {\n` +
|
|
430
|
+
` "${mcp.serverName}": {\n` +
|
|
431
|
+
` "command": "npx",\n` +
|
|
432
|
+
` "args": ["-y", "${mcp.packageName}@latest"],\n` +
|
|
433
|
+
` "env": {\n` +
|
|
434
|
+
` "ROBOSYSTEMS_API_URL": "${apiUrl}",\n` +
|
|
435
|
+
` "ROBOSYSTEMS_API_KEY": "${apiKey}",\n` +
|
|
436
|
+
` "ROBOSYSTEMS_GRAPH_ID": "${graphId || mcp.contextIdFallback}"\n` +
|
|
437
|
+
` }\n` +
|
|
438
|
+
` }\n` +
|
|
439
|
+
` }\n` +
|
|
440
|
+
` }\n\n` +
|
|
441
|
+
`Restart Claude Desktop or Claude Code to apply.\n\n` +
|
|
442
|
+
`Once connected, ask Claude questions like:\n` +
|
|
443
|
+
`${exampleLines}\n\n` +
|
|
444
|
+
`Keep this API key secure! It has full access to your account.`, true);
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
addErrorMessage(`Failed to create API key: ${error.message || 'Unknown error'}\n\n` +
|
|
448
|
+
`You can manually create an API key in Settings and use it with:\n` +
|
|
449
|
+
` ROBOSYSTEMS_API_URL: https://api.robosystems.ai\n` +
|
|
450
|
+
` ROBOSYSTEMS_GRAPH_ID: ${graphId || config.mcp.contextIdFallback}`);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
const handleCommand = async (command) => {
|
|
454
|
+
var _a;
|
|
455
|
+
if (!command.trim())
|
|
456
|
+
return;
|
|
457
|
+
addUserMessage(command);
|
|
458
|
+
setCommandHistory((prev) => [...prev, command]);
|
|
459
|
+
setHistoryIndex(-1);
|
|
460
|
+
setCommandInput('');
|
|
461
|
+
// Handle /query command
|
|
462
|
+
if (command.toLowerCase().startsWith('/query ')) {
|
|
463
|
+
const cypherQuery = command.slice(7).trim();
|
|
464
|
+
if (cypherQuery) {
|
|
465
|
+
await executeCypherQuery(cypherQuery);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
addErrorMessage('Usage: /query <cypher-query>');
|
|
469
|
+
}
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
// Handle /search command
|
|
473
|
+
if (command.toLowerCase().startsWith('/search')) {
|
|
474
|
+
const searchQuery = command.slice(7).trim();
|
|
475
|
+
if (!searchQuery) {
|
|
476
|
+
addErrorMessage('Usage: /search <query>\n\nExamples:\n /search revenue recognition\n /search month end close procedures');
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (!graphId) {
|
|
480
|
+
addErrorMessage('No graph selected. Please select a graph first.');
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
addSystemMessage(`Searching for "${searchQuery}"...`);
|
|
484
|
+
try {
|
|
485
|
+
const body = {
|
|
486
|
+
query: searchQuery,
|
|
487
|
+
size: 10,
|
|
488
|
+
};
|
|
489
|
+
const res = await SDK.searchDocuments({
|
|
490
|
+
path: { graph_id: graphId },
|
|
491
|
+
body: body,
|
|
492
|
+
});
|
|
493
|
+
if (res.data) {
|
|
494
|
+
const data = res.data;
|
|
495
|
+
if (data.hits.length === 0) {
|
|
496
|
+
addSystemMessage(`No results found for "${searchQuery}".`);
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
const lines = data.hits.map((hit, idx) => {
|
|
500
|
+
var _a;
|
|
501
|
+
const title = hit.document_title || hit.section_label || 'Untitled';
|
|
502
|
+
const section = hit.section_label && hit.document_title
|
|
503
|
+
? ` > ${hit.section_label}`
|
|
504
|
+
: '';
|
|
505
|
+
const tags = ((_a = hit.tags) === null || _a === void 0 ? void 0 : _a.length) ? ` [${hit.tags.join(', ')}]` : '';
|
|
506
|
+
const snippet = hit.snippet
|
|
507
|
+
? `\n ${hit.snippet.slice(0, 150)}${hit.snippet.length > 150 ? '...' : ''}`
|
|
508
|
+
: '';
|
|
509
|
+
return ` ${idx + 1}. [${hit.score.toFixed(2)}] ${title}${section}${tags}${snippet}`;
|
|
510
|
+
});
|
|
511
|
+
addSystemMessage(`Found ${data.total} results for "${searchQuery}" (showing ${data.hits.length}):\n\n${lines.join('\n\n')}`, true);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
addErrorMessage('Search failed. Please try again.');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch (_b) {
|
|
519
|
+
addErrorMessage('An error occurred while searching.');
|
|
520
|
+
}
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
// Handle slash commands
|
|
524
|
+
if (command.startsWith('/')) {
|
|
525
|
+
const cmd = command.toLowerCase().split(' ')[0];
|
|
526
|
+
// Check extra commands first
|
|
527
|
+
const extra = (_a = config.extraCommands) === null || _a === void 0 ? void 0 : _a.find((ec) => ec.command.toLowerCase() === cmd);
|
|
528
|
+
if (extra) {
|
|
529
|
+
await extra.handler({ addSystemMessage, addErrorMessage, graphId });
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
switch (cmd) {
|
|
533
|
+
case '/help':
|
|
534
|
+
addSystemMessage(getWelcomeMessage(), true);
|
|
535
|
+
return;
|
|
536
|
+
case '/clear':
|
|
537
|
+
setTerminalMessages([]);
|
|
538
|
+
addSystemMessage('Console cleared.', true);
|
|
539
|
+
return;
|
|
540
|
+
case '/examples': {
|
|
541
|
+
const examples = config.sampleQueries
|
|
542
|
+
.map((q, idx) => `${idx + 1}. ${q.name}\n/query ${q.query}`)
|
|
543
|
+
.join('\n\n');
|
|
544
|
+
addSystemMessage(`${config.examplesLabel}\n\n${examples}\n\nUse: /query <cypher> to execute`, true);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
case '/mcp':
|
|
548
|
+
await showMcpSetup();
|
|
549
|
+
return;
|
|
550
|
+
default:
|
|
551
|
+
addErrorMessage(`Unknown command: ${cmd}\n\nType /help for available commands.`);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Default: treat as natural language query
|
|
556
|
+
await executeOperatorQuery(command);
|
|
557
|
+
};
|
|
558
|
+
const cancelQuery = () => {
|
|
559
|
+
streamingQuery.cancelQuery();
|
|
560
|
+
};
|
|
561
|
+
// ── Render ──────────────────────────────────────────────────────────
|
|
562
|
+
const { header } = config;
|
|
563
|
+
return (_jsxs("div", { className: "mx-auto max-w-7xl space-y-6 p-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx("div", { className: `rounded-lg bg-gradient-to-br ${header.gradientFrom} ${header.gradientTo} p-3`, children: _jsx(HiTerminal, { className: "h-8 w-8 text-white" }) }), _jsxs("div", { children: [_jsx("h1", { className: "font-heading text-3xl font-bold text-gray-900 dark:text-white", children: header.title }), _jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: header.subtitle })] })] }), (streamingQuery.isStreaming || operatorProgress.isRunning) && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400", children: [_jsx("span", { className: "inline-block h-2 w-2 animate-pulse rounded-full bg-green-500" }), operatorProgress.isRunning && operatorProgress.message
|
|
564
|
+
? operatorProgress.message
|
|
565
|
+
: 'Processing...', operatorProgress.percentage !== undefined &&
|
|
566
|
+
` (${operatorProgress.percentage}%)`] }))] }), _jsx(Card, { className: "overflow-hidden bg-gray-950 !p-0 [&>div]:!p-0", children: _jsxs("div", { className: "flex flex-col bg-gray-950", style: { height: 'calc(100vh - 280px)' }, children: [_jsxs("div", { ref: terminalScrollRef, className: "flex-1 overflow-y-auto p-4 font-mono text-sm", children: [terminalMessages.map((message) => (_jsxs("div", { className: "mb-4", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2 text-xs text-gray-700", children: [_jsx("span", { children: message.timestamp.toLocaleTimeString() }), _jsx("span", { children: "-" }), _jsx("span", { className: "tracking-wider uppercase", children: message.type })] }), _jsxs("div", { className: `leading-relaxed break-words ${message.markdown && !message.isAnimating
|
|
567
|
+
? ''
|
|
568
|
+
: 'whitespace-pre-wrap'} ${message.type === 'system'
|
|
569
|
+
? 'text-cyan-400'
|
|
570
|
+
: message.type === 'user'
|
|
571
|
+
? 'text-green-400'
|
|
572
|
+
: message.type === 'error'
|
|
573
|
+
? 'text-red-400'
|
|
574
|
+
: 'text-gray-300'}`, children: [message.type === 'user' && (_jsx("span", { className: "mr-2 text-green-500", children: "$" })), message.isAnimating ? (_jsx(ProgressiveText, { text: message.content, onComplete: () => handleAnimationComplete(message.id), onUpdate: scrollToBottom })) : message.markdown ? (_jsx(ConsoleMarkdown, { content: message.content })) : (message.content)] }), message.footer && (_jsx("div", { className: "mt-3 text-xs whitespace-pre-wrap text-gray-500", children: message.footer })), message.cypher && (_jsxs("div", { className: "mt-3 overflow-hidden rounded border border-gray-800 bg-gray-900/40", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-800 px-3 py-1.5", children: [_jsx("span", { className: "text-xs tracking-wider text-gray-500 uppercase", children: "Generated Cypher" }), _jsx("button", { onClick: () => { var _a; return handleCommand(`/query ${(_a = message.cypher) !== null && _a !== void 0 ? _a : ''}`); }, className: "rounded bg-cyan-600/90 px-2 py-0.5 text-xs font-medium text-white transition-colors hover:bg-cyan-600", children: "Run" })] }), _jsx("pre", { className: "overflow-x-auto px-3 py-2 font-mono text-xs whitespace-pre-wrap text-cyan-300", children: message.cypher })] })), message.data && message.data.length > 0 && (_jsxs("div", { className: "mt-3 overflow-hidden rounded border border-gray-800", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-800 bg-gray-900 px-4 py-2", children: [_jsxs("span", { className: "text-xs text-gray-500", children: [message.data.length, " row", message.data.length === 1 ? '' : 's'] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { onClick: () => copyRowsJson(message.data), className: "rounded border border-gray-700 px-2 py-0.5 text-xs text-gray-300 transition-colors hover:bg-gray-800", children: "Copy JSON" }), _jsx("button", { onClick: () => downloadText('results.csv', rowsToCsv(message.data), 'text/csv'), className: "rounded border border-gray-700 px-2 py-0.5 text-xs text-gray-300 transition-colors hover:bg-gray-800", children: "Download CSV" })] })] }), _jsx("div", { className: "max-h-96 overflow-auto", children: _jsxs("table", { className: "w-full border-collapse text-xs", children: [_jsx("thead", { className: "sticky top-0", children: _jsx("tr", { className: "border-b border-gray-800 bg-gray-900", children: Object.keys(message.data[0]).map((key) => (_jsx("th", { className: "px-4 py-2 text-left font-semibold whitespace-nowrap text-cyan-400", children: key }, key))) }) }), _jsx("tbody", { children: message.data
|
|
575
|
+
.slice(0, MAX_TABLE_ROWS)
|
|
576
|
+
.map((row, idx) => (_jsx("tr", { className: "border-b border-gray-900 hover:bg-gray-900/50", children: Object.keys(message.data[0]).map((key) => {
|
|
577
|
+
const value = row[key];
|
|
578
|
+
const numeric = isNumericValue(value);
|
|
579
|
+
const isNull = value === null || value === undefined;
|
|
580
|
+
return (_jsx("td", { className: `px-4 py-2 ${numeric
|
|
581
|
+
? 'text-right text-gray-300 tabular-nums'
|
|
582
|
+
: 'text-gray-400'} ${isNull ? 'text-gray-600' : ''}`, children: formatCell(value) }, key));
|
|
583
|
+
}) }, idx))) })] }) }), message.data.length > MAX_TABLE_ROWS && (_jsxs("div", { className: "border-t border-gray-800 bg-gray-900/50 px-4 py-2 text-xs text-gray-600", children: ["Showing ", MAX_TABLE_ROWS, " of ", message.data.length, " rows \u2014 download CSV for the full set"] }))] }))] }, message.id))), _jsx("div", { ref: terminalEndRef })] }), _jsxs("div", { className: "flex items-center gap-3 border-t border-gray-700 bg-gray-950 px-4 py-3", children: [_jsx("span", { className: "font-mono text-sm text-green-500", children: "$" }), _jsx("input", { type: "text", value: commandInput, onChange: (e) => setCommandInput(e.target.value), onKeyDown: (e) => {
|
|
584
|
+
if (e.key === 'Enter') {
|
|
585
|
+
handleCommand(commandInput);
|
|
586
|
+
}
|
|
587
|
+
else if (e.key === 'ArrowUp') {
|
|
588
|
+
e.preventDefault();
|
|
589
|
+
if (commandHistory.length > 0) {
|
|
590
|
+
const newIndex = historyIndex === -1
|
|
591
|
+
? commandHistory.length - 1
|
|
592
|
+
: Math.max(0, historyIndex - 1);
|
|
593
|
+
setHistoryIndex(newIndex);
|
|
594
|
+
setCommandInput(commandHistory[newIndex]);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
else if (e.key === 'ArrowDown') {
|
|
598
|
+
e.preventDefault();
|
|
599
|
+
if (historyIndex !== -1) {
|
|
600
|
+
const newIndex = historyIndex + 1;
|
|
601
|
+
if (newIndex >= commandHistory.length) {
|
|
602
|
+
setHistoryIndex(-1);
|
|
603
|
+
setCommandInput('');
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
setHistoryIndex(newIndex);
|
|
607
|
+
setCommandInput(commandHistory[newIndex]);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}, placeholder: "Type a question, /query <cypher>, or /help...", className: "terminal-input flex-1 border-none bg-transparent font-mono text-sm text-gray-300 outline-none placeholder:text-gray-700", disabled: !graphId }), streamingQuery.isStreaming && (_jsx("button", { onClick: cancelQuery, className: "rounded bg-red-600/90 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-red-600", children: "Cancel" }))] })] }) })] }));
|
|
612
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a natural-language console answer as GitHub-flavored markdown, themed
|
|
3
|
+
* for the dark terminal. The backend now returns enriched markdown (### headings,
|
|
4
|
+
* **bold**, and | pipe tables); without this it rendered as raw literal text.
|
|
5
|
+
*
|
|
6
|
+
* Styling stays inside the terminal's cyan/mono aesthetic — the surrounding
|
|
7
|
+
* container supplies `font-mono text-sm`, so tables and code inherit it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ConsoleMarkdown({ content }: {
|
|
10
|
+
content: string;
|
|
11
|
+
}): import("react").JSX.Element;
|