@tonyclaw/agent-inspector 4.0.1 → 4.0.3
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/.output/backend/nitro.json +1 -1
- package/.output/cli.js +193 -110
- package/.output/server/_ssr/index.mjs +1 -1
- package/.output/server/_ssr/{router-CTHqmroT.mjs → router-ChILzhmz.mjs} +92 -4
- package/.output/server/index.mjs +1 -1
- package/.output/ui/assets/{CompareDrawer-L3n4J-Or.js → CompareDrawer-Co3cUF_I.js} +1 -1
- package/.output/ui/assets/{InspectorPet-D5o_3OZS.js → InspectorPet-jFcrL37X.js} +1 -1
- package/.output/ui/assets/{ProxyViewerContainer-Ocagx75S.js → ProxyViewerContainer-BPzt_VpM.js} +4 -4
- package/.output/ui/assets/{ReplayDialog-Bk7x1h-y.js → ReplayDialog-C6O_zuwI.js} +1 -1
- package/.output/ui/assets/{RequestAnatomy-U4lzL7Sc.js → RequestAnatomy-Ca6NvB7L.js} +1 -1
- package/.output/ui/assets/{ResponseView-LOWfvBLK.js → ResponseView-BzqKcveJ.js} +1 -1
- package/.output/ui/assets/{StreamingChunkSequence-DPfsrrXQ.js → StreamingChunkSequence-9pCZIsS_.js} +1 -1
- package/.output/ui/assets/{_sessionId-0MlWS-CO.js → _sessionId-BMP3KVGR.js} +1 -1
- package/.output/ui/assets/{_sessionId-OdX2Jvt1.js → _sessionId-y4eRoT1H.js} +1 -1
- package/.output/ui/assets/{index-Ea-c8hPg.js → index-CH5w-haG.js} +1 -1
- package/.output/ui/assets/{index-zVVS53Hd.js → index-CmocEhyU.js} +1 -1
- package/.output/ui/assets/{index-Du4qNOOt.js → index-DT8sMJ_n.js} +2 -2
- package/.output/ui/assets/{index-gvFj_9K6.js → index-bfsbIRZa.js} +1 -1
- package/.output/ui/assets/{json-viewer-C1WAMzg3.js → json-viewer-BfFOSDG5.js} +1 -1
- package/.output/ui/assets/{jszip.min-B8AKu2zm.js → jszip.min-BkbVkOsp.js} +1 -1
- package/.output/ui/index.html +1 -1
- package/package.json +5 -10
- package/src/assets/IDE/claudecode-color.svg +0 -1
- package/src/assets/IDE/codex-color.svg +0 -1
- package/src/assets/IDE/opencode.svg +0 -1
- package/src/assets/IDE/xiaomimimo.svg +0 -1
- package/src/assets/favicon.svg +0 -21
- package/src/assets/logos/alibaba.svg +0 -1
- package/src/assets/logos/anthropic.svg +0 -1
- package/src/assets/logos/claude-code.svg +0 -4
- package/src/assets/logos/deepseek.svg +0 -1
- package/src/assets/logos/minimax.jpeg +0 -0
- package/src/assets/logos/openai.svg +0 -1
- package/src/assets/logos/opencode.svg +0 -4
- package/src/assets/logos/qwen.webp +0 -0
- package/src/assets/logos/zhipuai.svg +0 -219
- package/src/backend/router.tsx +0 -36
- package/src/backend/routes/$.ts +0 -26
- package/src/backend/routes/__root.ts +0 -4
- package/src/backend/routes/api/-instances.ts +0 -7
- package/src/backend/routes/api/alerts.summary.ts +0 -28
- package/src/backend/routes/api/alerts.ts +0 -70
- package/src/backend/routes/api/config.paths.ts +0 -14
- package/src/backend/routes/api/config.ts +0 -73
- package/src/backend/routes/api/ecosystem.packages.$packageId.help.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.install.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.runner-presets.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.upgrade.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.ts +0 -21
- package/src/backend/routes/api/ecosystem.recipes.$recipeId.run.ts +0 -11
- package/src/backend/routes/api/ecosystem.tasks.$taskId.ts +0 -16
- package/src/backend/routes/api/ecosystem.tasks.ts +0 -12
- package/src/backend/routes/api/groups.$groupId.evidence.ts +0 -55
- package/src/backend/routes/api/groups.$groupId.sessions.ts +0 -50
- package/src/backend/routes/api/groups.$groupId.ts +0 -93
- package/src/backend/routes/api/groups.ts +0 -79
- package/src/backend/routes/api/health.ts +0 -22
- package/src/backend/routes/api/instances.$name.connection.ts +0 -12
- package/src/backend/routes/api/instances.$name.restart.ts +0 -23
- package/src/backend/routes/api/instances.$name.start.ts +0 -18
- package/src/backend/routes/api/instances.$name.stop.ts +0 -23
- package/src/backend/routes/api/instances.$name.ts +0 -12
- package/src/backend/routes/api/instances.ts +0 -16
- package/src/backend/routes/api/knowledge.candidates.$candidateId.promote.ts +0 -32
- package/src/backend/routes/api/knowledge.candidates.$candidateId.ts +0 -50
- package/src/backend/routes/api/knowledge.candidates.ts +0 -10
- package/src/backend/routes/api/knowledge.project-context.ts +0 -29
- package/src/backend/routes/api/knowledge.search.ts +0 -62
- package/src/backend/routes/api/knowledge.sessions.$sessionId.candidates.ts +0 -26
- package/src/backend/routes/api/logs.$id.body.ts +0 -58
- package/src/backend/routes/api/logs.$id.chunks.ts +0 -37
- package/src/backend/routes/api/logs.$id.replay.ts +0 -297
- package/src/backend/routes/api/logs.$id.ts +0 -23
- package/src/backend/routes/api/logs.import.ts +0 -39
- package/src/backend/routes/api/logs.stream.ts +0 -174
- package/src/backend/routes/api/logs.ts +0 -214
- package/src/backend/routes/api/mcp.ts +0 -25
- package/src/backend/routes/api/models.ts +0 -10
- package/src/backend/routes/api/pi-agent.ts +0 -29
- package/src/backend/routes/api/providers.$providerId.model-metadata.ts +0 -173
- package/src/backend/routes/api/providers.$providerId.test.log.ts +0 -342
- package/src/backend/routes/api/providers.$providerId.ts +0 -111
- package/src/backend/routes/api/providers.export.ts +0 -44
- package/src/backend/routes/api/providers.import.ts +0 -81
- package/src/backend/routes/api/providers.scan.ts +0 -28
- package/src/backend/routes/api/providers.ts +0 -102
- package/src/backend/routes/api/runs.$runId.evidence.ts +0 -55
- package/src/backend/routes/api/runs.$runId.ts +0 -50
- package/src/backend/routes/api/runs.ts +0 -62
- package/src/backend/routes/api/runtime.ts +0 -46
- package/src/backend/routes/api/sessions.ts +0 -51
- package/src/backend/routes/api/storage.ts +0 -56
- package/src/backend/routes/livez.ts +0 -13
- package/src/backend/routes/metrics.ts +0 -14
- package/src/backend/routes/proxy/$.ts +0 -15
- package/src/backend/routes/readyz.ts +0 -18
- package/src/backend/start.ts +0 -10
- package/src/cli/alias.ts +0 -431
- package/src/cli/detect-tools.ts +0 -123
- package/src/cli/doctor.ts +0 -836
- package/src/cli/instance.ts +0 -1071
- package/src/cli/instanceApi.ts +0 -213
- package/src/cli/instanceArgs.ts +0 -301
- package/src/cli/instanceControl.ts +0 -191
- package/src/cli/instanceModel.ts +0 -276
- package/src/cli/instanceRegistry.ts +0 -731
- package/src/cli/networkHints.ts +0 -170
- package/src/cli/onboard.ts +0 -1668
- package/src/cli/onboardTarget.ts +0 -228
- package/src/cli/rustBackendPackage.ts +0 -458
- package/src/cli/startupOutput.ts +0 -16
- package/src/cli/templates/codex-skill-onboard.ts +0 -320
- package/src/cli/templates/command-onboard.ts +0 -25
- package/src/cli/templates/skill-onboard.ts +0 -625
- package/src/cli.ts +0 -1433
- package/src/components/OnboardingBanner.tsx +0 -115
- package/src/components/ProxyViewer.tsx +0 -2259
- package/src/components/ProxyViewerContainer.tsx +0 -1076
- package/src/components/alerts/AlertsDialog.tsx +0 -615
- package/src/components/clients/ClientLogo.tsx +0 -175
- package/src/components/ecosystem/AgentLabDialog.tsx +0 -947
- package/src/components/errors/SafeErrorBoundary.tsx +0 -201
- package/src/components/groups/GroupsDialog.tsx +0 -1304
- package/src/components/inspector-pet/InspectorPet.tsx +0 -398
- package/src/components/inspector-pet/InspectorPetCrab3D.tsx +0 -455
- package/src/components/inspector-pet/inspectorPetLogic.ts +0 -272
- package/src/components/pi-agent/PiAgentPanel.tsx +0 -446
- package/src/components/pi-agent/piAgentChatLogic.ts +0 -273
- package/src/components/providers/ImportWizardDialog.tsx +0 -281
- package/src/components/providers/ProviderCard.tsx +0 -347
- package/src/components/providers/ProviderForm.tsx +0 -1183
- package/src/components/providers/ProviderLogo.tsx +0 -122
- package/src/components/providers/ProviderTestResultsView.tsx +0 -734
- package/src/components/providers/ProvidersPanel.tsx +0 -714
- package/src/components/providers/SettingsDialog.tsx +0 -1042
- package/src/components/providers/providerFormCapabilities.ts +0 -36
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +0 -902
- package/src/components/proxy-viewer/AnswerMarkdown.tsx +0 -51
- package/src/components/proxy-viewer/ApplicationBar.tsx +0 -123
- package/src/components/proxy-viewer/CompareDrawer.tsx +0 -984
- package/src/components/proxy-viewer/ConversationGroup.tsx +0 -208
- package/src/components/proxy-viewer/ConversationGroupList.tsx +0 -652
- package/src/components/proxy-viewer/ConversationHeader.tsx +0 -429
- package/src/components/proxy-viewer/LiveConnectionStatus.tsx +0 -186
- package/src/components/proxy-viewer/LogEntry.tsx +0 -1419
- package/src/components/proxy-viewer/LogEntryHeader.tsx +0 -596
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -57
- package/src/components/proxy-viewer/RawExportWarning.tsx +0 -77
- package/src/components/proxy-viewer/ReplayDialog.tsx +0 -636
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +0 -299
- package/src/components/proxy-viewer/ResponseView.tsx +0 -202
- package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +0 -61
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +0 -265
- package/src/components/proxy-viewer/ThreadConnector.tsx +0 -165
- package/src/components/proxy-viewer/ToolTraceEvents.tsx +0 -64
- package/src/components/proxy-viewer/TurnGroup.tsx +0 -383
- package/src/components/proxy-viewer/TurnGroupList.tsx +0 -156
- package/src/components/proxy-viewer/accessibility.ts +0 -8
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +0 -887
- package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +0 -222
- package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +0 -737
- package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +0 -268
- package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +0 -53
- package/src/components/proxy-viewer/anatomy/types.ts +0 -41
- package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +0 -114
- package/src/components/proxy-viewer/bodyHydration.ts +0 -123
- package/src/components/proxy-viewer/cacheTrend.ts +0 -50
- package/src/components/proxy-viewer/diff/DiffView.tsx +0 -321
- package/src/components/proxy-viewer/diff/computeDiff.ts +0 -178
- package/src/components/proxy-viewer/diff/index.ts +0 -3
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +0 -194
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +0 -81
- package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +0 -21
- package/src/components/proxy-viewer/formats/index.tsx +0 -47
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +0 -442
- package/src/components/proxy-viewer/index.ts +0 -9
- package/src/components/proxy-viewer/lazy.ts +0 -41
- package/src/components/proxy-viewer/liveConnectionState.ts +0 -158
- package/src/components/proxy-viewer/log-formats/anthropic.ts +0 -191
- package/src/components/proxy-viewer/log-formats/index.ts +0 -23
- package/src/components/proxy-viewer/log-formats/openai.ts +0 -256
- package/src/components/proxy-viewer/log-formats/types.ts +0 -40
- package/src/components/proxy-viewer/log-formats/unknown.ts +0 -18
- package/src/components/proxy-viewer/logEntryVisibility.ts +0 -73
- package/src/components/proxy-viewer/logFocus.ts +0 -54
- package/src/components/proxy-viewer/proxyViewerContainerLogic.ts +0 -124
- package/src/components/proxy-viewer/proxyViewerLogic.ts +0 -488
- package/src/components/proxy-viewer/replayComparison.ts +0 -131
- package/src/components/proxy-viewer/requestDiff.ts +0 -277
- package/src/components/proxy-viewer/requestTools.ts +0 -220
- package/src/components/proxy-viewer/useCopyFeedback.ts +0 -38
- package/src/components/proxy-viewer/useKeyboardNavigation.ts +0 -232
- package/src/components/proxy-viewer/viewerState.ts +0 -176
- package/src/components/ui/badge.tsx +0 -47
- package/src/components/ui/button.tsx +0 -47
- package/src/components/ui/collapsible.tsx +0 -21
- package/src/components/ui/confirm-dialog.tsx +0 -51
- package/src/components/ui/crab-logo.tsx +0 -45
- package/src/components/ui/crab-variants.tsx +0 -467
- package/src/components/ui/dialog.tsx +0 -130
- package/src/components/ui/icon-trigger.ts +0 -2
- package/src/components/ui/json-viewer-bulk.ts +0 -132
- package/src/components/ui/json-viewer.tsx +0 -508
- package/src/components/ui/scroll-area.tsx +0 -54
- package/src/components/ui/select.tsx +0 -178
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/tabs.tsx +0 -88
- package/src/components/ui/tooltip.tsx +0 -51
- package/src/components/ui/transient-toast.tsx +0 -105
- package/src/contracts/anthropic.ts +0 -230
- package/src/contracts/index.ts +0 -59
- package/src/contracts/json.ts +0 -37
- package/src/contracts/log.ts +0 -139
- package/src/contracts/openai.ts +0 -267
- package/src/index.css +0 -11
- package/src/knowledge/candidateStore.ts +0 -94
- package/src/knowledge/distiller.ts +0 -98
- package/src/knowledge/openclawClient.ts +0 -161
- package/src/knowledge/openclawGatewayClient.ts +0 -249
- package/src/knowledge/openclawMarkdown.ts +0 -146
- package/src/knowledge/redactor.ts +0 -80
- package/src/knowledge/types.ts +0 -83
- package/src/lib/alertContract.ts +0 -76
- package/src/lib/apiClient.ts +0 -166
- package/src/lib/apiFormat.ts +0 -29
- package/src/lib/backendImplementation.ts +0 -16
- package/src/lib/basePath.ts +0 -107
- package/src/lib/browserRuntimeContract.ts +0 -124
- package/src/lib/browserRuntimeResolver.ts +0 -333
- package/src/lib/chunkLoadRecovery.ts +0 -83
- package/src/lib/claudeCodeStrip.ts +0 -98
- package/src/lib/clipboard.ts +0 -67
- package/src/lib/codingAgentEndpoints.ts +0 -54
- package/src/lib/ecosystemContract.ts +0 -130
- package/src/lib/export-logs.ts +0 -287
- package/src/lib/groupContract.ts +0 -166
- package/src/lib/instanceContract.ts +0 -239
- package/src/lib/jsonFile.ts +0 -46
- package/src/lib/logImportContract.ts +0 -12
- package/src/lib/managedInstance.ts +0 -112
- package/src/lib/mask.ts +0 -4
- package/src/lib/objectUtils.ts +0 -22
- package/src/lib/piAgentContract.ts +0 -102
- package/src/lib/providerContract.ts +0 -125
- package/src/lib/providerImportContract.ts +0 -27
- package/src/lib/providerModelMetadata.ts +0 -458
- package/src/lib/providerTestContract.ts +0 -170
- package/src/lib/providerTestPrompt.ts +0 -100
- package/src/lib/publicBasePathContext.ts +0 -137
- package/src/lib/resourceLimits.ts +0 -345
- package/src/lib/runContract.ts +0 -164
- package/src/lib/runtimeConfig.ts +0 -43
- package/src/lib/safeDiagnostic.ts +0 -38
- package/src/lib/safeFetch.ts +0 -508
- package/src/lib/serverPort.ts +0 -47
- package/src/lib/sessionInfoContract.ts +0 -78
- package/src/lib/sessionUrl.ts +0 -46
- package/src/lib/ssrfGuard.ts +0 -133
- package/src/lib/stopReason.ts +0 -130
- package/src/lib/themeMode.ts +0 -41
- package/src/lib/timeDisplay.ts +0 -22
- package/src/lib/toolTrace.ts +0 -290
- package/src/lib/uiScale.ts +0 -117
- package/src/lib/unknown.ts +0 -27
- package/src/lib/upstreamUrl.ts +0 -150
- package/src/lib/useAlerts.ts +0 -82
- package/src/lib/useGroupEvidence.ts +0 -37
- package/src/lib/useGroups.ts +0 -32
- package/src/lib/useOnboarding.ts +0 -93
- package/src/lib/useProviders.ts +0 -30
- package/src/lib/useStripConfig.ts +0 -154
- package/src/lib/useThemeMode.ts +0 -40
- package/src/lib/useUiScale.ts +0 -52
- package/src/lib/utils.ts +0 -78
- package/src/mcp/currentContext.ts +0 -65
- package/src/mcp/instanceHandlers.ts +0 -151
- package/src/mcp/loopback.ts +0 -88
- package/src/mcp/mode.ts +0 -75
- package/src/mcp/previewExtractor.ts +0 -233
- package/src/mcp/server.ts +0 -2274
- package/src/mcp/toolHandlers.ts +0 -1533
- package/src/proxy/alerts.ts +0 -664
- package/src/proxy/chunkStorage.ts +0 -123
- package/src/proxy/config.ts +0 -218
- package/src/proxy/constants.ts +0 -66
- package/src/proxy/dataDir.ts +0 -147
- package/src/proxy/ecosystemExecutionConfirmation.ts +0 -114
- package/src/proxy/ecosystemExecutionRoute.ts +0 -116
- package/src/proxy/ecosystemRegistry.ts +0 -296
- package/src/proxy/ecosystemTasks.ts +0 -415
- package/src/proxy/evidenceAnalysis.ts +0 -526
- package/src/proxy/evidenceExporter.ts +0 -235
- package/src/proxy/formats/anthropic/anthropicProvider.ts +0 -75
- package/src/proxy/formats/anthropic/handler.ts +0 -75
- package/src/proxy/formats/anthropic/index.ts +0 -13
- package/src/proxy/formats/anthropic/register.ts +0 -4
- package/src/proxy/formats/anthropic/schemas.ts +0 -6
- package/src/proxy/formats/anthropic/stream.ts +0 -205
- package/src/proxy/formats/handler.ts +0 -46
- package/src/proxy/formats/index.ts +0 -13
- package/src/proxy/formats/jsonSchema.ts +0 -2
- package/src/proxy/formats/openai/alibabaProvider.ts +0 -34
- package/src/proxy/formats/openai/codexMetadata.ts +0 -61
- package/src/proxy/formats/openai/handler.ts +0 -133
- package/src/proxy/formats/openai/index.ts +0 -33
- package/src/proxy/formats/openai/provider.ts +0 -50
- package/src/proxy/formats/openai/register.ts +0 -4
- package/src/proxy/formats/openai/schemas.ts +0 -25
- package/src/proxy/formats/openai/stream.ts +0 -600
- package/src/proxy/formats/openai/zhipuProvider.ts +0 -31
- package/src/proxy/formats/providerAdapter.ts +0 -50
- package/src/proxy/formats/providerRegistry.ts +0 -51
- package/src/proxy/formats/providers/index.ts +0 -3
- package/src/proxy/formats/registry.ts +0 -65
- package/src/proxy/groupEvidenceExporter.ts +0 -371
- package/src/proxy/groupStore.ts +0 -336
- package/src/proxy/handler.ts +0 -729
- package/src/proxy/identityProxy.ts +0 -1842
- package/src/proxy/jsonlRecovery.ts +0 -554
- package/src/proxy/logBodyChunks.ts +0 -91
- package/src/proxy/logFinalizer.ts +0 -777
- package/src/proxy/logFinalizer.worker.ts +0 -24
- package/src/proxy/logImportUpload.ts +0 -128
- package/src/proxy/logImporter.ts +0 -813
- package/src/proxy/logIndex.ts +0 -663
- package/src/proxy/logSearch.ts +0 -122
- package/src/proxy/logger.ts +0 -537
- package/src/proxy/openaiOrphanToolStrip.ts +0 -142
- package/src/proxy/platformCommands.ts +0 -258
- package/src/proxy/privateDataPath.ts +0 -419
- package/src/proxy/providerImporters.ts +0 -578
- package/src/proxy/providerScanStore.ts +0 -87
- package/src/proxy/providerSecretStore.ts +0 -216
- package/src/proxy/providers.ts +0 -998
- package/src/proxy/rawStreamCapture.ts +0 -342
- package/src/proxy/runFailures.ts +0 -94
- package/src/proxy/runStore.ts +0 -214
- package/src/proxy/runtimeAdmission.ts +0 -55
- package/src/proxy/runtimeHealth.ts +0 -221
- package/src/proxy/runtimeMetrics.ts +0 -149
- package/src/proxy/runtimeShutdown.ts +0 -157
- package/src/proxy/schemas.ts +0 -112
- package/src/proxy/sessionArchive.ts +0 -479
- package/src/proxy/sessionInfo.ts +0 -266
- package/src/proxy/sessionProcess.ts +0 -194
- package/src/proxy/sessionRuntime.ts +0 -121
- package/src/proxy/sessionSupervisor.ts +0 -305
- package/src/proxy/sessionWorkerEntry.ts +0 -26
- package/src/proxy/shutdownCoordinator.ts +0 -90
- package/src/proxy/socketTracker.ts +0 -639
- package/src/proxy/sqliteLogIndex.ts +0 -733
- package/src/proxy/storageLifecycle.ts +0 -432
- package/src/proxy/store.ts +0 -1773
- package/src/proxy/streamTiming.ts +0 -31
- package/src/proxy/toolSchemaWarnings.ts +0 -529
- package/src/proxy/uiAssetServer.ts +0 -106
- package/src/proxy/upstream.ts +0 -127
- package/src/services/alerts.ts +0 -12
- package/src/services/groups.ts +0 -119
- package/src/services/piAgent.ts +0 -421
- package/src/services/piAgentRetrieval.ts +0 -1180
- package/src/services/result.ts +0 -17
- package/src/services/runs.ts +0 -76
- package/src/ui/bootstrap.ts +0 -167
- package/src/ui/index.html +0 -38
- package/src/ui/logsStreamClient.ts +0 -20
- package/src/ui/main.tsx +0 -13
- package/src/ui/public/runtime-config.js +0 -2
- package/src/ui/router.tsx +0 -13
- package/src/ui/routes/__root.tsx +0 -69
- package/src/ui/routes/index.tsx +0 -35
- package/src/ui/routes/session/$sessionId.tsx +0 -50
- package/src/ui/runtimeDiscovery.ts +0 -50
|
@@ -1,887 +0,0 @@
|
|
|
1
|
-
import { ChevronDown, ChevronRight, Info } from "lucide-react";
|
|
2
|
-
import { type JSX, useMemo, useState } from "react";
|
|
3
|
-
import { cn, formatContextWindowTokens, formatTokens } from "../../../lib/utils";
|
|
4
|
-
import { useProviders } from "../../../lib/useProviders";
|
|
5
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../ui/tooltip";
|
|
6
|
-
import {
|
|
7
|
-
analyzeContextIntelligence,
|
|
8
|
-
CONTEXT_USAGE_THRESHOLDS,
|
|
9
|
-
type ContextDiagnostic,
|
|
10
|
-
type DuplicateContextGroup,
|
|
11
|
-
type DuplicateContextSegment,
|
|
12
|
-
type ContextHealth,
|
|
13
|
-
type ContextHealthLevel,
|
|
14
|
-
type ContextIntelligence,
|
|
15
|
-
type ContextRiskLevel,
|
|
16
|
-
} from "./contextIntelligence";
|
|
17
|
-
import { ROLE_COLOR_CLASSES, SegmentBar } from "./SegmentBar";
|
|
18
|
-
import { ANATOMY_ROLE_LABELS, type AnatomyRole, type AnatomySegment } from "./types";
|
|
19
|
-
|
|
20
|
-
export type RequestAnatomyProps = {
|
|
21
|
-
/** Parsed request body, or `null` if it cannot be parsed. */
|
|
22
|
-
parsed: unknown | null;
|
|
23
|
-
/** Server-reported input token count, or `null` if unknown. */
|
|
24
|
-
inputTokens: number | null;
|
|
25
|
-
/** Model name from the captured log; falls back to the parsed request body when omitted. */
|
|
26
|
-
model?: string | null;
|
|
27
|
-
/** Optional callback fired when the user activates a concrete request segment. */
|
|
28
|
-
onSegmentActivate?: (segment: AnatomySegment) => void;
|
|
29
|
-
/** Pre-computed segments; if provided, `parsed` is ignored. */
|
|
30
|
-
segments?: AnatomySegment[] | null;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
type ContextViewMode = "role" | "segment";
|
|
34
|
-
|
|
35
|
-
const DIVERGENCE_AMBER_THRESHOLD = 0.25;
|
|
36
|
-
const TOP_CONTRIBUTOR_COUNT = 5;
|
|
37
|
-
const DUPLICATE_GROUP_DISPLAY_COUNT = 4;
|
|
38
|
-
const DUPLICATE_SEGMENT_DISPLAY_COUNT = 6;
|
|
39
|
-
const TOOL_SCHEMA_SAVINGS_RATIO = 0.25;
|
|
40
|
-
const SYSTEM_PROMPT_SAVINGS_RATIO = 0.2;
|
|
41
|
-
const HISTORY_SAVINGS_RATIO = 0.2;
|
|
42
|
-
const ROLE_ORDER: AnatomyRole[] = ["system", "user", "assistant", "tool", "tools"];
|
|
43
|
-
|
|
44
|
-
const ROLE_DESCRIPTIONS: Record<AnatomyRole, string> = {
|
|
45
|
-
system: "instructions",
|
|
46
|
-
user: "user turns",
|
|
47
|
-
assistant: "assistant turns",
|
|
48
|
-
tool: "tool results",
|
|
49
|
-
tools: "tool schemas",
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const VIEW_MODE_OPTIONS: Array<{ value: ContextViewMode; label: string }> = [
|
|
53
|
-
{ value: "role", label: "By Role" },
|
|
54
|
-
{ value: "segment", label: "By Segment" },
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
const RISK_CLASS: Record<ContextRiskLevel, string> = {
|
|
58
|
-
unknown: "bg-muted-foreground/50",
|
|
59
|
-
ok: "bg-emerald-500/70",
|
|
60
|
-
watch: "bg-amber-400/75",
|
|
61
|
-
danger: "bg-red-500/80",
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const RISK_TEXT_CLASS: Record<ContextRiskLevel, string> = {
|
|
65
|
-
unknown: "text-muted-foreground",
|
|
66
|
-
ok: "text-emerald-400",
|
|
67
|
-
watch: "text-amber-300",
|
|
68
|
-
danger: "text-red-300",
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const HEALTH_BADGE_CLASS: Record<ContextHealthLevel, string> = {
|
|
72
|
-
ok: "border-emerald-400/30 bg-emerald-400/8 text-emerald-200",
|
|
73
|
-
optimizable: "border-amber-400/30 bg-amber-400/8 text-amber-100",
|
|
74
|
-
risk: "border-red-400/35 bg-red-500/10 text-red-100",
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const HEALTH_LABEL: Record<ContextHealthLevel, string> = {
|
|
78
|
-
ok: "OK",
|
|
79
|
-
optimizable: "Optimizable",
|
|
80
|
-
risk: "Risk",
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
type ContextAdvisorKind = ContextDiagnostic["kind"] | "context-pressure" | "context-window";
|
|
84
|
-
|
|
85
|
-
type ContextAdvisorItem = {
|
|
86
|
-
key: string;
|
|
87
|
-
kind: ContextAdvisorKind;
|
|
88
|
-
severity: "watch" | "danger";
|
|
89
|
-
title: string;
|
|
90
|
-
impact: string;
|
|
91
|
-
action: string;
|
|
92
|
-
savingTokens: number | null;
|
|
93
|
-
target: AnatomySegment | null;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
function formatPercent(value: number): string {
|
|
97
|
-
if (value >= 10) return `${value.toFixed(0)}%`;
|
|
98
|
-
if (value >= 1) return `${value.toFixed(1)}%`;
|
|
99
|
-
if (value > 0) return "<1%";
|
|
100
|
-
return "0%";
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function formatUsagePercent(value: number | null): string {
|
|
104
|
-
if (value === null) return "Unknown";
|
|
105
|
-
return formatPercent(value * 100);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function formatRemainingTokens(value: number | null): string {
|
|
109
|
-
if (value === null) return "Unknown";
|
|
110
|
-
if (value < 0) return `-${formatTokens(Math.abs(value))}`;
|
|
111
|
-
return formatTokens(value);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function formatContextLimit(intelligence: ContextIntelligence): string {
|
|
115
|
-
if (intelligence.contextWindow.tokens === null) return "Unknown";
|
|
116
|
-
return formatContextWindowTokens(intelligence.contextWindow.tokens);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function formatContextSource(intelligence: ContextIntelligence): string {
|
|
120
|
-
switch (intelligence.contextWindow.source) {
|
|
121
|
-
case "request":
|
|
122
|
-
return intelligence.contextWindow.label;
|
|
123
|
-
case "provider":
|
|
124
|
-
return intelligence.contextWindow.label;
|
|
125
|
-
case "model-rule":
|
|
126
|
-
return intelligence.contextWindow.label;
|
|
127
|
-
case "unknown":
|
|
128
|
-
return intelligence.model === null
|
|
129
|
-
? intelligence.contextWindow.label
|
|
130
|
-
: `${intelligence.contextWindow.label}: ${intelligence.model}`;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function advisorTone(severity: ContextAdvisorItem["severity"]): string {
|
|
135
|
-
switch (severity) {
|
|
136
|
-
case "watch":
|
|
137
|
-
return "border-amber-400/30 bg-amber-400/8 text-amber-100";
|
|
138
|
-
case "danger":
|
|
139
|
-
return "border-red-400/35 bg-red-500/10 text-red-100";
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function formatContextHealth(health: ContextHealth): string {
|
|
144
|
-
const label = HEALTH_LABEL[health.level];
|
|
145
|
-
if (health.level === "ok") return label;
|
|
146
|
-
const countLabel =
|
|
147
|
-
health.level === "risk"
|
|
148
|
-
? `${String(health.opportunityCount)} signal${health.opportunityCount === 1 ? "" : "s"}`
|
|
149
|
-
: `${String(health.opportunityCount)} opportunit${
|
|
150
|
-
health.opportunityCount === 1 ? "y" : "ies"
|
|
151
|
-
}`;
|
|
152
|
-
if (health.reclaimableTokens <= 0) return `${label} | ${countLabel}`;
|
|
153
|
-
return `${label} | ${countLabel} | ~${formatTokens(health.reclaimableTokens)} tokens`;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function ContextHealthBadge({ health }: { health: ContextHealth }): JSX.Element {
|
|
157
|
-
return (
|
|
158
|
-
<span
|
|
159
|
-
className={cn(
|
|
160
|
-
"inline-flex h-5 max-w-full items-center rounded border px-1.5 font-mono text-[10px]",
|
|
161
|
-
HEALTH_BADGE_CLASS[health.level],
|
|
162
|
-
)}
|
|
163
|
-
aria-label={`Context health ${formatContextHealth(health)}`}
|
|
164
|
-
>
|
|
165
|
-
<span className="truncate">{formatContextHealth(health)}</span>
|
|
166
|
-
</span>
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function roleUsageTokens(intelligence: ContextIntelligence, role: AnatomyRole): number {
|
|
171
|
-
return intelligence.roleUsages.find((usage) => usage.role === role)?.tokens ?? 0;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function roleUsagePercent(intelligence: ContextIntelligence, role: AnatomyRole): number {
|
|
175
|
-
return intelligence.roleUsages.find((usage) => usage.role === role)?.percent ?? 0;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function estimatedSaving(tokens: number, ratio: number): number | null {
|
|
179
|
-
if (tokens <= 0) return null;
|
|
180
|
-
const saving = Math.round(tokens * ratio);
|
|
181
|
-
return saving > 0 ? saving : null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function largestSegmentForRole(
|
|
185
|
-
segments: readonly AnatomySegment[],
|
|
186
|
-
role: AnatomyRole,
|
|
187
|
-
): AnatomySegment | null {
|
|
188
|
-
return (
|
|
189
|
-
[...segments]
|
|
190
|
-
.filter((segment) => segment.role === role)
|
|
191
|
-
.sort((left, right) => right.size - left.size)[0] ?? null
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function largestSegmentForHistory(segments: readonly AnatomySegment[]): AnatomySegment | null {
|
|
196
|
-
return (
|
|
197
|
-
[...segments]
|
|
198
|
-
.filter(
|
|
199
|
-
(segment) =>
|
|
200
|
-
segment.role === "user" || segment.role === "assistant" || segment.role === "tool",
|
|
201
|
-
)
|
|
202
|
-
.sort((left, right) => right.size - left.size)[0] ?? null
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function firstDuplicateTarget(
|
|
207
|
-
groups: readonly DuplicateContextGroup[],
|
|
208
|
-
segments: readonly AnatomySegment[],
|
|
209
|
-
): AnatomySegment | null {
|
|
210
|
-
const firstGroup = groups[0] ?? null;
|
|
211
|
-
if (firstGroup === null) return null;
|
|
212
|
-
const firstSegment = firstGroup.segments[0] ?? null;
|
|
213
|
-
if (firstSegment === null) return null;
|
|
214
|
-
return resolveDuplicateSegment(firstSegment, segments);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function duplicateSaving(groups: readonly DuplicateContextGroup[]): number | null {
|
|
218
|
-
const saving = groups.reduce((sum, group) => sum + group.repeatedTokens, 0);
|
|
219
|
-
return saving > 0 ? saving : null;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function historyTokens(intelligence: ContextIntelligence): number {
|
|
223
|
-
return (
|
|
224
|
-
roleUsageTokens(intelligence, "user") +
|
|
225
|
-
roleUsageTokens(intelligence, "assistant") +
|
|
226
|
-
roleUsageTokens(intelligence, "tool")
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function advisorItemFromDiagnostic({
|
|
231
|
-
diagnostic,
|
|
232
|
-
intelligence,
|
|
233
|
-
segments,
|
|
234
|
-
}: {
|
|
235
|
-
diagnostic: ContextDiagnostic;
|
|
236
|
-
intelligence: ContextIntelligence;
|
|
237
|
-
segments: readonly AnatomySegment[];
|
|
238
|
-
}): ContextAdvisorItem {
|
|
239
|
-
switch (diagnostic.kind) {
|
|
240
|
-
case "duplicate":
|
|
241
|
-
return {
|
|
242
|
-
key: "duplicate",
|
|
243
|
-
kind: "duplicate",
|
|
244
|
-
severity: diagnostic.severity,
|
|
245
|
-
title: "Duplicate content",
|
|
246
|
-
impact: diagnostic.detail,
|
|
247
|
-
action: "Remove repeated blocks or keep one canonical copy in the next request.",
|
|
248
|
-
savingTokens: duplicateSaving(intelligence.duplicateGroups),
|
|
249
|
-
target: firstDuplicateTarget(intelligence.duplicateGroups, segments),
|
|
250
|
-
};
|
|
251
|
-
case "tool-schema": {
|
|
252
|
-
const tokens = roleUsageTokens(intelligence, "tools");
|
|
253
|
-
const percent = roleUsagePercent(intelligence, "tools");
|
|
254
|
-
return {
|
|
255
|
-
key: "tool-schema",
|
|
256
|
-
kind: "tool-schema",
|
|
257
|
-
severity: diagnostic.severity,
|
|
258
|
-
title: "Tool schema pressure",
|
|
259
|
-
impact: `Tool definitions use ~${formatTokens(tokens)} tokens (${formatPercent(
|
|
260
|
-
percent * 100,
|
|
261
|
-
)}).`,
|
|
262
|
-
action: "Disable unused tools or shorten verbose schema descriptions.",
|
|
263
|
-
savingTokens: estimatedSaving(tokens, TOOL_SCHEMA_SAVINGS_RATIO),
|
|
264
|
-
target: largestSegmentForRole(segments, "tools"),
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
case "system-heavy": {
|
|
268
|
-
const tokens = roleUsageTokens(intelligence, "system");
|
|
269
|
-
const percent = roleUsagePercent(intelligence, "system");
|
|
270
|
-
return {
|
|
271
|
-
key: "system-heavy",
|
|
272
|
-
kind: "system-heavy",
|
|
273
|
-
severity: diagnostic.severity,
|
|
274
|
-
title: "Large system prompt",
|
|
275
|
-
impact: `System instructions use ~${formatTokens(tokens)} tokens (${formatPercent(
|
|
276
|
-
percent * 100,
|
|
277
|
-
)}).`,
|
|
278
|
-
action: "Compress repeated policy text and move stable guidance into reusable memory.",
|
|
279
|
-
savingTokens: estimatedSaving(tokens, SYSTEM_PROMPT_SAVINGS_RATIO),
|
|
280
|
-
target: largestSegmentForRole(segments, "system"),
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
case "history-bloat": {
|
|
284
|
-
const tokens = historyTokens(intelligence);
|
|
285
|
-
return {
|
|
286
|
-
key: "history-bloat",
|
|
287
|
-
kind: "history-bloat",
|
|
288
|
-
severity: diagnostic.severity,
|
|
289
|
-
title: "Conversation history growth",
|
|
290
|
-
impact: diagnostic.detail,
|
|
291
|
-
action: "Summarize older turns and trim resolved tool outputs before continuing.",
|
|
292
|
-
savingTokens: estimatedSaving(tokens, HISTORY_SAVINGS_RATIO),
|
|
293
|
-
target: largestSegmentForHistory(segments),
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function buildContextAdvisorItems({
|
|
300
|
-
intelligence,
|
|
301
|
-
segments,
|
|
302
|
-
}: {
|
|
303
|
-
intelligence: ContextIntelligence;
|
|
304
|
-
segments: readonly AnatomySegment[];
|
|
305
|
-
}): ContextAdvisorItem[] {
|
|
306
|
-
const items: ContextAdvisorItem[] = [];
|
|
307
|
-
|
|
308
|
-
if (intelligence.riskLevel === "watch" || intelligence.riskLevel === "danger") {
|
|
309
|
-
items.push({
|
|
310
|
-
key: "context-pressure",
|
|
311
|
-
kind: "context-pressure",
|
|
312
|
-
severity: intelligence.riskLevel === "danger" ? "danger" : "watch",
|
|
313
|
-
title: "Context window pressure",
|
|
314
|
-
impact: `${formatUsagePercent(intelligence.usagePercent)} used, ${formatRemainingTokens(
|
|
315
|
-
intelligence.remainingAfterReserveTokens,
|
|
316
|
-
)} remaining after output reserve.`,
|
|
317
|
-
action: "Start with the largest context block, then remove duplicates or summarize history.",
|
|
318
|
-
savingTokens: null,
|
|
319
|
-
target: [...segments].sort((left, right) => right.size - left.size)[0] ?? null,
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (intelligence.contextWindow.source === "unknown") {
|
|
324
|
-
items.push({
|
|
325
|
-
key: "context-window",
|
|
326
|
-
kind: "context-window",
|
|
327
|
-
severity: "watch",
|
|
328
|
-
title: "Context window unknown",
|
|
329
|
-
impact: "Agent Inspector cannot calculate remaining space for this model.",
|
|
330
|
-
action: "Add model metadata in Provider settings so risk and remaining space are accurate.",
|
|
331
|
-
savingTokens: null,
|
|
332
|
-
target: null,
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
intelligence.diagnostics.forEach((diagnostic) => {
|
|
337
|
-
items.push(advisorItemFromDiagnostic({ diagnostic, intelligence, segments }));
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
return items;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function ContextAdvisorPanel({
|
|
344
|
-
items,
|
|
345
|
-
onSegmentActivate,
|
|
346
|
-
}: {
|
|
347
|
-
items: readonly ContextAdvisorItem[];
|
|
348
|
-
onSegmentActivate?: (segment: AnatomySegment) => void;
|
|
349
|
-
}): JSX.Element | null {
|
|
350
|
-
if (items.length === 0) return null;
|
|
351
|
-
|
|
352
|
-
return (
|
|
353
|
-
<section className="space-y-2 rounded border border-border/70 bg-muted/15 p-2">
|
|
354
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
355
|
-
<div className="text-xs font-medium text-foreground">Context Advisor</div>
|
|
356
|
-
<div className="font-mono text-[11px] text-muted-foreground">
|
|
357
|
-
{String(items.length)} issue{items.length === 1 ? "" : "s"}
|
|
358
|
-
</div>
|
|
359
|
-
</div>
|
|
360
|
-
<div className="grid gap-2">
|
|
361
|
-
{items.map((item) => {
|
|
362
|
-
const target = item.target;
|
|
363
|
-
const canJump = target !== null && onSegmentActivate !== undefined;
|
|
364
|
-
return (
|
|
365
|
-
<div
|
|
366
|
-
key={item.key}
|
|
367
|
-
className={cn("rounded border px-2 py-1.5 text-xs", advisorTone(item.severity))}
|
|
368
|
-
>
|
|
369
|
-
<div className="flex min-w-0 flex-wrap items-start justify-between gap-2">
|
|
370
|
-
<div className="min-w-0">
|
|
371
|
-
<div className="font-medium">{item.title}</div>
|
|
372
|
-
<div className="mt-0.5 text-[11px] text-muted-foreground">
|
|
373
|
-
Impact: {item.impact}
|
|
374
|
-
</div>
|
|
375
|
-
<div className="mt-0.5 text-[11px] text-muted-foreground">
|
|
376
|
-
Action: {item.action}
|
|
377
|
-
</div>
|
|
378
|
-
</div>
|
|
379
|
-
<div className="flex shrink-0 items-center gap-1.5">
|
|
380
|
-
{item.savingTokens !== null && (
|
|
381
|
-
<span className="font-mono text-[11px] text-muted-foreground">
|
|
382
|
-
Save ~{formatTokens(item.savingTokens)}
|
|
383
|
-
</span>
|
|
384
|
-
)}
|
|
385
|
-
{canJump && target !== null && onSegmentActivate !== undefined && (
|
|
386
|
-
<button
|
|
387
|
-
type="button"
|
|
388
|
-
onClick={() => onSegmentActivate(target)}
|
|
389
|
-
className="inline-flex h-6 items-center gap-1 rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground hover:border-border hover:bg-muted/40 hover:text-foreground"
|
|
390
|
-
title="Jump to the most relevant context block"
|
|
391
|
-
>
|
|
392
|
-
<ChevronRight className="size-3" />
|
|
393
|
-
Jump
|
|
394
|
-
</button>
|
|
395
|
-
)}
|
|
396
|
-
</div>
|
|
397
|
-
</div>
|
|
398
|
-
</div>
|
|
399
|
-
);
|
|
400
|
-
})}
|
|
401
|
-
</div>
|
|
402
|
-
</section>
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function ContextIntelligenceStrip({
|
|
407
|
-
intelligence,
|
|
408
|
-
segments,
|
|
409
|
-
onSegmentActivate,
|
|
410
|
-
}: {
|
|
411
|
-
intelligence: ContextIntelligence;
|
|
412
|
-
segments: readonly AnatomySegment[];
|
|
413
|
-
onSegmentActivate?: (segment: AnatomySegment) => void;
|
|
414
|
-
}): JSX.Element {
|
|
415
|
-
const usageWidth =
|
|
416
|
-
intelligence.usagePercent === null ? 0 : Math.max(1, intelligence.usagePercent * 100);
|
|
417
|
-
const outputReserveLabel =
|
|
418
|
-
intelligence.outputReserveTokens === null
|
|
419
|
-
? "No output reserve"
|
|
420
|
-
: `Output reserve ${formatTokens(intelligence.outputReserveTokens)}`;
|
|
421
|
-
const largestRole = intelligence.largestRole;
|
|
422
|
-
const advisorItems = buildContextAdvisorItems({ intelligence, segments });
|
|
423
|
-
|
|
424
|
-
return (
|
|
425
|
-
<div className="space-y-2">
|
|
426
|
-
<div className="grid gap-x-4 gap-y-2 border-y border-border/60 py-2 text-xs sm:grid-cols-4">
|
|
427
|
-
<div className="min-w-0">
|
|
428
|
-
<div className="text-[10px] font-medium uppercase text-muted-foreground">Window</div>
|
|
429
|
-
<div className="mt-0.5 truncate font-mono text-foreground">
|
|
430
|
-
{formatContextLimit(intelligence)}
|
|
431
|
-
</div>
|
|
432
|
-
<div className="truncate text-[11px] text-muted-foreground">
|
|
433
|
-
{formatContextSource(intelligence)}
|
|
434
|
-
</div>
|
|
435
|
-
</div>
|
|
436
|
-
|
|
437
|
-
<div className="min-w-0">
|
|
438
|
-
<div className="text-[10px] font-medium uppercase text-muted-foreground">Used</div>
|
|
439
|
-
<div className={cn("mt-0.5 font-mono", RISK_TEXT_CLASS[intelligence.riskLevel])}>
|
|
440
|
-
{formatUsagePercent(intelligence.usagePercent)}
|
|
441
|
-
</div>
|
|
442
|
-
<div className="truncate text-[11px] text-muted-foreground">{outputReserveLabel}</div>
|
|
443
|
-
</div>
|
|
444
|
-
|
|
445
|
-
<div className="min-w-0">
|
|
446
|
-
<div className="text-[10px] font-medium uppercase text-muted-foreground">Remaining</div>
|
|
447
|
-
<div className="mt-0.5 font-mono text-foreground">
|
|
448
|
-
{formatRemainingTokens(intelligence.remainingAfterReserveTokens)}
|
|
449
|
-
</div>
|
|
450
|
-
<div className="truncate text-[11px] text-muted-foreground">
|
|
451
|
-
Input only {formatRemainingTokens(intelligence.remainingInputTokens)}
|
|
452
|
-
</div>
|
|
453
|
-
</div>
|
|
454
|
-
|
|
455
|
-
<div className="min-w-0">
|
|
456
|
-
<div className="text-[10px] font-medium uppercase text-muted-foreground">Largest</div>
|
|
457
|
-
{largestRole === null ? (
|
|
458
|
-
<div className="mt-0.5 text-muted-foreground">Unknown</div>
|
|
459
|
-
) : (
|
|
460
|
-
<>
|
|
461
|
-
<div className="mt-0.5 flex min-w-0 items-center gap-1.5">
|
|
462
|
-
<span
|
|
463
|
-
aria-hidden="true"
|
|
464
|
-
className={cn(
|
|
465
|
-
"size-2.5 shrink-0 rounded-[2px]",
|
|
466
|
-
ROLE_COLOR_CLASSES[largestRole.role],
|
|
467
|
-
)}
|
|
468
|
-
/>
|
|
469
|
-
<span className="truncate text-foreground">{largestRole.label}</span>
|
|
470
|
-
</div>
|
|
471
|
-
<div className="truncate font-mono text-[11px] text-muted-foreground">
|
|
472
|
-
{formatPercent(largestRole.percent * 100)} | ~{formatTokens(largestRole.tokens)}
|
|
473
|
-
</div>
|
|
474
|
-
</>
|
|
475
|
-
)}
|
|
476
|
-
</div>
|
|
477
|
-
</div>
|
|
478
|
-
|
|
479
|
-
{intelligence.contextWindow.tokens !== null && (
|
|
480
|
-
<div className="space-y-1.5">
|
|
481
|
-
<div className="relative h-2 overflow-hidden rounded-full bg-muted/40">
|
|
482
|
-
<div
|
|
483
|
-
className={cn("h-full rounded-full", RISK_CLASS[intelligence.riskLevel])}
|
|
484
|
-
style={{ width: `${String(usageWidth)}%` }}
|
|
485
|
-
/>
|
|
486
|
-
<div
|
|
487
|
-
aria-hidden="true"
|
|
488
|
-
className="absolute top-0 h-full w-px bg-amber-200/70"
|
|
489
|
-
style={{ left: `${String(CONTEXT_USAGE_THRESHOLDS.watch * 100)}%` }}
|
|
490
|
-
/>
|
|
491
|
-
<div
|
|
492
|
-
aria-hidden="true"
|
|
493
|
-
className="absolute top-0 h-full w-px bg-red-200/80"
|
|
494
|
-
style={{ left: `${String(CONTEXT_USAGE_THRESHOLDS.danger * 100)}%` }}
|
|
495
|
-
/>
|
|
496
|
-
</div>
|
|
497
|
-
<div className="flex justify-between text-[10px] text-muted-foreground">
|
|
498
|
-
<span>Context window</span>
|
|
499
|
-
<span>
|
|
500
|
-
Watch {formatUsagePercent(CONTEXT_USAGE_THRESHOLDS.watch)} / Danger{" "}
|
|
501
|
-
{formatUsagePercent(CONTEXT_USAGE_THRESHOLDS.danger)}
|
|
502
|
-
</span>
|
|
503
|
-
</div>
|
|
504
|
-
</div>
|
|
505
|
-
)}
|
|
506
|
-
|
|
507
|
-
<ContextAdvisorPanel items={advisorItems} onSegmentActivate={onSegmentActivate} />
|
|
508
|
-
</div>
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function resolveDuplicateSegment(
|
|
513
|
-
duplicateSegment: DuplicateContextSegment,
|
|
514
|
-
segments: readonly AnatomySegment[],
|
|
515
|
-
): AnatomySegment | null {
|
|
516
|
-
return segments.find((segment) => segment.path === duplicateSegment.path) ?? null;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function DuplicateContentPanel({
|
|
520
|
-
groups,
|
|
521
|
-
segments,
|
|
522
|
-
onSegmentActivate,
|
|
523
|
-
}: {
|
|
524
|
-
groups: readonly DuplicateContextGroup[];
|
|
525
|
-
segments: readonly AnatomySegment[];
|
|
526
|
-
onSegmentActivate?: (segment: AnatomySegment) => void;
|
|
527
|
-
}): JSX.Element | null {
|
|
528
|
-
const [expanded, setExpanded] = useState(false);
|
|
529
|
-
|
|
530
|
-
if (groups.length === 0) return null;
|
|
531
|
-
|
|
532
|
-
const visibleGroups = groups.slice(0, DUPLICATE_GROUP_DISPLAY_COUNT);
|
|
533
|
-
const hiddenGroupCount = groups.length - visibleGroups.length;
|
|
534
|
-
const totalRepeatedTokens = groups.reduce((sum, group) => sum + group.repeatedTokens, 0);
|
|
535
|
-
|
|
536
|
-
return (
|
|
537
|
-
<section className="space-y-2 rounded border border-border/70 bg-muted/15 p-2">
|
|
538
|
-
<button
|
|
539
|
-
type="button"
|
|
540
|
-
onClick={() => setExpanded((current) => !current)}
|
|
541
|
-
aria-expanded={expanded}
|
|
542
|
-
className="flex w-full flex-wrap items-center justify-between gap-2 rounded px-1 py-0.5 text-left transition-colors hover:bg-muted/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
|
|
543
|
-
>
|
|
544
|
-
<span className="inline-flex min-w-0 items-center gap-1.5">
|
|
545
|
-
{expanded ? (
|
|
546
|
-
<ChevronDown className="size-3.5 shrink-0 text-muted-foreground" />
|
|
547
|
-
) : (
|
|
548
|
-
<ChevronRight className="size-3.5 shrink-0 text-muted-foreground" />
|
|
549
|
-
)}
|
|
550
|
-
<span className="truncate text-xs font-medium text-foreground">Duplicate Content</span>
|
|
551
|
-
</span>
|
|
552
|
-
<span className="font-mono text-[11px] text-muted-foreground">
|
|
553
|
-
{String(groups.length)} group{groups.length === 1 ? "" : "s"} | ~
|
|
554
|
-
{formatTokens(totalRepeatedTokens)} repeated
|
|
555
|
-
</span>
|
|
556
|
-
</button>
|
|
557
|
-
|
|
558
|
-
{expanded && (
|
|
559
|
-
<>
|
|
560
|
-
<div className="space-y-2">
|
|
561
|
-
{visibleGroups.map((group, groupIndex) => {
|
|
562
|
-
const visibleSegments = group.segments.slice(0, DUPLICATE_SEGMENT_DISPLAY_COUNT);
|
|
563
|
-
const hiddenSegmentCount = group.segments.length - visibleSegments.length;
|
|
564
|
-
return (
|
|
565
|
-
<div
|
|
566
|
-
key={group.key}
|
|
567
|
-
className="space-y-1.5 border-t border-border/50 pt-2 first:border-t-0 first:pt-0"
|
|
568
|
-
>
|
|
569
|
-
<div className="flex min-w-0 items-center gap-2 text-xs">
|
|
570
|
-
<span className="w-5 shrink-0 text-right font-mono text-muted-foreground/70">
|
|
571
|
-
{String(groupIndex + 1)}
|
|
572
|
-
</span>
|
|
573
|
-
<span className="min-w-0 flex-1 truncate text-foreground">
|
|
574
|
-
{group.firstLabel}
|
|
575
|
-
</span>
|
|
576
|
-
<span className="shrink-0 font-mono text-muted-foreground">
|
|
577
|
-
x{String(group.count)} | ~{formatTokens(group.repeatedTokens)} saved
|
|
578
|
-
</span>
|
|
579
|
-
</div>
|
|
580
|
-
|
|
581
|
-
{group.preview.length > 0 && (
|
|
582
|
-
<div className="ml-7 line-clamp-2 break-words rounded bg-background/40 px-2 py-1 font-mono text-[11px] leading-4 text-muted-foreground">
|
|
583
|
-
{group.preview}
|
|
584
|
-
</div>
|
|
585
|
-
)}
|
|
586
|
-
|
|
587
|
-
<div className="ml-7 flex flex-wrap gap-1">
|
|
588
|
-
{visibleSegments.map((duplicateSegment, segmentIndex) => {
|
|
589
|
-
const target = resolveDuplicateSegment(duplicateSegment, segments);
|
|
590
|
-
const activate =
|
|
591
|
-
target === null || onSegmentActivate === undefined
|
|
592
|
-
? undefined
|
|
593
|
-
: onSegmentActivate;
|
|
594
|
-
const label = `${duplicateSegment.label} ~${formatTokens(
|
|
595
|
-
duplicateSegment.tokens,
|
|
596
|
-
)}`;
|
|
597
|
-
if (activate === undefined || target === null) {
|
|
598
|
-
return (
|
|
599
|
-
<span
|
|
600
|
-
key={`${duplicateSegment.path}-${segmentIndex}`}
|
|
601
|
-
className="inline-flex h-6 max-w-40 items-center rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground"
|
|
602
|
-
title={label}
|
|
603
|
-
>
|
|
604
|
-
<span className="truncate">{label}</span>
|
|
605
|
-
</span>
|
|
606
|
-
);
|
|
607
|
-
}
|
|
608
|
-
return (
|
|
609
|
-
<button
|
|
610
|
-
key={`${duplicateSegment.path}-${segmentIndex}`}
|
|
611
|
-
type="button"
|
|
612
|
-
onClick={() => activate(target)}
|
|
613
|
-
className="inline-flex h-6 max-w-40 items-center rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground hover:border-border hover:bg-muted/40 hover:text-foreground"
|
|
614
|
-
title="Jump to this duplicate block"
|
|
615
|
-
>
|
|
616
|
-
<span className="truncate">{label}</span>
|
|
617
|
-
</button>
|
|
618
|
-
);
|
|
619
|
-
})}
|
|
620
|
-
{hiddenSegmentCount > 0 && (
|
|
621
|
-
<span className="inline-flex h-6 items-center rounded border border-border/40 px-1.5 font-mono text-[11px] text-muted-foreground/70">
|
|
622
|
-
+{String(hiddenSegmentCount)}
|
|
623
|
-
</span>
|
|
624
|
-
)}
|
|
625
|
-
</div>
|
|
626
|
-
</div>
|
|
627
|
-
);
|
|
628
|
-
})}
|
|
629
|
-
</div>
|
|
630
|
-
|
|
631
|
-
{hiddenGroupCount > 0 && (
|
|
632
|
-
<div className="border-t border-border/50 pt-2 font-mono text-[11px] text-muted-foreground">
|
|
633
|
-
+{String(hiddenGroupCount)} more duplicate group{hiddenGroupCount === 1 ? "" : "s"}
|
|
634
|
-
</div>
|
|
635
|
-
)}
|
|
636
|
-
</>
|
|
637
|
-
)}
|
|
638
|
-
</section>
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
function aggregateByRole(segments: AnatomySegment[]): AnatomySegment[] {
|
|
643
|
-
const result: AnatomySegment[] = [];
|
|
644
|
-
for (const role of ROLE_ORDER) {
|
|
645
|
-
const matching = segments.filter((segment) => segment.role === role);
|
|
646
|
-
if (matching.length === 0) continue;
|
|
647
|
-
const size = matching.reduce((sum, segment) => sum + segment.size, 0);
|
|
648
|
-
const characters = matching.reduce((sum, segment) => sum + segment.characters, 0);
|
|
649
|
-
const label = ANATOMY_ROLE_LABELS[role];
|
|
650
|
-
const text = `${matching.length} segment${matching.length === 1 ? "" : "s"} grouped as ${
|
|
651
|
-
ROLE_DESCRIPTIONS[role]
|
|
652
|
-
}`;
|
|
653
|
-
result.push({
|
|
654
|
-
role,
|
|
655
|
-
label,
|
|
656
|
-
size,
|
|
657
|
-
characters,
|
|
658
|
-
text,
|
|
659
|
-
path: `role:${role}`,
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
return result;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
function topContributors(segments: AnatomySegment[]): AnatomySegment[] {
|
|
666
|
-
return [...segments].sort((a, b) => b.size - a.size).slice(0, TOP_CONTRIBUTOR_COUNT);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* Render a request context breakdown: headline metrics, a stacked token bar,
|
|
671
|
-
* role legend, and the largest individual context blocks.
|
|
672
|
-
*/
|
|
673
|
-
export function RequestAnatomy({
|
|
674
|
-
parsed,
|
|
675
|
-
inputTokens,
|
|
676
|
-
model = null,
|
|
677
|
-
onSegmentActivate,
|
|
678
|
-
segments: providedSegments,
|
|
679
|
-
}: RequestAnatomyProps): JSX.Element | null {
|
|
680
|
-
const { providers } = useProviders();
|
|
681
|
-
const [viewMode, setViewMode] = useState<ContextViewMode>("role");
|
|
682
|
-
const segments = useMemo(() => providedSegments ?? null, [providedSegments]);
|
|
683
|
-
const total = useMemo(
|
|
684
|
-
() => (segments ?? []).reduce((sum, segment) => sum + segment.size, 0),
|
|
685
|
-
[segments],
|
|
686
|
-
);
|
|
687
|
-
const roleSegments = useMemo(
|
|
688
|
-
() => (segments === null ? [] : aggregateByRole(segments)),
|
|
689
|
-
[segments],
|
|
690
|
-
);
|
|
691
|
-
const topSegments = useMemo(
|
|
692
|
-
() => (segments === null ? [] : topContributors(segments)),
|
|
693
|
-
[segments],
|
|
694
|
-
);
|
|
695
|
-
const intelligence = useMemo(
|
|
696
|
-
() =>
|
|
697
|
-
segments === null
|
|
698
|
-
? null
|
|
699
|
-
: analyzeContextIntelligence({
|
|
700
|
-
segments,
|
|
701
|
-
inputTokens,
|
|
702
|
-
parsed,
|
|
703
|
-
model,
|
|
704
|
-
providers,
|
|
705
|
-
}),
|
|
706
|
-
[inputTokens, model, parsed, providers, segments],
|
|
707
|
-
);
|
|
708
|
-
|
|
709
|
-
const showDivergenceWarning = useMemo(() => {
|
|
710
|
-
if (segments === null) return false;
|
|
711
|
-
if (inputTokens === null) return false;
|
|
712
|
-
if (total === 0) return false;
|
|
713
|
-
const ratio = Math.abs(inputTokens - total) / Math.max(inputTokens, total);
|
|
714
|
-
return ratio >= DIVERGENCE_AMBER_THRESHOLD;
|
|
715
|
-
}, [inputTokens, segments, total]);
|
|
716
|
-
|
|
717
|
-
if (segments === null) return null;
|
|
718
|
-
if (segments.length === 0) return null;
|
|
719
|
-
if (parsed === null && providedSegments === undefined) return null;
|
|
720
|
-
|
|
721
|
-
const summaryColorClass =
|
|
722
|
-
inputTokens !== null && showDivergenceWarning ? "text-amber-400" : "text-muted-foreground";
|
|
723
|
-
const displayedSegments = viewMode === "role" ? roleSegments : segments;
|
|
724
|
-
const displayedTotal =
|
|
725
|
-
total > 0 ? total : displayedSegments.reduce((sum, item) => sum + item.size, 0);
|
|
726
|
-
const activateSegment = viewMode === "segment" ? onSegmentActivate : undefined;
|
|
727
|
-
const segmentCountLabel = `${String(segments.length)} segment${segments.length === 1 ? "" : "s"}`;
|
|
728
|
-
const providerInputLabel =
|
|
729
|
-
inputTokens === null ? "Provider input unknown" : `Provider input ${formatTokens(inputTokens)}`;
|
|
730
|
-
const contextLimitLabel =
|
|
731
|
-
intelligence === null ? "Limit unknown" : `Limit ${formatContextLimit(intelligence)}`;
|
|
732
|
-
const usageLabel =
|
|
733
|
-
intelligence === null
|
|
734
|
-
? "Usage unknown"
|
|
735
|
-
: `${formatUsagePercent(intelligence.usagePercent)} used`;
|
|
736
|
-
|
|
737
|
-
return (
|
|
738
|
-
<TooltipProvider delayDuration={150}>
|
|
739
|
-
<div className="px-4 py-3 space-y-3" data-testid="anatomy-root">
|
|
740
|
-
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
741
|
-
<div className="min-w-0">
|
|
742
|
-
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
|
743
|
-
<div className="text-sm font-semibold text-foreground">Request Context</div>
|
|
744
|
-
{intelligence !== null && <ContextHealthBadge health={intelligence.health} />}
|
|
745
|
-
</div>
|
|
746
|
-
<div className={cn("mt-0.5 text-xs font-mono tabular-nums", summaryColorClass)}>
|
|
747
|
-
Estimated ~{formatTokens(total)} tokens | {providerInputLabel} | {contextLimitLabel} |{" "}
|
|
748
|
-
{usageLabel} | {segmentCountLabel}
|
|
749
|
-
</div>
|
|
750
|
-
</div>
|
|
751
|
-
|
|
752
|
-
<div
|
|
753
|
-
className="inline-flex shrink-0 rounded border border-border bg-muted/20 p-0.5"
|
|
754
|
-
role="group"
|
|
755
|
-
aria-label="Context breakdown mode"
|
|
756
|
-
>
|
|
757
|
-
{VIEW_MODE_OPTIONS.map((option) => (
|
|
758
|
-
<button
|
|
759
|
-
key={option.value}
|
|
760
|
-
type="button"
|
|
761
|
-
aria-pressed={viewMode === option.value}
|
|
762
|
-
onClick={() => setViewMode(option.value)}
|
|
763
|
-
className={cn(
|
|
764
|
-
"h-6 rounded-sm px-2 text-[11px] font-medium transition-colors",
|
|
765
|
-
viewMode === option.value
|
|
766
|
-
? "bg-background text-foreground shadow-sm"
|
|
767
|
-
: "text-muted-foreground hover:text-foreground",
|
|
768
|
-
)}
|
|
769
|
-
>
|
|
770
|
-
{option.label}
|
|
771
|
-
</button>
|
|
772
|
-
))}
|
|
773
|
-
</div>
|
|
774
|
-
</div>
|
|
775
|
-
|
|
776
|
-
{showDivergenceWarning && (
|
|
777
|
-
<div className="inline-flex items-center gap-1.5 text-xs text-amber-400">
|
|
778
|
-
<Tooltip>
|
|
779
|
-
<TooltipTrigger asChild>
|
|
780
|
-
<button
|
|
781
|
-
type="button"
|
|
782
|
-
className="inline-flex items-center hover:text-amber-300"
|
|
783
|
-
aria-label="Token estimate differs from provider input"
|
|
784
|
-
>
|
|
785
|
-
<Info className="size-3.5" />
|
|
786
|
-
</button>
|
|
787
|
-
</TooltipTrigger>
|
|
788
|
-
<TooltipContent className="max-w-xs text-xs">
|
|
789
|
-
The bar uses a local token estimate. Provider input tokens remain the source of
|
|
790
|
-
truth for billing and context-window usage.
|
|
791
|
-
</TooltipContent>
|
|
792
|
-
</Tooltip>
|
|
793
|
-
Estimate differs from provider-reported input.
|
|
794
|
-
</div>
|
|
795
|
-
)}
|
|
796
|
-
|
|
797
|
-
{intelligence !== null && (
|
|
798
|
-
<ContextIntelligenceStrip
|
|
799
|
-
intelligence={intelligence}
|
|
800
|
-
segments={segments}
|
|
801
|
-
onSegmentActivate={onSegmentActivate}
|
|
802
|
-
/>
|
|
803
|
-
)}
|
|
804
|
-
|
|
805
|
-
{intelligence !== null && (
|
|
806
|
-
<DuplicateContentPanel
|
|
807
|
-
groups={intelligence.duplicateGroups}
|
|
808
|
-
segments={segments}
|
|
809
|
-
onSegmentActivate={onSegmentActivate}
|
|
810
|
-
/>
|
|
811
|
-
)}
|
|
812
|
-
|
|
813
|
-
<SegmentBar
|
|
814
|
-
segments={displayedSegments}
|
|
815
|
-
totalTokens={displayedTotal}
|
|
816
|
-
showLabels={viewMode === "segment"}
|
|
817
|
-
onActivate={activateSegment}
|
|
818
|
-
/>
|
|
819
|
-
|
|
820
|
-
<div className="flex flex-wrap items-center gap-x-3 gap-y-1.5 text-[11px] text-muted-foreground">
|
|
821
|
-
{roleSegments.map((segment) => {
|
|
822
|
-
const percent = total > 0 ? (segment.size / total) * 100 : 0;
|
|
823
|
-
return (
|
|
824
|
-
<div key={segment.role} className="inline-flex items-center gap-1.5">
|
|
825
|
-
<span
|
|
826
|
-
aria-hidden="true"
|
|
827
|
-
className={cn("size-2.5 rounded-[2px]", ROLE_COLOR_CLASSES[segment.role])}
|
|
828
|
-
/>
|
|
829
|
-
<span>{segment.label}</span>
|
|
830
|
-
<span className="font-mono text-muted-foreground/70">{formatPercent(percent)}</span>
|
|
831
|
-
</div>
|
|
832
|
-
);
|
|
833
|
-
})}
|
|
834
|
-
</div>
|
|
835
|
-
|
|
836
|
-
<div className="space-y-1.5">
|
|
837
|
-
<div className="text-xs font-medium text-foreground">Top Contributors</div>
|
|
838
|
-
<div className="grid gap-1">
|
|
839
|
-
{topSegments.map((segment, index) => {
|
|
840
|
-
const percent = total > 0 ? (segment.size / total) * 100 : 0;
|
|
841
|
-
const activate = onSegmentActivate;
|
|
842
|
-
const content = (
|
|
843
|
-
<>
|
|
844
|
-
<span className="w-5 shrink-0 text-right font-mono text-muted-foreground/70">
|
|
845
|
-
{String(index + 1)}
|
|
846
|
-
</span>
|
|
847
|
-
<span
|
|
848
|
-
aria-hidden="true"
|
|
849
|
-
className={cn(
|
|
850
|
-
"size-2.5 shrink-0 rounded-[2px]",
|
|
851
|
-
ROLE_COLOR_CLASSES[segment.role],
|
|
852
|
-
)}
|
|
853
|
-
/>
|
|
854
|
-
<span className="min-w-0 flex-1 truncate text-left">{segment.label}</span>
|
|
855
|
-
<span className="shrink-0 font-mono text-muted-foreground">
|
|
856
|
-
{formatPercent(percent)} | ~{formatTokens(segment.size)}
|
|
857
|
-
</span>
|
|
858
|
-
</>
|
|
859
|
-
);
|
|
860
|
-
if (activate !== undefined) {
|
|
861
|
-
return (
|
|
862
|
-
<button
|
|
863
|
-
key={`${segment.path}-${index}`}
|
|
864
|
-
type="button"
|
|
865
|
-
onClick={() => activate(segment)}
|
|
866
|
-
className="flex h-7 items-center gap-2 rounded px-1.5 text-xs text-muted-foreground hover:bg-muted/40 hover:text-foreground"
|
|
867
|
-
title="Jump to this request block"
|
|
868
|
-
>
|
|
869
|
-
{content}
|
|
870
|
-
</button>
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
return (
|
|
874
|
-
<div
|
|
875
|
-
key={`${segment.path}-${index}`}
|
|
876
|
-
className="flex h-7 items-center gap-2 rounded px-1.5 text-xs text-muted-foreground"
|
|
877
|
-
>
|
|
878
|
-
{content}
|
|
879
|
-
</div>
|
|
880
|
-
);
|
|
881
|
-
})}
|
|
882
|
-
</div>
|
|
883
|
-
</div>
|
|
884
|
-
</div>
|
|
885
|
-
</TooltipProvider>
|
|
886
|
-
);
|
|
887
|
-
}
|