修复agents历史记录跳转

This commit is contained in:
2025-10-11 13:00:52 +08:00
parent 6aefec3312
commit e7775ce8ed
8 changed files with 53 additions and 40 deletions

View File

@@ -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
*/

View File

@@ -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) {

View File

@@ -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 } }));
}
};

View File

@@ -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) {

View File

@@ -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 = () => {

View File

@@ -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 => {

View File

@@ -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 => {

View File

@@ -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: {