Skip to content

Commit 236d1b3

Browse files
committed
feat(tarko-agent-ui): add workspace environment mismatch warning
- Add useWorkspaceEnvironmentCheck hook to detect workspace mismatches - Add WorkspaceEnvironmentWarning component for visual warnings - Disable chat input when session workspace differs from server workspace - Update ChatPanel and ChatInput to handle workspace environment checks - Show preview mode message when environment mismatch detected
1 parent 1fef3d7 commit 236d1b3

File tree

4 files changed

+173
-6
lines changed

4 files changed

+173
-6
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { useEffect, useState } from 'react';
2+
import { useSession } from './useSession';
3+
import { apiService } from '../services/apiService';
4+
5+
/**
6+
* Hook to check if the current session's workspace differs from the server's current workspace
7+
* Returns warning state when there's a mismatch, indicating the session is in preview-only mode
8+
*/
9+
export function useWorkspaceEnvironmentCheck() {
10+
const { activeSessionId, sessions } = useSession();
11+
const [isWorkspaceMismatch, setIsWorkspaceMismatch] = useState(false);
12+
const [sessionWorkspace, setSessionWorkspace] = useState<string>('');
13+
const [serverWorkspace, setServerWorkspace] = useState<string>('');
14+
const [loading, setLoading] = useState(false);
15+
16+
useEffect(() => {
17+
if (!activeSessionId) {
18+
setIsWorkspaceMismatch(false);
19+
setSessionWorkspace('');
20+
setServerWorkspace('');
21+
return;
22+
}
23+
24+
const checkWorkspaceEnvironment = async () => {
25+
try {
26+
setLoading(true);
27+
28+
// Get current session's workspace
29+
const currentSession = sessions.find(session => session.id === activeSessionId);
30+
if (!currentSession) {
31+
setIsWorkspaceMismatch(false);
32+
return;
33+
}
34+
35+
const sessionWorkspacePath = currentSession.workspace;
36+
37+
// Get server's current workspace
38+
const serverWorkspaceInfo = await apiService.getWorkspaceInfo();
39+
const serverWorkspacePath = serverWorkspaceInfo.path;
40+
41+
setSessionWorkspace(sessionWorkspacePath);
42+
setServerWorkspace(serverWorkspacePath);
43+
44+
// Check if workspaces match
45+
const mismatch = sessionWorkspacePath !== serverWorkspacePath;
46+
setIsWorkspaceMismatch(mismatch);
47+
48+
if (mismatch) {
49+
console.warn('Workspace environment mismatch detected:', {
50+
sessionWorkspace: sessionWorkspacePath,
51+
serverWorkspace: serverWorkspacePath,
52+
});
53+
}
54+
} catch (error) {
55+
console.error('Failed to check workspace environment:', error);
56+
// On error, assume no mismatch to avoid false positives
57+
setIsWorkspaceMismatch(false);
58+
} finally {
59+
setLoading(false);
60+
}
61+
};
62+
63+
checkWorkspaceEnvironment();
64+
}, [activeSessionId, sessions]);
65+
66+
return {
67+
isWorkspaceMismatch,
68+
sessionWorkspace,
69+
serverWorkspace,
70+
loading,
71+
};
72+
}

multimodal/tarko/agent-ui/src/standalone/chat/ChatPanel.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useRef } from 'react';
22
import { useParams } from 'react-router-dom';
33
import { useSession } from '@/common/hooks/useSession';
4+
import { useWorkspaceEnvironmentCheck } from '@/common/hooks/useWorkspaceEnvironmentCheck';
45
import { MessageGroup } from './Message/components/MessageGroup';
56
import { ChatInput } from './MessageInput';
67
import { useAtomValue } from 'jotai';
@@ -12,13 +13,15 @@ import { ScrollToBottomButton } from './components/ScrollToBottomButton';
1213
import { EmptyState } from './components/EmptyState';
1314
import { OfflineBanner } from './components/OfflineBanner';
1415
import { SessionCreatingState } from './components/SessionCreatingState';
16+
import { WorkspaceEnvironmentWarning } from './components/WorkspaceEnvironmentWarning';
1517

1618
import './ChatPanel.css';
1719

1820
export const ChatPanel: React.FC = () => {
1921
const { sessionId: urlSessionId } = useParams<{ sessionId: string }>();
2022
const { activeSessionId, isProcessing, connectionStatus, checkServerStatus, sendMessage } =
2123
useSession();
24+
const { isWorkspaceMismatch, sessionWorkspace, serverWorkspace } = useWorkspaceEnvironmentCheck();
2225

2326
const currentSessionId = urlSessionId || activeSessionId;
2427
const groupedMessages = useAtomValue(groupedMessagesAtom);
@@ -60,6 +63,12 @@ export const ChatPanel: React.FC = () => {
6063
onReconnect={checkServerStatus}
6164
/>
6265

66+
<WorkspaceEnvironmentWarning
67+
isVisible={isWorkspaceMismatch && !isReplayMode}
68+
sessionWorkspace={sessionWorkspace}
69+
serverWorkspace={serverWorkspace}
70+
/>
71+
6372
{showEmptyState ? (
6473
<EmptyState replayState={replayState} isReplayMode={isReplayMode} />
6574
) : (
@@ -90,7 +99,8 @@ export const ChatPanel: React.FC = () => {
9099
currentSessionId === 'creating' ||
91100
isProcessing ||
92101
!connectionStatus.connected ||
93-
isReplayMode
102+
isReplayMode ||
103+
isWorkspaceMismatch
94104
}
95105
isProcessing={isProcessing}
96106
connectionStatus={connectionStatus}
@@ -100,6 +110,7 @@ export const ChatPanel: React.FC = () => {
100110
showContextualSelector={true}
101111
autoFocus={false}
102112
showHelpText={true}
113+
isWorkspaceMismatch={isWorkspaceMismatch}
103114
/>
104115
)}
105116
</div>

multimodal/tarko/agent-ui/src/standalone/chat/MessageInput/ChatInput.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ interface ChatInputProps {
4141
autoFocus?: boolean;
4242
showHelpText?: boolean;
4343
variant?: 'default' | 'home';
44+
isWorkspaceMismatch?: boolean;
4445
}
4546

4647
export const ChatInput: React.FC<ChatInputProps> = ({
@@ -58,6 +59,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({
5859
autoFocus = true,
5960
showHelpText = true,
6061
variant = 'default',
62+
isWorkspaceMismatch = false,
6163
}) => {
6264
const [uploadedImages, setUploadedImages] = useState<ChatCompletionContentPart[]>([]);
6365
const [isAborting, setIsAborting] = useState(false);
@@ -379,11 +381,13 @@ export const ChatInput: React.FC<ChatInputProps> = ({
379381
const defaultPlaceholder =
380382
connectionStatus && !connectionStatus.connected
381383
? 'Server disconnected...'
382-
: isProcessing
383-
? `${getAgentTitle()} is running...`
384-
: contextualSelectorEnabled
385-
? `Ask ${getAgentTitle()} something... (Use @ to reference files/folders, Ctrl+Enter to send)`
386-
: `Ask ${getAgentTitle()} something... (Ctrl+Enter to send)`;
384+
: isWorkspaceMismatch
385+
? 'Preview mode - workspace environment mismatch'
386+
: isProcessing
387+
? `${getAgentTitle()} is running...`
388+
: contextualSelectorEnabled
389+
? `Ask ${getAgentTitle()} something... (Use @ to reference files/folders, Ctrl+Enter to send)`
390+
: `Ask ${getAgentTitle()} something... (Ctrl+Enter to send)`;
387391

388392
return (
389393
<div className={`relative ${className}`}>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
import { FiAlertTriangle, FiEye } from 'react-icons/fi';
3+
import { motion, AnimatePresence } from 'framer-motion';
4+
5+
interface WorkspaceEnvironmentWarningProps {
6+
isVisible: boolean;
7+
sessionWorkspace: string;
8+
serverWorkspace: string;
9+
}
10+
11+
/**
12+
* Warning banner component for workspace environment mismatch
13+
* Displays when session workspace differs from server workspace
14+
*/
15+
export const WorkspaceEnvironmentWarning: React.FC<WorkspaceEnvironmentWarningProps> = ({
16+
isVisible,
17+
sessionWorkspace,
18+
serverWorkspace,
19+
}) => {
20+
if (!isVisible) return null;
21+
22+
const getWorkspaceName = (path: string) => {
23+
if (!path) return 'Unknown';
24+
return path.split('/').pop() || path;
25+
};
26+
27+
return (
28+
<AnimatePresence>
29+
{isVisible && (
30+
<motion.div
31+
initial={{ opacity: 0, height: 0 }}
32+
animate={{ opacity: 1, height: 'auto' }}
33+
exit={{ opacity: 0, height: 0 }}
34+
transition={{ duration: 0.3, ease: 'easeInOut' }}
35+
className="mx-4 mb-4 overflow-hidden"
36+
>
37+
<div className="bg-gradient-to-r from-amber-50 via-orange-50 to-amber-50 dark:from-amber-900/20 dark:via-orange-900/20 dark:to-amber-900/20 border border-amber-200 dark:border-amber-700/50 rounded-2xl p-4 shadow-sm">
38+
<div className="flex items-start gap-3">
39+
{/* Warning icon */}
40+
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-amber-100 dark:bg-amber-800/30 flex items-center justify-center mt-0.5">
41+
<FiAlertTriangle size={16} className="text-amber-600 dark:text-amber-400" />
42+
</div>
43+
44+
{/* Content */}
45+
<div className="flex-1 min-w-0">
46+
<div className="flex items-center gap-2 mb-2">
47+
<FiEye size={16} className="text-amber-600 dark:text-amber-400 flex-shrink-0" />
48+
<h3 className="text-sm font-semibold text-amber-800 dark:text-amber-200">
49+
Preview Mode - Environment Mismatch
50+
</h3>
51+
</div>
52+
53+
<p className="text-sm text-amber-700 dark:text-amber-300 leading-relaxed mb-3">
54+
This session was created in a different workspace environment.
55+
You can view the conversation history, but cannot send new messages.
56+
</p>
57+
58+
{/* Workspace comparison */}
59+
<div className="space-y-2 text-xs">
60+
<div className="flex items-center gap-2">
61+
<span className="text-amber-600 dark:text-amber-400 font-medium">Session:</span>
62+
<code className="px-2 py-1 bg-amber-100 dark:bg-amber-800/40 text-amber-800 dark:text-amber-200 rounded font-mono">
63+
{getWorkspaceName(sessionWorkspace)}
64+
</code>
65+
</div>
66+
<div className="flex items-center gap-2">
67+
<span className="text-amber-600 dark:text-amber-400 font-medium">Current:</span>
68+
<code className="px-2 py-1 bg-amber-100 dark:bg-amber-800/40 text-amber-800 dark:text-amber-200 rounded font-mono">
69+
{getWorkspaceName(serverWorkspace)}
70+
</code>
71+
</div>
72+
</div>
73+
</div>
74+
</div>
75+
</div>
76+
</motion.div>
77+
)}
78+
</AnimatePresence>
79+
);
80+
};

0 commit comments

Comments
 (0)