Skip to content

Commit 87b81f6

Browse files
committed
add /init command
1 parent 94b375b commit 87b81f6

23 files changed

+439
-211
lines changed

cli/src/chat.tsx

Lines changed: 31 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,7 @@ import os from 'os'
22
import path from 'path'
33

44
import { useRenderer, useTerminalDimensions } from '@opentui/react'
5-
import React, {
6-
type ReactNode,
7-
useCallback,
8-
useEffect,
9-
useMemo,
10-
useRef,
11-
useState,
12-
} from 'react'
5+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
136
import stringWidth from 'string-width'
147
import { useShallow } from 'zustand/react/shallow'
158

@@ -37,6 +30,7 @@ import { useChatScrollbox } from './hooks/use-scroll-management'
3730
import { useSendMessage } from './hooks/use-send-message'
3831
import { useSuggestionEngine } from './hooks/use-suggestion-engine'
3932
import { useSystemThemeDetector } from './hooks/use-system-theme-detector'
33+
import { handleSlashCommands } from './slash-commands/handlers'
4034
import { useChatStore } from './state/chat-store'
4135
import { flushAnalytics } from './utils/analytics'
4236
import { getUserCredentials } from './utils/auth'
@@ -50,87 +44,18 @@ import {
5044
import { logger } from './utils/logger'
5145
import { buildMessageTree } from './utils/message-tree-utils'
5246
import { openFileAtPath } from './utils/open-file'
53-
import { handleSlashCommands } from './utils/slash-commands'
54-
import {
55-
chatThemes,
56-
createMarkdownPalette,
57-
type ChatTheme,
58-
} from './utils/theme-system'
47+
import { chatThemes, createMarkdownPalette } from './utils/theme-system'
5948
import { formatValidationError } from './utils/validation-error-formatting'
6049

6150
import type { SendMessageTimerEvent } from './hooks/use-send-message'
51+
import type { ChatMessage, ContentBlock } from './types/chat'
52+
import type { SendMessageFn } from './types/contracts/send-message'
6253
import type { User } from './utils/auth'
63-
import type { AgentMode } from './utils/constants'
64-
import type { ToolName } from '@codebuff/sdk'
6554
import type { ScrollBoxRenderable } from '@opentui/core'
6655

67-
type ChatVariant = 'ai' | 'user' | 'agent' | 'error'
68-
6956
const MAX_VIRTUALIZED_TOP_LEVEL = 60
7057
const VIRTUAL_OVERSCAN = 12
7158

72-
// LOGO_BLOCK moved to component to be reactive to terminal width changes
73-
74-
type AgentMessage = {
75-
agentName: string
76-
agentType: string
77-
responseCount: number
78-
subAgentCount?: number
79-
}
80-
81-
export type ContentBlock =
82-
| {
83-
type: 'text'
84-
content: string
85-
marginTop?: number
86-
marginBottom?: number
87-
}
88-
| {
89-
type: 'html'
90-
marginTop?: number
91-
marginBottom?: number
92-
render: (context: { textColor: string; theme: ChatTheme }) => ReactNode
93-
}
94-
| {
95-
type: 'tool'
96-
toolCallId: string
97-
toolName: ToolName
98-
input: any
99-
output?: string
100-
agentId?: string
101-
}
102-
| {
103-
type: 'agent'
104-
agentId: string
105-
agentName: string
106-
agentType: string
107-
content: string
108-
status: 'running' | 'complete'
109-
blocks?: ContentBlock[]
110-
initialPrompt?: string
111-
}
112-
| {
113-
type: 'agent-list'
114-
id: string
115-
agents: Array<{ id: string; displayName: string }>
116-
agentsDir: string
117-
}
118-
119-
export type ChatMessage = {
120-
id: string
121-
variant: ChatVariant
122-
content: string
123-
blocks?: ContentBlock[]
124-
timestamp: string
125-
parentId?: string
126-
agent?: AgentMessage
127-
isCompletion?: boolean
128-
credits?: number
129-
completionTime?: string
130-
isComplete?: boolean
131-
metadata?: Record<string, any>
132-
}
133-
13459
export const App = ({
13560
initialPrompt,
13661
agentId,
@@ -788,10 +713,7 @@ export const App = ({
788713
setInputValue,
789714
)
790715

791-
const sendMessageRef =
792-
useRef<
793-
(content: string, params: { agentMode: AgentMode }) => Promise<void>
794-
>()
716+
const sendMessageRef = useRef<SendMessageFn>()
795717

796718
const {
797719
queuedMessages,
@@ -806,7 +728,7 @@ export const App = ({
806728
setIsStreaming,
807729
} = useMessageQueue(
808730
(content: string) =>
809-
sendMessageRef.current?.(content, { agentMode }) ?? Promise.resolve(),
731+
sendMessageRef.current?.({ content, agentMode }) ?? Promise.resolve(),
810732
isChainInProgressRef,
811733
activeAgentStreamsRef,
812734
)
@@ -870,7 +792,7 @@ export const App = ({
870792
const timeout = setTimeout(() => {
871793
logger.info({ prompt: initialPrompt }, 'Auto-submitting initial prompt')
872794
if (sendMessageRef.current) {
873-
sendMessageRef.current(initialPrompt, { agentMode })
795+
sendMessageRef.current({ content: initialPrompt, agentMode })
874796
}
875797
}, 100)
876798

@@ -888,28 +810,29 @@ export const App = ({
888810
)
889811

890812
const handleSubmit = useCallback(
891-
() => handleSlashCommands({
892-
abortControllerRef,
893-
agentMode,
894-
inputRef,
895-
inputValue,
896-
isChainInProgressRef,
897-
isStreaming,
898-
logoutMutation,
899-
streamMessageIdRef,
900-
addToQueue,
901-
handleCtrlC,
902-
saveToHistory,
903-
scrollToLatest,
904-
sendMessage,
905-
setCanProcessQueue,
906-
setInputFocused,
907-
setInputValue,
908-
setIsAuthenticated,
909-
setMessages,
910-
setUser,
911-
stopStreaming,
912-
}),
813+
() =>
814+
handleSlashCommands({
815+
abortControllerRef,
816+
agentMode,
817+
inputRef,
818+
inputValue,
819+
isChainInProgressRef,
820+
isStreaming,
821+
logoutMutation,
822+
streamMessageIdRef,
823+
addToQueue,
824+
handleCtrlC,
825+
saveToHistory,
826+
scrollToLatest,
827+
sendMessage,
828+
setCanProcessQueue,
829+
setInputFocused,
830+
setInputValue,
831+
setIsAuthenticated,
832+
setMessages,
833+
setUser,
834+
stopStreaming,
835+
}),
913836
[
914837
inputValue,
915838
isStreaming,

cli/src/components/agent-mode-toggle.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { AgentMode } from '../utils/constants'
2-
import type { ChatTheme } from '../utils/theme-system'
1+
import type { ChatTheme } from '../types/theme-system'
2+
import type { AgentMode } from '../utils/constants'
33

44
export const AgentModeToggle = ({
55
mode,
66
theme,
77
onToggle,
88
}: {
9-
mode: AgentMode,
9+
mode: AgentMode
1010
theme: ChatTheme
1111
onToggle: () => void
1212
}) => {

cli/src/components/branch-item.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { TextAttributes, type BorderCharacters } from '@opentui/core'
22
import React, { type ReactNode } from 'react'
33

4+
import type { ChatTheme } from '../types/theme-system'
5+
46
const borderCharsWithoutVertical: BorderCharacters = {
57
topLeft: '┌',
68
topRight: '┐',
@@ -15,8 +17,6 @@ const borderCharsWithoutVertical: BorderCharacters = {
1517
cross: ' ',
1618
}
1719

18-
import type { ChatTheme } from '../utils/theme-system'
19-
2020
interface BranchItemProps {
2121
name: string
2222
content: ReactNode
@@ -170,16 +170,17 @@ export const BranchItem = ({
170170
</text>
171171
</box>
172172
<box style={{ flexShrink: 1, marginBottom: 0 }}>
173-
{isCollapsed && (isStreaming ? streamingPreview : finishedPreview) && (
174-
<text
175-
key={isStreaming ? 'streaming-preview' : 'finished-preview'}
176-
wrap
177-
fg={isStreaming ? theme.agentText : theme.agentResponseCount}
178-
attributes={TextAttributes.ITALIC}
179-
>
180-
{isStreaming ? streamingPreview : finishedPreview}
181-
</text>
182-
)}
173+
{isCollapsed &&
174+
(isStreaming ? streamingPreview : finishedPreview) && (
175+
<text
176+
key={isStreaming ? 'streaming-preview' : 'finished-preview'}
177+
wrap
178+
fg={isStreaming ? theme.agentText : theme.agentResponseCount}
179+
attributes={TextAttributes.ITALIC}
180+
>
181+
{isStreaming ? streamingPreview : finishedPreview}
182+
</text>
183+
)}
183184
{!isCollapsed && (
184185
<box style={{ flexDirection: 'column', gap: 1 }}>
185186
{content && (

cli/src/components/login-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ import { useLoginStore } from '../state/login-store'
3131
import { copyTextToClipboard } from '../utils/clipboard'
3232
import { logger } from '../utils/logger'
3333

34+
import type { ChatTheme } from '../types/theme-system'
3435
import type { User } from '../utils/auth'
35-
import type { ChatTheme } from '../utils/theme-system'
3636

3737
interface LoginModalProps {
3838
onLoginSuccess: (user: User) => void

cli/src/components/message-block.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1+
import { pluralize } from '@codebuff/common/util/string'
12
import { TextAttributes } from '@opentui/core'
23
import React, { type ReactNode } from 'react'
34

4-
import { pluralize } from '@codebuff/common/util/string'
5-
65
import { BranchItem } from './branch-item'
76
import { getToolDisplayInfo } from '../utils/codebuff-client'
87
import {
@@ -13,8 +12,8 @@ import {
1312
} from '../utils/markdown-renderer'
1413

1514
import type { ElapsedTimeTracker } from '../hooks/use-elapsed-time'
16-
import type { ContentBlock } from '../chat'
17-
import type { ChatTheme } from '../utils/theme-system'
15+
import type { ContentBlock } from '../types/chat'
16+
import type { ChatTheme } from '../types/theme-system'
1817

1918
const trimTrailingNewlines = (value: string): string =>
2019
value.replace(/[\r\n]+$/g, '')
@@ -462,11 +461,7 @@ export const MessageBlock = ({
462461
markdownOptions,
463462
)
464463
return (
465-
<text
466-
key={`message-content-${messageId}`}
467-
wrap
468-
style={{ fg: textColor }}
469-
>
464+
<text key={`message-content-${messageId}`} wrap style={{ fg: textColor }}>
470465
{displayContent}
471466
</text>
472467
)

cli/src/components/separator.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22

3-
import type { ChatTheme } from '../utils/theme-system'
3+
import type { ChatTheme } from '../types/theme-system'
44

55
interface SeparatorProps {
66
theme: ChatTheme

cli/src/components/status-indicator.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import React, { useEffect, useState } from 'react'
22

33
import { ShimmerText } from './shimmer-text'
44
import { getCodebuffClient } from '../utils/codebuff-client'
5-
import { logger } from '../utils/logger'
65

76
import type { ElapsedTimeTracker } from '../hooks/use-elapsed-time'
8-
import type { ChatTheme } from '../utils/theme-system'
7+
import type { ChatTheme } from '../types/theme-system'
98

109
const useConnectionStatus = () => {
1110
const [isConnected, setIsConnected] = useState(true)

cli/src/components/suggestion-menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22

3-
import type { ChatTheme } from '../utils/theme-system'
3+
import type { ChatTheme } from '../types/theme-system'
44

55
export interface SuggestionItem {
66
id: string

cli/src/hooks/use-message-renderer.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
import { getDescendantIds, getAncestorIds } from '../utils/message-tree-utils'
1212

1313
import type { ElapsedTimeTracker } from './use-elapsed-time'
14-
import type { ChatMessage } from '../chat'
15-
import type { ChatTheme } from '../utils/theme-system'
14+
import type { ChatMessage } from '../types/chat'
15+
import type { ChatTheme } from '../types/theme-system'
1616

1717
interface UseMessageRendererProps {
1818
messages: ChatMessage[]
@@ -275,8 +275,16 @@ export const useMessageRenderer = (
275275
const isUser = message.variant === 'user'
276276
const isError = message.variant === 'error'
277277
const lineColor = isError ? 'red' : isAi ? theme.aiLine : theme.userLine
278-
const textColor = isError ? theme.messageAiText : isAi ? theme.messageAiText : theme.messageUserText
279-
const timestampColor = isError ? 'red' : isAi ? theme.timestampAi : theme.timestampUser
278+
const textColor = isError
279+
? theme.messageAiText
280+
: isAi
281+
? theme.messageAiText
282+
: theme.messageUserText
283+
const timestampColor = isError
284+
? 'red'
285+
: isAi
286+
? theme.timestampAi
287+
: theme.timestampUser
280288
const estimatedMessageWidth = availableWidth
281289
const codeBlockWidth = Math.max(10, estimatedMessageWidth - 8)
282290
const paletteForMessage: MarkdownPalette = {

0 commit comments

Comments
 (0)