修复agents历史记录跳转
This commit is contained in:
22
src/App.tsx
22
src/App.tsx
@@ -90,7 +90,6 @@ function AppContent() {
|
||||
const backendLocale = frontendLang === 'zh' ? 'zh-CN' : 'en-US';
|
||||
// Sync to backend
|
||||
await api.setLanguage(backendLocale);
|
||||
console.log('Backend language initialized to:', backendLocale);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize backend language:', error);
|
||||
}
|
||||
@@ -187,6 +186,27 @@ function AppContent() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Listen for a global request to switch to the tabbed interface
|
||||
useEffect(() => {
|
||||
const handleSwitchToTabs = (event: Event) => {
|
||||
// Accept optional tabId in event detail
|
||||
const detail = (event as CustomEvent).detail || {};
|
||||
const tabId = detail.tabId as string | undefined;
|
||||
setView('tabs');
|
||||
if (tabId) {
|
||||
// Wait a tick for TabManager to mount, then switch to the tab
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(new CustomEvent('switch-to-tab', { detail: { tabId } }));
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('switch-to-tabs', handleSwitchToTabs as EventListener);
|
||||
return () => {
|
||||
window.removeEventListener('switch-to-tabs', handleSwitchToTabs as EventListener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Loads all projects from the ~/.claude/projects directory
|
||||
*/
|
||||
|
||||
@@ -58,6 +58,7 @@ export function AgentRunOutputViewer({
|
||||
tabId,
|
||||
className
|
||||
}: AgentRunOutputViewerProps) {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { updateTabTitle, updateTabStatus } = useTabState();
|
||||
const [run, setRun] = useState<AgentRunWithMetrics | null>(null);
|
||||
@@ -143,25 +144,17 @@ export function AgentRunOutputViewer({
|
||||
const loadOutput = async (skipCache = false) => {
|
||||
if (!run?.id) return;
|
||||
|
||||
console.log('[AgentRunOutputViewer] Loading output for run:', {
|
||||
runId: run.id,
|
||||
status: run.status,
|
||||
sessionId: run.session_id,
|
||||
skipCache
|
||||
});
|
||||
|
||||
try {
|
||||
// Check cache first if not skipping cache
|
||||
if (!skipCache) {
|
||||
const cached = getCachedOutput(run.id);
|
||||
if (cached) {
|
||||
console.log('[AgentRunOutputViewer] Found cached output');
|
||||
const cachedJsonlLines = cached.output.split('\n').filter(line => line.trim());
|
||||
setRawJsonlOutput(cachedJsonlLines);
|
||||
setMessages(cached.messages);
|
||||
// If cache is recent (less than 5 seconds old) and session isn't running, use cache only
|
||||
if (Date.now() - cached.lastUpdated < 5000 && run.status !== 'running') {
|
||||
console.log('[AgentRunOutputViewer] Using recent cache, skipping refresh');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -171,10 +164,8 @@ export function AgentRunOutputViewer({
|
||||
|
||||
// If we have a session_id, try to load from JSONL file first
|
||||
if (run.session_id && run.session_id !== '') {
|
||||
console.log('[AgentRunOutputViewer] Attempting to load from JSONL with session_id:', run.session_id);
|
||||
try {
|
||||
const history = await api.loadAgentSessionHistory(run.session_id);
|
||||
console.log('[AgentRunOutputViewer] Successfully loaded JSONL history:', history.length, 'messages');
|
||||
|
||||
// Convert history to messages format
|
||||
const loadedMessages: ClaudeStreamMessage[] = history.map(entry => ({
|
||||
@@ -195,29 +186,22 @@ export function AgentRunOutputViewer({
|
||||
|
||||
// Set up live event listeners for running sessions
|
||||
if (run.status === 'running') {
|
||||
console.log('[AgentRunOutputViewer] Setting up live listeners for running session');
|
||||
setupLiveEventListeners();
|
||||
|
||||
try {
|
||||
await api.streamSessionOutput(run.id);
|
||||
} catch (streamError) {
|
||||
console.warn('[AgentRunOutputViewer] Failed to start streaming, will poll instead:', streamError);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (err) {
|
||||
console.warn('[AgentRunOutputViewer] Failed to load from JSONL:', err);
|
||||
console.warn('[AgentRunOutputViewer] Falling back to regular output method');
|
||||
// Fallback path will be used if JSONL loading fails
|
||||
}
|
||||
} else {
|
||||
console.log('[AgentRunOutputViewer] No session_id available, using fallback method');
|
||||
}
|
||||
|
||||
// Fallback to the original method if JSONL loading fails or no session_id
|
||||
console.log('[AgentRunOutputViewer] Using getSessionOutput fallback');
|
||||
const rawOutput = await api.getSessionOutput(run.id);
|
||||
console.log('[AgentRunOutputViewer] Received raw output:', rawOutput.length, 'characters');
|
||||
|
||||
// Parse JSONL output into messages
|
||||
const jsonlLines = rawOutput.split('\n').filter(line => line.trim());
|
||||
@@ -232,7 +216,6 @@ export function AgentRunOutputViewer({
|
||||
console.error("[AgentRunOutputViewer] Failed to parse message:", err, line);
|
||||
}
|
||||
}
|
||||
console.log('[AgentRunOutputViewer] Parsed', parsedMessages.length, 'messages from output');
|
||||
setMessages(parsedMessages);
|
||||
|
||||
// Update cache
|
||||
@@ -245,13 +228,11 @@ export function AgentRunOutputViewer({
|
||||
|
||||
// Set up live event listeners for running sessions
|
||||
if (run.status === 'running') {
|
||||
console.log('[AgentRunOutputViewer] Setting up live listeners for running session (fallback)');
|
||||
setupLiveEventListeners();
|
||||
|
||||
try {
|
||||
await api.streamSessionOutput(run.id);
|
||||
} catch (streamError) {
|
||||
console.warn('[AgentRunOutputViewer] Failed to start streaming (fallback), will poll instead:', streamError);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -285,7 +266,6 @@ export function AgentRunOutputViewer({
|
||||
try {
|
||||
// Skip messages during initial load phase
|
||||
if (isInitialLoadRef.current) {
|
||||
console.log('[AgentRunOutputViewer] Skipping message during initial load');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -403,7 +383,6 @@ export function AgentRunOutputViewer({
|
||||
const success = await api.killAgentSession(run.id);
|
||||
|
||||
if (success) {
|
||||
console.log(`[AgentRunOutputViewer] Successfully stopped agent session ${run.id}`);
|
||||
setToast({ message: 'Agent execution stopped', type: 'success' });
|
||||
|
||||
// Clean up listeners
|
||||
@@ -431,7 +410,6 @@ export function AgentRunOutputViewer({
|
||||
// Refresh the output to get updated status
|
||||
await loadOutput(true);
|
||||
} else {
|
||||
console.warn(`[AgentRunOutputViewer] Failed to stop agent session ${run.id} - it may have already finished`);
|
||||
setToast({ message: 'Failed to stop agent - it may have already finished', type: 'error' });
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -828,4 +806,4 @@ export function AgentRunOutputViewer({
|
||||
);
|
||||
}
|
||||
|
||||
export default AgentRunOutputViewer;
|
||||
export default AgentRunOutputViewer;
|
||||
|
||||
@@ -98,12 +98,19 @@ export const AgentRunsList: React.FC<AgentRunsListProps> = ({
|
||||
};
|
||||
|
||||
const handleRunClick = (run: AgentRunWithMetrics) => {
|
||||
|
||||
if (!run.id) {
|
||||
console.error('[AgentRunsList] Cannot open run - no ID available:', run);
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's a callback, use it (for full-page navigation)
|
||||
if (onRunClick) {
|
||||
onRunClick(run);
|
||||
} else if (run.id) {
|
||||
} else {
|
||||
// Otherwise, open in new tab
|
||||
createAgentTab(run.id.toString(), run.agent_name);
|
||||
const tabId = createAgentTab(run.id.toString(), run.agent_name);
|
||||
window.dispatchEvent(new CustomEvent('switch-to-tabs', { detail: { tabId } }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,4 +222,4 @@ export const AgentRunsList: React.FC<AgentRunsListProps> = ({
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -226,9 +226,12 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
);
|
||||
|
||||
case 'agent':
|
||||
|
||||
if (!tab.agentRunId) {
|
||||
console.error('[TabContent] No agentRunId in tab:', tab);
|
||||
return <div className="p-4">{t('messages.noAgentRunIdSpecified')}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AgentRunOutputViewer
|
||||
agentRunId={tab.agentRunId}
|
||||
@@ -317,6 +320,10 @@ export const TabContent: React.FC = () => {
|
||||
const { tabs, activeTabId, createChatTab, findTabBySessionId, createClaudeFileTab, createAgentExecutionTab, createCreateAgentTab, createImportAgentTab, closeTab, updateTab } = useTabState();
|
||||
const [hasInitialized, setHasInitialized] = React.useState(false);
|
||||
|
||||
// Debug: Monitor activeTabId changes
|
||||
useEffect(() => {
|
||||
}, [activeTabId, tabs]);
|
||||
|
||||
// Auto redirect to home when no tabs (but not on initial load)
|
||||
useEffect(() => {
|
||||
if (hasInitialized && tabs.length === 0) {
|
||||
|
||||
@@ -93,7 +93,6 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
|
||||
// Debug: Log initial URL on mount
|
||||
useEffect(() => {
|
||||
console.log('[WebviewPreview] Component mounted with initialUrl:', initialUrl, 'isMaximized:', isMaximized);
|
||||
}, []);
|
||||
|
||||
// Focus management for full screen mode
|
||||
@@ -127,7 +126,6 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
const urlObj = new URL(url.startsWith('http') ? url : `https://${url}`);
|
||||
const finalUrl = urlObj.href;
|
||||
|
||||
console.log('[WebviewPreview] Navigating to:', finalUrl);
|
||||
setCurrentUrl(finalUrl);
|
||||
setInputUrl(finalUrl);
|
||||
setHasError(false);
|
||||
@@ -152,12 +150,10 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
|
||||
const handleGoBack = () => {
|
||||
// In real implementation, this would call webview.goBack()
|
||||
console.log("Go back");
|
||||
};
|
||||
|
||||
const handleGoForward = () => {
|
||||
// In real implementation, this would call webview.goForward()
|
||||
console.log("Go forward");
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
@@ -354,4 +350,4 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const WebviewPreview = React.memo(WebviewPreviewComponent);
|
||||
export const WebviewPreview = React.memo(WebviewPreviewComponent);
|
||||
|
||||
@@ -42,6 +42,7 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const [tabs, setTabs] = useState<Tab[]>([]);
|
||||
const [activeTabId, setActiveTabId] = useState<string | null>(null);
|
||||
|
||||
|
||||
// Always start with a fresh CC Projects tab
|
||||
useEffect(() => {
|
||||
// Create default projects tab
|
||||
@@ -90,6 +91,7 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
|
||||
setTabs(prevTabs => [...prevTabs, newTab]);
|
||||
setActiveTabId(newTab.id);
|
||||
|
||||
return newTab.id;
|
||||
}, [tabs.length, t]);
|
||||
|
||||
@@ -127,10 +129,11 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
}, []);
|
||||
|
||||
const setActiveTab = useCallback((id: string) => {
|
||||
if (tabs.find(tab => tab.id === id)) {
|
||||
const tabExists = tabs.find(tab => tab.id === id);
|
||||
if (tabExists) {
|
||||
setActiveTabId(id);
|
||||
}
|
||||
}, [tabs]);
|
||||
}, [tabs, activeTabId]);
|
||||
|
||||
const reorderTabs = useCallback((startIndex: number, endIndex: number) => {
|
||||
setTabs(prevTabs => {
|
||||
|
||||
@@ -82,7 +82,7 @@ export const useTabState = (): UseTabStateReturn => {
|
||||
return existingTab.id;
|
||||
}
|
||||
|
||||
return addTab({
|
||||
const newTabId = addTab({
|
||||
type: 'agent',
|
||||
title: agentName,
|
||||
agentRunId,
|
||||
@@ -90,6 +90,7 @@ export const useTabState = (): UseTabStateReturn => {
|
||||
hasUnsavedChanges: false,
|
||||
icon: 'bot'
|
||||
});
|
||||
return newTabId;
|
||||
}, [addTab, tabs, setActiveTab]);
|
||||
|
||||
const createProjectsTab = useCallback((): string | null => {
|
||||
@@ -346,4 +347,4 @@ export const useTabState = (): UseTabStateReturn => {
|
||||
findTabByType,
|
||||
canAddTab
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -16,14 +16,15 @@ const languageDetectorOptions = {
|
||||
checkWhitelist: true,
|
||||
};
|
||||
|
||||
const i18nDebug = (typeof import.meta !== 'undefined' && (import.meta as any).env?.VITE_I18N_DEBUG === 'true');
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
// 回退语言
|
||||
fallbackLng: 'en',
|
||||
// 调试模式(开发环境)
|
||||
debug: process.env.NODE_ENV === 'development',
|
||||
debug: i18nDebug,
|
||||
|
||||
// 语言资源
|
||||
resources: {
|
||||
|
||||
Reference in New Issue
Block a user