ethagent 3.3.4 → 4.0.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/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +35 -0
- package/LICENSE +1 -1
- package/README.md +64 -104
- package/commands/ethagent.md +40 -0
- package/package.json +16 -16
- package/src/app/keybindings/KeybindingProvider.tsx +1 -6
- package/src/app/keybindings/types.ts +1 -6
- package/src/cli/ResetConfirmView.tsx +54 -53
- package/src/cli/demo.ts +86 -0
- package/src/cli/hookIo.ts +45 -0
- package/src/cli/main.tsx +94 -123
- package/src/cli/memoryGuard.ts +49 -0
- package/src/cli/reset.ts +28 -70
- package/src/cli/sessionStart.ts +33 -0
- package/src/cli/status.ts +46 -0
- package/src/cli/sync.ts +167 -0
- package/src/cli/syncAdapters/claude-code.ts +86 -0
- package/src/cli/syncAdapters/codex.ts +66 -0
- package/src/cli/syncAdapters/index.ts +45 -0
- package/src/cli/syncAdapters/managedBlock.ts +175 -0
- package/src/cli/syncAdapters/shared.ts +63 -0
- package/src/identity/continuity/envelopeParse.ts +20 -1
- package/src/identity/continuity/publicSkills.ts +3 -1
- package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
- package/src/identity/continuity/skills/scaffold.ts +5 -2
- package/src/identity/continuity/snapshots.ts +12 -5
- package/src/identity/continuity/storage/defaults.ts +20 -19
- package/src/identity/continuity/storage/status.ts +1 -1
- package/src/identity/ens/ensLookup/constants.ts +1 -1
- package/src/identity/manager/IdentityManager.tsx +33 -0
- package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
- package/src/identity/{hub → manager}/Routes.tsx +48 -34
- package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
- package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
- package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
- package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
- package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
- package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
- package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
- package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
- package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
- package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
- package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
- package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
- package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
- package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
- package/src/identity/{hub → manager}/create/effects.ts +19 -10
- package/src/identity/manager/create/importScan.ts +122 -0
- package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
- package/src/identity/{hub → manager}/custody/actions.ts +1 -15
- package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
- package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
- package/src/identity/{hub → manager}/custody/types.ts +1 -2
- package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
- package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
- package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
- package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
- package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
- package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
- package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
- package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
- package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
- package/src/identity/{hub → manager}/profile/effects.ts +1 -3
- package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
- package/src/identity/{hub → manager}/profile/state.ts +1 -1
- package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
- package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
- package/src/identity/{hub → manager}/restore/apply.ts +1 -1
- package/src/identity/{hub → manager}/restore/auth.ts +1 -1
- package/src/identity/{hub → manager}/restore/discover.ts +1 -1
- package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
- package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
- package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
- package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
- package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
- package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
- package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
- package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
- package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
- package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
- package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
- package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
- package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
- package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
- package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
- package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
- package/src/identity/manager/shared/effects/types.ts +30 -0
- package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
- package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
- package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
- package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
- package/src/identity/{hub → manager}/shared/utils.ts +6 -10
- package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
- package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
- package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
- package/src/identity/{hub → manager}/types.ts +5 -6
- package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
- package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
- package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
- package/src/identity/registry/erc8004/discovery.ts +3 -17
- package/src/identity/registry/erc8004/utils.ts +1 -1
- package/src/identity/storage/ipfs.ts +21 -1
- package/src/identity/wallet/browserWallet/html.ts +10 -2
- package/src/identity/wallet/browserWallet/http.ts +18 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
- package/src/identity/wallet/browserWallet/requests.ts +10 -28
- package/src/identity/wallet/browserWallet/session.ts +26 -33
- package/src/identity/wallet/browserWallet/validation.ts +14 -0
- package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
- package/src/identity/wallet/page/boot.ts +43 -0
- package/src/identity/wallet/page/config.ts +59 -0
- package/src/identity/wallet/page/constants.ts +12 -0
- package/src/identity/wallet/page/copy.ts +47 -68
- package/src/identity/wallet/page/css.ts +638 -0
- package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
- package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
- package/src/identity/wallet/page/markup.ts +44 -34
- package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
- package/src/identity/wallet/page/resize.ts +95 -0
- package/src/identity/wallet/page/state.ts +135 -8
- package/src/identity/wallet/page/timeline.ts +161 -0
- package/src/identity/wallet/page/view.ts +22 -302
- package/src/storage/config.ts +30 -80
- package/src/storage/reset.ts +31 -0
- package/src/storage/secrets.ts +1 -16
- package/src/ui/Select.tsx +27 -5
- package/src/ui/Spinner.tsx +16 -15
- package/src/ui/Surface.tsx +21 -17
- package/src/ui/TextArea.tsx +173 -0
- package/src/ui/TextInput.tsx +31 -133
- package/src/ui/theme.ts +22 -13
- package/src/utils/clipboard.ts +0 -140
- package/src/app/FirstRun.tsx +0 -577
- package/src/app/FirstRunTimeline.tsx +0 -51
- package/src/app/firstRunConfig.ts +0 -26
- package/src/app/hooks/useCancelRequest.ts +0 -22
- package/src/app/hooks/useDoublePress.ts +0 -46
- package/src/app/hooks/useExitOnCtrlC.ts +0 -36
- package/src/auth/openaiOAuth/credentials.ts +0 -47
- package/src/auth/openaiOAuth/crypto.ts +0 -23
- package/src/auth/openaiOAuth/index.ts +0 -238
- package/src/auth/openaiOAuth/landingPage.ts +0 -116
- package/src/auth/openaiOAuth/listener.ts +0 -151
- package/src/auth/openaiOAuth/refresh.ts +0 -70
- package/src/auth/openaiOAuth/shared.ts +0 -115
- package/src/chat/ChatBottomPane.tsx +0 -296
- package/src/chat/ChatScreen.tsx +0 -1685
- package/src/chat/ConversationStack.tsx +0 -56
- package/src/chat/MessageList.tsx +0 -638
- package/src/chat/SessionStatus.tsx +0 -53
- package/src/chat/chatEnvironment.ts +0 -16
- package/src/chat/chatScreenUtils.ts +0 -194
- package/src/chat/chatSessionState.ts +0 -146
- package/src/chat/chatTurnContext.ts +0 -50
- package/src/chat/chatTurnOrchestrator.ts +0 -603
- package/src/chat/chatTurnRows.ts +0 -64
- package/src/chat/commands.ts +0 -494
- package/src/chat/continuityEditReview.ts +0 -42
- package/src/chat/display/DiffView.tsx +0 -193
- package/src/chat/display/SyntaxText.tsx +0 -192
- package/src/chat/display/toolCallDisplay.ts +0 -103
- package/src/chat/display/toolResultDisplay.ts +0 -19
- package/src/chat/input/ChatInput.tsx +0 -625
- package/src/chat/input/chatInputHelpers.ts +0 -62
- package/src/chat/input/chatInputState.ts +0 -247
- package/src/chat/input/chatPaste.ts +0 -49
- package/src/chat/input/imageRefs.ts +0 -30
- package/src/chat/input/inputRendering.tsx +0 -93
- package/src/chat/input/textCursor.ts +0 -212
- package/src/chat/messageMarkdown.ts +0 -220
- package/src/chat/messageRows.ts +0 -43
- package/src/chat/planImplementation.ts +0 -62
- package/src/chat/slashCommandHandlers.ts +0 -122
- package/src/chat/slashCommandViews.ts +0 -120
- package/src/chat/transcript/TranscriptView.tsx +0 -184
- package/src/chat/transcript/transcriptViewport.ts +0 -295
- package/src/chat/views/ContextLimitView.tsx +0 -95
- package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
- package/src/chat/views/CopyPicker.tsx +0 -50
- package/src/chat/views/PermissionPrompt.tsx +0 -156
- package/src/chat/views/PermissionsView.tsx +0 -165
- package/src/chat/views/PlanApprovalView.tsx +0 -91
- package/src/chat/views/ResumeView.tsx +0 -273
- package/src/chat/views/RewindView.tsx +0 -412
- package/src/cli/preview.tsx +0 -14
- package/src/cli/updateNotice.ts +0 -54
- package/src/identity/continuity/privateEdit/apply.ts +0 -170
- package/src/identity/continuity/privateEdit/diff.ts +0 -6
- package/src/identity/continuity/privateEdit/files.ts +0 -23
- package/src/identity/continuity/privateEdit/types.ts +0 -28
- package/src/identity/continuity/privateEdit.ts +0 -46
- package/src/identity/hub/IdentityHub.tsx +0 -14
- package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
- package/src/identity/hub/ens/effects.ts +0 -218
- package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
- package/src/identity/hub/shared/effects/types.ts +0 -53
- package/src/identity/hub/shared/reconciliation/index.ts +0 -14
- package/src/identity/wallet/page/grainient.ts +0 -278
- package/src/identity/wallet/page/html.ts +0 -28
- package/src/identity/wallet/page/styles/base.ts +0 -260
- package/src/identity/wallet/page/styles/components.ts +0 -262
- package/src/identity/wallet/page/styles/index.ts +0 -5
- package/src/identity/wallet/page/styles/responsive.ts +0 -247
- package/src/identity/wallet/page.tsx +0 -38
- package/src/mcp/approvals.ts +0 -113
- package/src/mcp/config.ts +0 -235
- package/src/mcp/manager.ts +0 -482
- package/src/mcp/managerHelpers.ts +0 -70
- package/src/mcp/names.ts +0 -19
- package/src/mcp/output.ts +0 -96
- package/src/models/ModelPicker.tsx +0 -1009
- package/src/models/catalog.ts +0 -327
- package/src/models/huggingface.ts +0 -712
- package/src/models/huggingfaceStorage.ts +0 -136
- package/src/models/llamacpp.ts +0 -848
- package/src/models/llamacppCommands.ts +0 -44
- package/src/models/llamacppConfig.ts +0 -34
- package/src/models/llamacppDiscovery.ts +0 -176
- package/src/models/llamacppOutput.ts +0 -65
- package/src/models/llamacppPreflight.ts +0 -158
- package/src/models/modelDisplay.ts +0 -180
- package/src/models/modelPickerCatalogFlow.ts +0 -56
- package/src/models/modelPickerCredentials.ts +0 -166
- package/src/models/modelPickerData.ts +0 -41
- package/src/models/modelPickerDisplay.tsx +0 -132
- package/src/models/modelPickerHfFlow.ts +0 -192
- package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
- package/src/models/modelPickerOptions.ts +0 -457
- package/src/models/modelPickerTypes.ts +0 -69
- package/src/models/modelPickerUninstallFlow.ts +0 -48
- package/src/models/modelPickerViewHelpers.ts +0 -174
- package/src/models/modelRecommendation.ts +0 -139
- package/src/models/providerDisplay.ts +0 -16
- package/src/models/runtimeDetection.ts +0 -81
- package/src/models/uncensoredCatalog.ts +0 -86
- package/src/providers/anthropic.ts +0 -290
- package/src/providers/contracts.ts +0 -71
- package/src/providers/errors.ts +0 -80
- package/src/providers/gemini.ts +0 -391
- package/src/providers/openai-chat.ts +0 -474
- package/src/providers/openai-responses-format.ts +0 -177
- package/src/providers/openai-responses.ts +0 -306
- package/src/providers/openaiChatWire.ts +0 -124
- package/src/providers/registry.ts +0 -120
- package/src/providers/retry.ts +0 -58
- package/src/providers/sse.ts +0 -93
- package/src/runtime/compaction.ts +0 -395
- package/src/runtime/cwd.ts +0 -43
- package/src/runtime/providerTurn.ts +0 -38
- package/src/runtime/sessionMode.ts +0 -55
- package/src/runtime/systemPrompt.ts +0 -213
- package/src/runtime/textToolParser.ts +0 -161
- package/src/runtime/toolClaimGuards.ts +0 -143
- package/src/runtime/toolExecution.ts +0 -304
- package/src/runtime/toolIntent.ts +0 -143
- package/src/runtime/turn.ts +0 -369
- package/src/runtime/turnNudges.ts +0 -223
- package/src/runtime/turnTypes.ts +0 -86
- package/src/storage/factoryReset.ts +0 -127
- package/src/storage/history.ts +0 -58
- package/src/storage/permissions.ts +0 -76
- package/src/storage/rewind.ts +0 -266
- package/src/storage/sessionExport.ts +0 -49
- package/src/storage/sessions.ts +0 -495
- package/src/tools/bashSafety.ts +0 -186
- package/src/tools/bashTool.ts +0 -140
- package/src/tools/changeDirectoryTool.ts +0 -213
- package/src/tools/contracts.ts +0 -192
- package/src/tools/deleteFileTool.ts +0 -116
- package/src/tools/editTool.ts +0 -165
- package/src/tools/editUtils.ts +0 -170
- package/src/tools/fileDiff.ts +0 -261
- package/src/tools/listDirectoryTool.ts +0 -55
- package/src/tools/listSkillFilesTool.ts +0 -77
- package/src/tools/listSkillsTool.ts +0 -68
- package/src/tools/mcpResourceTools.ts +0 -95
- package/src/tools/permissionRules.ts +0 -85
- package/src/tools/privateContinuityEditTool.ts +0 -187
- package/src/tools/privateContinuityReadTool.ts +0 -106
- package/src/tools/readSkillTool.ts +0 -107
- package/src/tools/readTool.ts +0 -85
- package/src/tools/registry.ts +0 -103
- package/src/tools/writeFileTool.ts +0 -167
- package/src/ui/BrandSplash.tsx +0 -133
- package/src/ui/terminalTitle.ts +0 -30
- package/src/utils/images.ts +0 -140
- package/src/utils/markdownSegments.ts +0 -51
- package/src/utils/messages.ts +0 -37
- package/src/utils/withRetry.ts +0 -324
- /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
- /package/src/identity/{hub → manager}/custody/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
- /package/src/identity/{hub → manager}/ens/state.ts +0 -0
- /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
- /package/src/identity/{hub → manager}/ens/types.ts +0 -0
- /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
- /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
- /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
- /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
- /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
- /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
- /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
- /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
- /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react'
|
|
2
|
-
import { Box, Text, useStdout } from 'ink'
|
|
3
|
-
import { useAppInput } from '../../app/input/AppInputProvider.js'
|
|
4
|
-
import { MessageList, type MessageRow } from '../MessageList.js'
|
|
5
|
-
import { theme } from '../../ui/theme.js'
|
|
6
|
-
import {
|
|
7
|
-
anchorForScrollTop,
|
|
8
|
-
buildLineOffsets,
|
|
9
|
-
clampLine,
|
|
10
|
-
estimateMessageRowHeight,
|
|
11
|
-
resolveScrollTopFromAnchor,
|
|
12
|
-
scrollTopForPageDown,
|
|
13
|
-
scrollTopForPageUp,
|
|
14
|
-
selectRowsForScrollTop,
|
|
15
|
-
type TranscriptViewportState,
|
|
16
|
-
} from './transcriptViewport.js'
|
|
17
|
-
|
|
18
|
-
type TranscriptViewProps = {
|
|
19
|
-
rows: MessageRow[]
|
|
20
|
-
active?: boolean
|
|
21
|
-
bottomVariant?: 'prompt' | 'overlay'
|
|
22
|
-
onVisibleReasoningIdsChange?: (ids: string[]) => void
|
|
23
|
-
onScrollabilityChange?: (canScroll: boolean) => void
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const PROMPT_RESERVED_LINES = 12
|
|
27
|
-
const OVERLAY_RESERVED_LINES = 16
|
|
28
|
-
const MIN_TRANSCRIPT_LINES = 6
|
|
29
|
-
const MAX_TRANSCRIPT_LINES = 240
|
|
30
|
-
|
|
31
|
-
export const TranscriptView: React.FC<TranscriptViewProps> = ({
|
|
32
|
-
rows,
|
|
33
|
-
active = true,
|
|
34
|
-
bottomVariant = 'prompt',
|
|
35
|
-
onVisibleReasoningIdsChange,
|
|
36
|
-
onScrollabilityChange,
|
|
37
|
-
}) => {
|
|
38
|
-
const { stdout } = useStdout()
|
|
39
|
-
const columns = stdout.columns ?? process.stdout.columns ?? 80
|
|
40
|
-
const terminalRows = stdout.rows ?? process.stdout.rows ?? 24
|
|
41
|
-
const reservedLines = bottomVariant === 'overlay' ? OVERLAY_RESERVED_LINES : PROMPT_RESERVED_LINES
|
|
42
|
-
const maxLines = Math.min(
|
|
43
|
-
MAX_TRANSCRIPT_LINES,
|
|
44
|
-
Math.max(MIN_TRANSCRIPT_LINES, terminalRows - reservedLines),
|
|
45
|
-
)
|
|
46
|
-
const [viewportState, setViewportState] = useState<TranscriptViewportState>({
|
|
47
|
-
scrollTopLine: 0,
|
|
48
|
-
followTail: true,
|
|
49
|
-
anchor: null,
|
|
50
|
-
})
|
|
51
|
-
const metrics = useMemo(() => {
|
|
52
|
-
const heights = rows.map(row => Math.max(1, estimateMessageRowHeight(row, columns)))
|
|
53
|
-
const offsets = buildLineOffsets(heights)
|
|
54
|
-
const totalLines = offsets[offsets.length - 1] ?? 0
|
|
55
|
-
return {
|
|
56
|
-
rowIds: rows.map(row => row.id),
|
|
57
|
-
offsets,
|
|
58
|
-
maxScrollTop: Math.max(0, totalLines - maxLines),
|
|
59
|
-
}
|
|
60
|
-
}, [columns, maxLines, rows])
|
|
61
|
-
const resolvedViewportState = useMemo(
|
|
62
|
-
() => resolveViewportState(viewportState, metrics.rowIds, metrics.offsets, metrics.maxScrollTop),
|
|
63
|
-
[metrics, viewportState],
|
|
64
|
-
)
|
|
65
|
-
const selection = useMemo(
|
|
66
|
-
() => selectRowsForScrollTop(
|
|
67
|
-
rows,
|
|
68
|
-
maxLines,
|
|
69
|
-
resolvedViewportState.scrollTopLine,
|
|
70
|
-
row => estimateMessageRowHeight(row, columns),
|
|
71
|
-
),
|
|
72
|
-
[columns, maxLines, resolvedViewportState, rows],
|
|
73
|
-
)
|
|
74
|
-
const visibleReasoningIds = useMemo(
|
|
75
|
-
() => selection.rows
|
|
76
|
-
.filter((slice): slice is { row: Extract<MessageRow, { role: 'thinking' }>; clipStart: number; clipEnd: number; rowHeight: number } =>
|
|
77
|
-
slice.row.role === 'thinking',
|
|
78
|
-
)
|
|
79
|
-
.map(slice => slice.row.id),
|
|
80
|
-
[selection.rows],
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
setViewportState(prev => sameViewportState(prev, resolvedViewportState) ? prev : resolvedViewportState)
|
|
85
|
-
}, [resolvedViewportState])
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
onVisibleReasoningIdsChange?.(visibleReasoningIds)
|
|
89
|
-
}, [onVisibleReasoningIdsChange, visibleReasoningIds])
|
|
90
|
-
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
onScrollabilityChange?.(metrics.maxScrollTop > 0)
|
|
93
|
-
}, [metrics.maxScrollTop, onScrollabilityChange])
|
|
94
|
-
|
|
95
|
-
useAppInput((_input, key) => {
|
|
96
|
-
if (key.pageUp) {
|
|
97
|
-
const target = scrollTopForPageUp(
|
|
98
|
-
resolvedViewportState.scrollTopLine,
|
|
99
|
-
metrics.maxScrollTop,
|
|
100
|
-
maxLines,
|
|
101
|
-
)
|
|
102
|
-
setViewportState(viewportForScrollTop(
|
|
103
|
-
target,
|
|
104
|
-
metrics.rowIds,
|
|
105
|
-
metrics.offsets,
|
|
106
|
-
metrics.maxScrollTop,
|
|
107
|
-
))
|
|
108
|
-
} else if (key.pageDown) {
|
|
109
|
-
const target = scrollTopForPageDown(
|
|
110
|
-
resolvedViewportState.scrollTopLine,
|
|
111
|
-
metrics.maxScrollTop,
|
|
112
|
-
maxLines,
|
|
113
|
-
)
|
|
114
|
-
setViewportState(viewportForScrollTop(
|
|
115
|
-
target,
|
|
116
|
-
metrics.rowIds,
|
|
117
|
-
metrics.offsets,
|
|
118
|
-
metrics.maxScrollTop,
|
|
119
|
-
))
|
|
120
|
-
}
|
|
121
|
-
}, { isActive: active })
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<Box flexDirection="column">
|
|
125
|
-
{selection.hiddenBefore > 0 ? (
|
|
126
|
-
<Text color={theme.dim}>
|
|
127
|
-
{` ${selection.hiddenBefore} earlier message${selection.hiddenBefore === 1 ? '' : 's'} above · `}
|
|
128
|
-
<Text color={theme.accentPeriwinkle}>pgup</Text>
|
|
129
|
-
{` to scroll · `}
|
|
130
|
-
<Text color={theme.accentPeriwinkle}>/export</Text>
|
|
131
|
-
{` saves the full transcript`}
|
|
132
|
-
</Text>
|
|
133
|
-
) : null}
|
|
134
|
-
<MessageList slices={selection.rows} />
|
|
135
|
-
{selection.hiddenAfter > 0 ? (
|
|
136
|
-
<Text color={theme.dim}>
|
|
137
|
-
{` ${selection.hiddenAfter} later message${selection.hiddenAfter === 1 ? '' : 's'} below · `}
|
|
138
|
-
<Text color={theme.accentPeriwinkle}>pgdn</Text>
|
|
139
|
-
{` to return`}
|
|
140
|
-
</Text>
|
|
141
|
-
) : null}
|
|
142
|
-
</Box>
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function resolveViewportState(
|
|
147
|
-
state: TranscriptViewportState,
|
|
148
|
-
rowIds: string[],
|
|
149
|
-
offsets: number[],
|
|
150
|
-
maxScrollTop: number,
|
|
151
|
-
): TranscriptViewportState {
|
|
152
|
-
if (rowIds.length === 0) {
|
|
153
|
-
return { scrollTopLine: 0, followTail: true, anchor: null }
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const scrollTopLine = state.followTail
|
|
157
|
-
? maxScrollTop
|
|
158
|
-
: resolveScrollTopFromAnchor(rowIds, offsets, state.anchor, maxScrollTop)
|
|
159
|
-
?? clampLine(state.scrollTopLine, maxScrollTop)
|
|
160
|
-
|
|
161
|
-
return viewportForScrollTop(scrollTopLine, rowIds, offsets, maxScrollTop)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function viewportForScrollTop(
|
|
165
|
-
scrollTopLine: number,
|
|
166
|
-
rowIds: string[],
|
|
167
|
-
offsets: number[],
|
|
168
|
-
maxScrollTop: number,
|
|
169
|
-
): TranscriptViewportState {
|
|
170
|
-
const clamped = clampLine(scrollTopLine, maxScrollTop)
|
|
171
|
-
const followTail = clamped >= maxScrollTop
|
|
172
|
-
return {
|
|
173
|
-
scrollTopLine: clamped,
|
|
174
|
-
followTail,
|
|
175
|
-
anchor: followTail ? null : anchorForScrollTop(rowIds, offsets, clamped),
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function sameViewportState(left: TranscriptViewportState, right: TranscriptViewportState): boolean {
|
|
180
|
-
return left.scrollTopLine === right.scrollTopLine
|
|
181
|
-
&& left.followTail === right.followTail
|
|
182
|
-
&& left.anchor?.rowId === right.anchor?.rowId
|
|
183
|
-
&& left.anchor?.offset === right.anchor?.offset
|
|
184
|
-
}
|
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
import type { MessageRow } from '../MessageList.js'
|
|
2
|
-
import { flattenAssistantBody } from '../MessageList.js'
|
|
3
|
-
|
|
4
|
-
export type TranscriptAnchor = {
|
|
5
|
-
rowId: string
|
|
6
|
-
offset: number
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type TranscriptViewportState = {
|
|
10
|
-
scrollTopLine: number
|
|
11
|
-
followTail: boolean
|
|
12
|
-
anchor: TranscriptAnchor | null
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type RowSlice<T> = {
|
|
16
|
-
row: T
|
|
17
|
-
clipStart: number
|
|
18
|
-
clipEnd: number
|
|
19
|
-
rowHeight: number
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function buildLineOffsets(rowHeights: number[]): number[] {
|
|
23
|
-
const out = new Array<number>(rowHeights.length + 1).fill(0)
|
|
24
|
-
for (let i = 0; i < rowHeights.length; i += 1) {
|
|
25
|
-
out[i + 1] = out[i]! + (rowHeights[i] ?? 1)
|
|
26
|
-
}
|
|
27
|
-
return out
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function findRowIndexAtLine(offsets: number[], line: number): number {
|
|
31
|
-
let low = 0
|
|
32
|
-
let high = offsets.length - 1
|
|
33
|
-
while (low < high) {
|
|
34
|
-
const mid = Math.floor((low + high + 1) / 2)
|
|
35
|
-
if ((offsets[mid] ?? 0) <= line) low = mid
|
|
36
|
-
else high = mid - 1
|
|
37
|
-
}
|
|
38
|
-
return low
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function anchorForScrollTop(
|
|
42
|
-
rowIds: string[],
|
|
43
|
-
offsets: number[],
|
|
44
|
-
scrollTopLine: number,
|
|
45
|
-
): TranscriptAnchor | null {
|
|
46
|
-
if (rowIds.length === 0) return null
|
|
47
|
-
const rowIndex = Math.min(rowIds.length - 1, findRowIndexAtLine(offsets, scrollTopLine))
|
|
48
|
-
const rowId = rowIds[rowIndex]
|
|
49
|
-
if (!rowId) return null
|
|
50
|
-
return {
|
|
51
|
-
rowId,
|
|
52
|
-
offset: Math.max(0, scrollTopLine - (offsets[rowIndex] ?? 0)),
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function resolveScrollTopFromAnchor(
|
|
57
|
-
rowIds: string[],
|
|
58
|
-
offsets: number[],
|
|
59
|
-
anchor: TranscriptAnchor | null,
|
|
60
|
-
maxScrollTop: number,
|
|
61
|
-
): number | null {
|
|
62
|
-
if (!anchor) return null
|
|
63
|
-
const rowIndex = rowIds.indexOf(anchor.rowId)
|
|
64
|
-
if (rowIndex === -1) return null
|
|
65
|
-
return clampLine((offsets[rowIndex] ?? 0) + anchor.offset, maxScrollTop)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function clampLine(line: number, maxScrollTop: number): number {
|
|
69
|
-
return Math.max(0, Math.min(maxScrollTop, Math.floor(line)))
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export type TranscriptTailSelection<T> = {
|
|
73
|
-
rows: Array<RowSlice<T>>
|
|
74
|
-
hiddenCount: number
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export type TranscriptWindowSelection<T> = {
|
|
78
|
-
rows: Array<RowSlice<T>>
|
|
79
|
-
hiddenBefore: number
|
|
80
|
-
hiddenAfter: number
|
|
81
|
-
totalLines: number
|
|
82
|
-
maxScrollOffset: number
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function selectTailRowsForViewport<T>(
|
|
86
|
-
rows: T[],
|
|
87
|
-
maxLines: number,
|
|
88
|
-
estimateHeight: (row: T) => number,
|
|
89
|
-
): TranscriptTailSelection<T> {
|
|
90
|
-
if (rows.length === 0) return { rows: [], hiddenCount: 0 }
|
|
91
|
-
|
|
92
|
-
const budget = Math.max(1, Math.floor(maxLines))
|
|
93
|
-
let used = 0
|
|
94
|
-
let start = rows.length - 1
|
|
95
|
-
|
|
96
|
-
for (; start >= 0; start -= 1) {
|
|
97
|
-
const row = rows[start]
|
|
98
|
-
if (!row) break
|
|
99
|
-
const height = Math.max(1, estimateHeight(row))
|
|
100
|
-
if (used > 0 && used + height > budget) break
|
|
101
|
-
used += height
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const firstVisible = Math.max(0, start + 1)
|
|
105
|
-
const slice = rows.slice(firstVisible).map(row => {
|
|
106
|
-
const height = Math.max(1, estimateHeight(row))
|
|
107
|
-
return { row, clipStart: 0, clipEnd: height, rowHeight: height }
|
|
108
|
-
})
|
|
109
|
-
return {
|
|
110
|
-
rows: slice,
|
|
111
|
-
hiddenCount: firstVisible,
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function selectRowsForScrollOffset<T>(
|
|
116
|
-
rows: T[],
|
|
117
|
-
maxLines: number,
|
|
118
|
-
scrollOffsetFromTail: number,
|
|
119
|
-
estimateHeight: (row: T) => number,
|
|
120
|
-
): TranscriptWindowSelection<T> {
|
|
121
|
-
if (rows.length === 0) {
|
|
122
|
-
return { rows: [], hiddenBefore: 0, hiddenAfter: 0, totalLines: 0, maxScrollOffset: 0 }
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const budget = Math.max(1, Math.floor(maxLines))
|
|
126
|
-
const heights = rows.map(row => Math.max(1, estimateHeight(row)))
|
|
127
|
-
const offsets = buildLineOffsets(heights)
|
|
128
|
-
const totalLines = offsets[offsets.length - 1] ?? 0
|
|
129
|
-
const maxScrollOffset = Math.max(0, totalLines - budget)
|
|
130
|
-
const scrollOffset = clampLine(scrollOffsetFromTail, maxScrollOffset)
|
|
131
|
-
const startLine = Math.max(0, totalLines - budget - scrollOffset)
|
|
132
|
-
|
|
133
|
-
return selectRowsForLineWindow(rows, heights, offsets, budget, startLine, totalLines, maxScrollOffset)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function selectRowsForScrollTop<T>(
|
|
137
|
-
rows: T[],
|
|
138
|
-
maxLines: number,
|
|
139
|
-
scrollTopLine: number,
|
|
140
|
-
estimateHeight: (row: T) => number,
|
|
141
|
-
): TranscriptWindowSelection<T> {
|
|
142
|
-
if (rows.length === 0) {
|
|
143
|
-
return { rows: [], hiddenBefore: 0, hiddenAfter: 0, totalLines: 0, maxScrollOffset: 0 }
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const budget = Math.max(1, Math.floor(maxLines))
|
|
147
|
-
const heights = rows.map(row => Math.max(1, estimateHeight(row)))
|
|
148
|
-
const offsets = buildLineOffsets(heights)
|
|
149
|
-
const totalLines = offsets[offsets.length - 1] ?? 0
|
|
150
|
-
const maxScrollOffset = Math.max(0, totalLines - budget)
|
|
151
|
-
const startLine = clampLine(scrollTopLine, maxScrollOffset)
|
|
152
|
-
|
|
153
|
-
return selectRowsForLineWindow(rows, heights, offsets, budget, startLine, totalLines, maxScrollOffset)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function scrollTopForPageUp(
|
|
157
|
-
scrollTopLine: number,
|
|
158
|
-
maxScrollTop: number,
|
|
159
|
-
viewportLines: number,
|
|
160
|
-
): number {
|
|
161
|
-
return clampLine(scrollTopLine - pageScrollDistance(viewportLines), maxScrollTop)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function scrollTopForPageDown(
|
|
165
|
-
scrollTopLine: number,
|
|
166
|
-
maxScrollTop: number,
|
|
167
|
-
viewportLines: number,
|
|
168
|
-
): number {
|
|
169
|
-
return clampLine(scrollTopLine + pageScrollDistance(viewportLines), maxScrollTop)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function pageScrollDistance(viewportLines: number): number {
|
|
173
|
-
const viewport = Math.max(1, Math.floor(viewportLines))
|
|
174
|
-
return Math.max(1, viewport - 2)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function selectRowsForLineWindow<T>(
|
|
178
|
-
rows: T[],
|
|
179
|
-
heights: number[],
|
|
180
|
-
offsets: number[],
|
|
181
|
-
budget: number,
|
|
182
|
-
startLine: number,
|
|
183
|
-
totalLines: number,
|
|
184
|
-
maxScrollOffset: number,
|
|
185
|
-
): TranscriptWindowSelection<T> {
|
|
186
|
-
const endLine = Math.min(totalLines, startLine + budget)
|
|
187
|
-
|
|
188
|
-
const startIndex = Math.min(rows.length - 1, findRowIndexAtLine(offsets, startLine))
|
|
189
|
-
const lastVisibleLine = Math.max(startLine, endLine - 1)
|
|
190
|
-
const endIndex = endLine >= totalLines
|
|
191
|
-
? rows.length
|
|
192
|
-
: Math.min(rows.length, findRowIndexAtLine(offsets, lastVisibleLine) + 1)
|
|
193
|
-
|
|
194
|
-
const slices: Array<RowSlice<T>> = []
|
|
195
|
-
for (let i = startIndex; i < endIndex; i += 1) {
|
|
196
|
-
const row = rows[i]
|
|
197
|
-
if (!row) continue
|
|
198
|
-
const rowTop = offsets[i] ?? 0
|
|
199
|
-
const height = heights[i] ?? 1
|
|
200
|
-
const clipStart = Math.max(0, startLine - rowTop)
|
|
201
|
-
const clipEnd = Math.min(height, endLine - rowTop)
|
|
202
|
-
if (clipEnd <= clipStart) continue
|
|
203
|
-
slices.push({ row, clipStart, clipEnd, rowHeight: height })
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
rows: slices,
|
|
208
|
-
hiddenBefore: startIndex,
|
|
209
|
-
hiddenAfter: rows.length - endIndex,
|
|
210
|
-
totalLines,
|
|
211
|
-
maxScrollOffset,
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export function estimateMessageRowHeight(row: MessageRow, columns = 80): number {
|
|
216
|
-
const contentWidth = Math.max(20, columns - 8)
|
|
217
|
-
switch (row.role) {
|
|
218
|
-
case 'user':
|
|
219
|
-
return userRowLineCount(row.content, contentWidth)
|
|
220
|
-
case 'assistant':
|
|
221
|
-
return assistantRowLineCount(row.content, row.liveTail ?? '', contentWidth, Boolean(row.streaming))
|
|
222
|
-
case 'thinking':
|
|
223
|
-
return thinkingRowLineCount(row, contentWidth)
|
|
224
|
-
case 'tool_call':
|
|
225
|
-
return 1
|
|
226
|
-
case 'note':
|
|
227
|
-
return 1 + wrappedLineCount(row.content, contentWidth)
|
|
228
|
-
case 'progress':
|
|
229
|
-
return 4
|
|
230
|
-
case 'splash':
|
|
231
|
-
return 28
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export function userRowLineCount(content: string, contentWidth: number): number {
|
|
236
|
-
const lines = splitLines(content)
|
|
237
|
-
return 1 + lines.reduce((sum, line) => sum + Math.max(1, Math.ceil(line.length / contentWidth)), 0)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export function assistantRowLineCount(content: string, liveTail: string, _contentWidth: number, streaming = false): number {
|
|
241
|
-
const fullText = liveTail ? content + liveTail : content
|
|
242
|
-
return 1 + flattenAssistantBody(fullText, streaming).length
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
export function thinkingRowLineCount(
|
|
246
|
-
row: Extract<MessageRow, { role: 'thinking' }>,
|
|
247
|
-
_contentWidth: number,
|
|
248
|
-
): number {
|
|
249
|
-
const omitted = thinkingDisplayOmittedChars(row)
|
|
250
|
-
const overhead = 1 + (omitted > 0 ? 1 : 0) + 1
|
|
251
|
-
if (!row.expanded) return overhead
|
|
252
|
-
const body = thinkingDisplayBody(row)
|
|
253
|
-
const lines = splitLines(body)
|
|
254
|
-
return overhead + lines.length
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export function thinkingDisplayBody(row: Extract<MessageRow, { role: 'thinking' }>): string {
|
|
258
|
-
const text = row.liveTail ? row.content + row.liveTail : row.content
|
|
259
|
-
return clipReasoningForDisplayText(text)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export function thinkingDisplayOmittedChars(row: Extract<MessageRow, { role: 'thinking' }>): number {
|
|
263
|
-
const text = row.liveTail ? row.content + row.liveTail : row.content
|
|
264
|
-
return clipReasoningForDisplayOmitted(text)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const MAX_RENDERED_REASONING_CHARS = 10_000
|
|
268
|
-
|
|
269
|
-
function clipReasoningForDisplayText(text: string): string {
|
|
270
|
-
if (text.length <= MAX_RENDERED_REASONING_CHARS) return text
|
|
271
|
-
const rawStart = Math.max(0, text.length - MAX_RENDERED_REASONING_CHARS)
|
|
272
|
-
const newline = text.indexOf('\n', rawStart)
|
|
273
|
-
const start = newline >= 0 && newline - rawStart <= 240 ? newline + 1 : rawStart
|
|
274
|
-
return text.slice(start)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function clipReasoningForDisplayOmitted(text: string): number {
|
|
278
|
-
if (text.length <= MAX_RENDERED_REASONING_CHARS) return 0
|
|
279
|
-
const rawStart = Math.max(0, text.length - MAX_RENDERED_REASONING_CHARS)
|
|
280
|
-
const newline = text.indexOf('\n', rawStart)
|
|
281
|
-
return newline >= 0 && newline - rawStart <= 240 ? newline + 1 : rawStart
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export function splitLines(text: string): string[] {
|
|
285
|
-
if (!text) return ['']
|
|
286
|
-
const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
|
287
|
-
return normalized.split('\n')
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function wrappedLineCount(text: string, width: number): number {
|
|
291
|
-
if (!text) return 1
|
|
292
|
-
return text
|
|
293
|
-
.split(/\r?\n/)
|
|
294
|
-
.reduce((total, line) => total + Math.max(1, Math.ceil(line.length / width)), 0)
|
|
295
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import type { ContextUsage } from '../../runtime/compaction.js'
|
|
4
|
-
import { useAppInput } from '../../app/input/AppInputProvider.js'
|
|
5
|
-
import { theme } from '../../ui/theme.js'
|
|
6
|
-
|
|
7
|
-
export type ContextLimitAction = 'compact' | 'switchModel' | 'send' | 'cancel'
|
|
8
|
-
|
|
9
|
-
type ContextLimitViewProps = {
|
|
10
|
-
usage: ContextUsage
|
|
11
|
-
promptPreview: string
|
|
12
|
-
onSelect: (action: ContextLimitAction) => void | Promise<void>
|
|
13
|
-
onCancel: () => void
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const CONTEXT_LIMIT_OPTIONS: Array<{ action: ContextLimitAction; label: string; detail: string }> = [
|
|
17
|
-
{
|
|
18
|
-
action: 'compact',
|
|
19
|
-
label: 'Summarize and move to new conversation',
|
|
20
|
-
detail: 'Summarize this transcript into a new conversation, then send the pending message.',
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
action: 'switchModel',
|
|
24
|
-
label: 'Switch to larger-context model',
|
|
25
|
-
detail: 'Pick a model that can fit this conversation, then send the pending message.',
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
action: 'send',
|
|
29
|
-
label: 'Ignore warning and send',
|
|
30
|
-
detail: 'May hit rate/context limits faster or degrade local/cloud model behavior.',
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
action: 'cancel',
|
|
34
|
-
label: 'Cancel',
|
|
35
|
-
detail: 'Return to the prompt without sending the pending message.',
|
|
36
|
-
},
|
|
37
|
-
]
|
|
38
|
-
|
|
39
|
-
export const ContextLimitView: React.FC<ContextLimitViewProps> = ({
|
|
40
|
-
usage,
|
|
41
|
-
promptPreview,
|
|
42
|
-
onSelect,
|
|
43
|
-
onCancel,
|
|
44
|
-
}) => {
|
|
45
|
-
const [selected, setSelected] = useState(0)
|
|
46
|
-
|
|
47
|
-
useAppInput((_input, key) => {
|
|
48
|
-
if (key.escape) {
|
|
49
|
-
onCancel()
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
if (key.upArrow) {
|
|
53
|
-
setSelected(i => Math.max(0, i - 1))
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
if (key.downArrow) {
|
|
57
|
-
setSelected(i => Math.min(CONTEXT_LIMIT_OPTIONS.length - 1, i + 1))
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
if (key.return) {
|
|
61
|
-
const picked = CONTEXT_LIMIT_OPTIONS[selected]
|
|
62
|
-
if (picked) void onSelect(picked.action)
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<Box flexDirection="column" borderStyle="round" borderColor={theme.accentPeriwinkle} paddingX={1}>
|
|
68
|
-
<Text color={theme.accentPeriwinkle} bold>context limit</Text>
|
|
69
|
-
<Text color={theme.dim}>
|
|
70
|
-
{`Context ${usage.percent}% · ~${formatTokens(usage.usedTokens)} / ${formatTokens(usage.windowTokens)} tokens (${usage.source}).`}
|
|
71
|
-
</Text>
|
|
72
|
-
{usage.percent >= 100 ? (
|
|
73
|
-
<Text color={theme.accentPeriwinkle}>
|
|
74
|
-
This transcript is over the selected model's estimated window. You can still send, but summarizing first is safer.
|
|
75
|
-
</Text>
|
|
76
|
-
) : null}
|
|
77
|
-
<Text color={theme.textSubtle}>{`Pending: ${promptPreview || '(empty)'}`}</Text>
|
|
78
|
-
<Box flexDirection="column" marginTop={1}>
|
|
79
|
-
{CONTEXT_LIMIT_OPTIONS.map((option, index) => (
|
|
80
|
-
<Text key={option.action} color={index === selected ? theme.accentPeriwinkle : theme.text}>
|
|
81
|
-
{index === selected ? '> ' : ' '}
|
|
82
|
-
{option.label}
|
|
83
|
-
<Text color={theme.dim}>{` · ${option.detail}`}</Text>
|
|
84
|
-
</Text>
|
|
85
|
-
))}
|
|
86
|
-
</Box>
|
|
87
|
-
</Box>
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function formatTokens(count: number): string {
|
|
92
|
-
if (count < 1000) return String(count)
|
|
93
|
-
if (count < 10_000) return `${(count / 1000).toFixed(1)}k`
|
|
94
|
-
return `${Math.round(count / 1000)}k`
|
|
95
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import { Surface } from '../../ui/Surface.js'
|
|
4
|
-
import { Select } from '../../ui/Select.js'
|
|
5
|
-
import { theme } from '../../ui/theme.js'
|
|
6
|
-
import { DiffView } from '../display/DiffView.js'
|
|
7
|
-
|
|
8
|
-
export type ContinuityEditReviewState = {
|
|
9
|
-
file: 'SOUL.md' | 'MEMORY.md'
|
|
10
|
-
filePath: string
|
|
11
|
-
summary: string
|
|
12
|
-
diff?: string
|
|
13
|
-
editorOpened?: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type ContinuityEditReviewAction = 'open' | 'save-publish' | 'later'
|
|
17
|
-
|
|
18
|
-
export const ContinuityEditReviewView: React.FC<{
|
|
19
|
-
review: ContinuityEditReviewState
|
|
20
|
-
onSelect: (action: ContinuityEditReviewAction) => void | Promise<void>
|
|
21
|
-
onCancel: () => void
|
|
22
|
-
}> = ({ review, onSelect, onCancel }) => (
|
|
23
|
-
<Surface
|
|
24
|
-
title={`${review.file} Updated`}
|
|
25
|
-
footer="enter select · esc dismiss"
|
|
26
|
-
>
|
|
27
|
-
<Text color={theme.accentPeriwinkle}>{displayContinuityReviewText(review.summary)}</Text>
|
|
28
|
-
{review.diff ? (
|
|
29
|
-
<Box flexDirection="column" marginTop={1}>
|
|
30
|
-
<Text color={theme.accentPeriwinkle}>diff</Text>
|
|
31
|
-
<DiffView diff={review.diff} />
|
|
32
|
-
</Box>
|
|
33
|
-
) : null}
|
|
34
|
-
<Box marginTop={1}>
|
|
35
|
-
<Select<ContinuityEditReviewAction>
|
|
36
|
-
options={[
|
|
37
|
-
{ value: 'open', label: `Open ${review.file}`, hint: 'Review in editor' },
|
|
38
|
-
{ value: 'save-publish', label: 'Save Snapshot', hint: 'Wallet signature' },
|
|
39
|
-
{ value: 'later', label: 'Dismiss', hint: 'Save later from Identity Hub' },
|
|
40
|
-
]}
|
|
41
|
-
onSubmit={onSelect}
|
|
42
|
-
onCancel={onCancel}
|
|
43
|
-
/>
|
|
44
|
-
</Box>
|
|
45
|
-
</Surface>
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
function displayContinuityReviewText(value: string): string {
|
|
49
|
-
return value ? value[0]!.toUpperCase() + value.slice(1) : value
|
|
50
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Surface } from '../../ui/Surface.js'
|
|
3
|
-
import { Select } from '../../ui/Select.js'
|
|
4
|
-
import { parseSegments, type Segment } from '../../utils/markdownSegments.js'
|
|
5
|
-
import { copyToClipboard, type CopyResult } from '../../utils/clipboard.js'
|
|
6
|
-
|
|
7
|
-
type CopyPickerProps = {
|
|
8
|
-
turnText: string
|
|
9
|
-
turnLabel: string
|
|
10
|
-
onDone: (result: CopyResult, label: string) => void
|
|
11
|
-
onCancel: () => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type Choice = { index: number; segment: Segment | null; label: string }
|
|
15
|
-
|
|
16
|
-
export const CopyPicker: React.FC<CopyPickerProps> = ({ turnText, turnLabel, onDone, onCancel }) => {
|
|
17
|
-
const segments = parseSegments(turnText)
|
|
18
|
-
const choices: Choice[] = [
|
|
19
|
-
{ index: -1, segment: null, label: `all (${turnText.length} chars)` },
|
|
20
|
-
...segments.map((segment, i) => ({ index: i, segment, label: segment.preview })),
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
const options = choices.map(c => ({
|
|
24
|
-
value: c.index,
|
|
25
|
-
label: c.label,
|
|
26
|
-
hint: c.segment ? (c.segment.kind === 'code' ? 'code block' : undefined) : 'full reply',
|
|
27
|
-
}))
|
|
28
|
-
|
|
29
|
-
const handleSubmit = (index: number) => {
|
|
30
|
-
const chosen = choices.find(c => c.index === index)
|
|
31
|
-
const payload = chosen?.segment ? chosen.segment.content : turnText
|
|
32
|
-
const label = chosen?.label ?? 'copy'
|
|
33
|
-
void copyToClipboard(payload).then(result => onDone(result, label))
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<Surface
|
|
38
|
-
title={`Copy From ${turnLabel}`}
|
|
39
|
-
subtitle="Choose the full reply or an extracted segment."
|
|
40
|
-
footer="enter copies · esc closes"
|
|
41
|
-
>
|
|
42
|
-
<Select<number>
|
|
43
|
-
options={options}
|
|
44
|
-
initialIndex={0}
|
|
45
|
-
onSubmit={handleSubmit}
|
|
46
|
-
onCancel={onCancel}
|
|
47
|
-
/>
|
|
48
|
-
</Surface>
|
|
49
|
-
)
|
|
50
|
-
}
|