Skip to content

Commit 3814204

Browse files
committed
from claude, with slash-commands file fixed
1 parent 70a56e6 commit 3814204

File tree

9 files changed

+623
-2
lines changed

9 files changed

+623
-2
lines changed

bun.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/chat.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import {
1212
import { useShallow } from 'zustand/react/shallow'
1313

1414
import { getAdsEnabled } from './commands/ads'
15+
import { handleChatSelection } from './commands/chats'
1516
import { routeUserPrompt, addBashMessageToHistory } from './commands/router'
1617
import { AdBanner } from './components/ad-banner'
1718
import { ChatInputBar } from './components/chat-input-bar'
19+
import { ChatPickerScreen } from './components/chat-picker-screen'
1820
import { BottomStatusLine } from './components/bottom-status-line'
1921
import { areCreditsRestored } from './components/out-of-credits-banner'
2022
import { LoadPreviousButton } from './components/load-previous-button'
@@ -215,6 +217,8 @@ export const Chat = ({
215217
[streamingAgentsKey],
216218
)
217219
const pendingBashMessages = useChatStore((state) => state.pendingBashMessages)
220+
const showChatPicker = useChatStore((state) => state.showChatPicker)
221+
const setShowChatPicker = useChatStore((state) => state.setShowChatPicker)
218222

219223
// Refs for tracking state across renders
220224
const activeAgentStreamsRef = useRef<number>(0)
@@ -1401,6 +1405,39 @@ export const Chat = ({
14011405
}
14021406
}, [])
14031407

1408+
// Show chat picker if active
1409+
if (showChatPicker) {
1410+
return (
1411+
<ChatPickerScreen
1412+
onSelectChat={async (chatId) => {
1413+
await handleChatSelection(chatId, {
1414+
abortControllerRef,
1415+
agentMode,
1416+
inputRef,
1417+
inputValue,
1418+
isChainInProgressRef,
1419+
isStreaming,
1420+
logoutMutation,
1421+
streamMessageIdRef,
1422+
addToQueue,
1423+
clearMessages,
1424+
saveToHistory,
1425+
scrollToLatest,
1426+
sendMessage,
1427+
setCanProcessQueue,
1428+
setInputFocused,
1429+
setInputValue,
1430+
setIsAuthenticated,
1431+
setMessages,
1432+
setUser,
1433+
stopStreaming,
1434+
})
1435+
}}
1436+
onClose={() => setShowChatPicker(false)}
1437+
/>
1438+
)
1439+
}
1440+
14041441
return (
14051442
<box
14061443
onMouseMove={handleMouseActivity}

cli/src/commands/chats.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import * as fs from 'fs'
2+
import path from 'path'
3+
4+
import { getAllChatDirs, getProjectDataDir, setCurrentChatId, startNewChat } from '../project-files'
5+
import { useChatStore } from '../state/chat-store'
6+
import {
7+
generateChatTitle,
8+
formatRelativeDate,
9+
getLastUserPrompt,
10+
} from '../utils/chat-title-generator'
11+
import { loadMostRecentChatState, saveChatState } from '../utils/run-state-storage'
12+
13+
import type { ChatMessage } from '../types/chat'
14+
import type { RouterParams } from './command-registry'
15+
16+
export type ChatMetadata = {
17+
chatId: string
18+
title: string
19+
lastPrompt: string
20+
timestamp: string
21+
messageCount: number
22+
formattedDate: string
23+
}
24+
25+
/**
26+
* Load metadata for a specific chat
27+
* Returns null if the chat doesn't exist or can't be loaded
28+
*/
29+
export function loadChatMetadata(chatId: string): ChatMetadata | null {
30+
try {
31+
const chatsDir = path.join(getProjectDataDir(), 'chats', chatId)
32+
33+
// Try both file names (chat-messages.json is newer format)
34+
const messagesPath = fs.existsSync(path.join(chatsDir, 'chat-messages.json'))
35+
? path.join(chatsDir, 'chat-messages.json')
36+
: path.join(chatsDir, 'messages.json')
37+
38+
if (!fs.existsSync(messagesPath)) {
39+
return null
40+
}
41+
42+
const messagesContent = fs.readFileSync(messagesPath, 'utf8')
43+
const messages = JSON.parse(messagesContent) as ChatMessage[]
44+
45+
if (messages.length === 0) {
46+
return null
47+
}
48+
49+
// Extract metadata
50+
const title = generateChatTitle(messages)
51+
const lastPrompt = getLastUserPrompt(messages)
52+
// chatId is an ISO timestamp, convert it to ISO format by replacing hyphens with colons in the time part
53+
// const timestamp = chatId.replace(/T(\d{2})-(\d{2})-(\d{2})/, 'T$1:$2:$3')
54+
const timestamp = messages[0]?.timestamp || new Date().toISOString()
55+
const messageCount = messages.length
56+
const formattedDate = formatRelativeDate(timestamp)
57+
58+
return {
59+
chatId,
60+
title,
61+
lastPrompt,
62+
timestamp,
63+
messageCount,
64+
formattedDate,
65+
}
66+
} catch (error) {
67+
// Silently skip corrupted chats
68+
return null
69+
}
70+
}
71+
72+
/**
73+
* Get metadata for all chats, sorted by most recent first
74+
*/
75+
export function getAllChatsMetadata(): ChatMetadata[] {
76+
const chatDirs = getAllChatDirs()
77+
78+
const metadata = chatDirs
79+
.map((dir) => loadChatMetadata(dir.chatId))
80+
.filter((meta): meta is ChatMetadata => meta !== null)
81+
82+
// Sort by timestamp descending (most recent first)
83+
metadata.sort((a, b) => {
84+
const aTime = new Date(a.timestamp).getTime()
85+
const bTime = new Date(b.timestamp).getTime()
86+
return bTime - aTime
87+
})
88+
89+
return metadata
90+
}
91+
92+
/**
93+
* Command handler for /chats
94+
* Opens the chat picker screen
95+
*/
96+
export async function handleChatsCommand(params: RouterParams): Promise<void> {
97+
// Save current command to history
98+
params.saveToHistory(params.inputValue.trim())
99+
100+
// Clear input
101+
params.setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
102+
103+
// Set chat picker mode/state
104+
useChatStore.getState().setShowChatPicker(true)
105+
}
106+
107+
/**
108+
* Handle selection of a chat from the chat picker
109+
*/
110+
export async function handleChatSelection(
111+
chatId: string,
112+
params: RouterParams,
113+
): Promise<void> {
114+
try {
115+
// Save current chat state before switching
116+
const currentMessages = useChatStore.getState().messages
117+
const currentRunState = useChatStore.getState().runState
118+
if (currentMessages.length > 0 && currentRunState) {
119+
saveChatState(currentRunState, currentMessages)
120+
}
121+
122+
// Start a new chat session (generate new chat ID)
123+
startNewChat()
124+
125+
// Load selected chat
126+
const savedState = loadMostRecentChatState(chatId)
127+
128+
if (!savedState) {
129+
throw new Error('Failed to load chat')
130+
}
131+
132+
// Update state with loaded chat
133+
params.setMessages(() => savedState.messages)
134+
useChatStore.getState().setRunState(savedState.runState)
135+
136+
// Update current chat ID to the loaded chat
137+
setCurrentChatId(chatId)
138+
139+
// Close chat picker
140+
useChatStore.getState().setShowChatPicker(false)
141+
} catch (error) {
142+
console.error('Error resuming chat:', error)
143+
144+
// Show error to user
145+
params.setMessages((prev) => [
146+
...prev,
147+
{
148+
id: Date.now().toString(),
149+
variant: 'error',
150+
content: 'Failed to resume chat. Please try again.',
151+
timestamp: new Date().toISOString(),
152+
},
153+
])
154+
155+
// Close chat picker
156+
useChatStore.getState().setShowChatPicker(false)
157+
}
158+
}

cli/src/commands/command-registry.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import open from 'open'
22

33
import { handleAdsEnable, handleAdsDisable } from './ads'
4+
import { handleChatsCommand } from './chats'
45
import { handleHelpCommand } from './help'
56
import { handleImageCommand } from './image'
67
import { handleInitializationFlowLocally } from './init'
@@ -187,6 +188,13 @@ export const COMMAND_REGISTRY: CommandDefinition[] = [
187188
clearInput(params)
188189
},
189190
}),
191+
defineCommand({
192+
name: 'chats',
193+
aliases: ['history'],
194+
handler: async (params) => {
195+
await handleChatsCommand(params)
196+
},
197+
}),
190198
defineCommandWithArgs({
191199
name: 'feedback',
192200
aliases: ['bug', 'report'],

0 commit comments

Comments
 (0)