provider, model, and mode selections
User can now update the AI Provider, selected model on that provider, and chat session mode.
This commit is contained in:
parent
cb73d276a3
commit
819654e20a
@ -211,6 +211,17 @@ class SocketClient {
|
||||
}
|
||||
}
|
||||
|
||||
submitPrompt(
|
||||
prompt: string,
|
||||
cb: (success: boolean, data: { turnId?: string; message?: string }) => void,
|
||||
): void {
|
||||
if (this._socket?.connected) {
|
||||
this._socket.emit('submitPrompt', prompt, cb);
|
||||
} else {
|
||||
cb(false, { message: 'Socket not connected' });
|
||||
}
|
||||
}
|
||||
|
||||
requestSessionLock(
|
||||
registration: any,
|
||||
project: any,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useRef, useContext, useCallback } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { socketClient } from '../lib/socket';
|
||||
import { chatSessionApi, projectApi, type ChatSession, type ChatTurn } from '../lib/api';
|
||||
import { chatSessionApi, projectApi, providerApi, type ChatSession, type ChatTurn, ChatSessionMode, type AiProvider } from '../lib/api';
|
||||
import { WorkspaceMode } from '../lib/types';
|
||||
import WorkspaceModeIndicator from '../components/WorkspaceModeIndicator';
|
||||
import FilesPanel from '../components/FilesPanel';
|
||||
@ -30,9 +30,15 @@ export default function ChatSessionView() {
|
||||
const [error, setError] = useState('');
|
||||
const [sessionLocked, setSessionLocked] = useState(true);
|
||||
const [workspaceMode, setWorkspaceMode] = useState<WorkspaceMode>(WorkspaceMode.Idle);
|
||||
const [isUpdatingMode, setIsUpdatingMode] = useState(false);
|
||||
const [isUpdatingProvider, setIsUpdatingProvider] = useState(false);
|
||||
const [isUpdatingModel, setIsUpdatingModel] = useState(false);
|
||||
const [logExpanded, setLogExpanded] = useState(false);
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
const [toast, setToast] = useState<string | null>(null);
|
||||
const [providers, setProviders] = useState<AiProvider[]>([]);
|
||||
const [selectedProviderId, setSelectedProviderId] = useState<string>('');
|
||||
const [selectedModelId, setSelectedModelId] = useState<string>('');
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
@ -81,6 +87,15 @@ export default function ChatSessionView() {
|
||||
const turnsData = await chatSessionApi.getTurns(sessionId);
|
||||
setTurns(turnsData);
|
||||
setSessionLocked(true);
|
||||
|
||||
const allProviders = await providerApi.getAll();
|
||||
setProviders(allProviders);
|
||||
|
||||
const providerId = typeof sessionData.provider === 'string'
|
||||
? sessionData.provider
|
||||
: sessionData.provider?._id;
|
||||
setSelectedProviderId(providerId || '');
|
||||
setSelectedModelId(sessionData.selectedModel || '');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load session');
|
||||
@ -257,13 +272,91 @@ export default function ChatSessionView() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleModeChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
if (!session) return;
|
||||
|
||||
const newMode = e.target.value as ChatSessionMode;
|
||||
if (newMode === session.mode) return;
|
||||
|
||||
setIsUpdatingMode(true);
|
||||
try {
|
||||
const updatedSession = await chatSessionApi.update(session._id, { mode: newMode });
|
||||
setSession(updatedSession);
|
||||
showToast(`Session mode changed to ${newMode}`);
|
||||
} catch (err) {
|
||||
showToast(`Failed to change mode: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
||||
} finally {
|
||||
setIsUpdatingMode(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleProviderChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
if (!session) return;
|
||||
|
||||
const newProviderId = e.target.value;
|
||||
if (newProviderId === selectedProviderId) return;
|
||||
|
||||
setIsUpdatingProvider(true);
|
||||
try {
|
||||
const updatedSession = await chatSessionApi.update(session._id, {
|
||||
provider: newProviderId,
|
||||
selectedModel: ''
|
||||
});
|
||||
setSession(updatedSession);
|
||||
setSelectedProviderId(newProviderId);
|
||||
setSelectedModelId('');
|
||||
showToast('Provider changed. Please select a model.');
|
||||
} catch (err) {
|
||||
showToast(`Failed to change provider: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
||||
} finally {
|
||||
setIsUpdatingProvider(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleModelChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
if (!session) return;
|
||||
|
||||
const newModelId = e.target.value;
|
||||
if (newModelId === selectedModelId) return;
|
||||
|
||||
setIsUpdatingModel(true);
|
||||
try {
|
||||
const updatedSession = await chatSessionApi.update(session._id, {
|
||||
selectedModel: newModelId
|
||||
});
|
||||
setSession(updatedSession);
|
||||
setSelectedModelId(newModelId);
|
||||
showToast(`Model changed`);
|
||||
} catch (err) {
|
||||
showToast(`Failed to change model: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
||||
} finally {
|
||||
setIsUpdatingModel(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getSortedModels = (providerId: string) => {
|
||||
const provider = providers.find(p => p._id === providerId);
|
||||
if (!provider) return [];
|
||||
return [...provider.models].sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
const truncateModelName = (name: string, maxLength = 25) => {
|
||||
if (name.length <= maxLength) return name;
|
||||
return name.slice(0, maxLength - 3) + '...';
|
||||
};
|
||||
|
||||
const handleSubmitPrompt = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!promptInput.trim() || isProcessing || !socketClient.connected || !sessionLocked) return;
|
||||
if (workspaceMode !== WorkspaceMode.Agent) return;
|
||||
if (!session?.selectedModel) {
|
||||
showToast('Please select a model before submitting a prompt');
|
||||
return;
|
||||
}
|
||||
|
||||
const tempTurnId = `temp-${Date.now()}`;
|
||||
const userTurn: ChatTurn = {
|
||||
_id: `temp-${Date.now()}`,
|
||||
_id: tempTurnId,
|
||||
createdAt: new Date().toISOString(),
|
||||
user: session?.user?._id || '',
|
||||
project: session?.project?._id || projectId || '',
|
||||
@ -288,12 +381,33 @@ export default function ChatSessionView() {
|
||||
};
|
||||
|
||||
setTurns(prev => [...prev, userTurn]);
|
||||
currentTurnIdRef.current = userTurn._id;
|
||||
currentTurnIdRef.current = tempTurnId;
|
||||
setPromptInput('');
|
||||
setIsProcessing(true);
|
||||
setError('');
|
||||
|
||||
socketClient.emitServer('submitPrompt', promptInput.trim());
|
||||
socketClient.submitPrompt(promptInput.trim(), (success, data) => {
|
||||
if (success && data.turnId) {
|
||||
setTurns(prevTurns =>
|
||||
prevTurns.map(turn =>
|
||||
turn._id === tempTurnId
|
||||
? { ...turn, _id: data.turnId! }
|
||||
: turn
|
||||
)
|
||||
);
|
||||
currentTurnIdRef.current = data.turnId;
|
||||
} else if (!success) {
|
||||
setTurns(prevTurns =>
|
||||
prevTurns.map(turn =>
|
||||
turn._id === tempTurnId
|
||||
? { ...turn, status: 'error', errorMessage: data.message || 'Failed to submit prompt' }
|
||||
: turn
|
||||
)
|
||||
);
|
||||
setIsProcessing(false);
|
||||
currentTurnIdRef.current = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
@ -308,9 +422,11 @@ export default function ChatSessionView() {
|
||||
);
|
||||
}, []);
|
||||
|
||||
const promptDisabled = isProcessing || workspaceMode !== WorkspaceMode.Agent || !sessionLocked;
|
||||
const promptDisabled = isProcessing || workspaceMode !== WorkspaceMode.Agent || !sessionLocked || !session?.selectedModel;
|
||||
const promptPlaceholder = workspaceMode !== WorkspaceMode.Agent
|
||||
? 'Select Agent mode to enter a prompt.'
|
||||
: !session?.selectedModel
|
||||
? 'Please select a model first.'
|
||||
: 'Enter your prompt... (Shift+Enter for new line)';
|
||||
|
||||
if (loading) {
|
||||
@ -414,20 +530,66 @@ export default function ChatSessionView() {
|
||||
disabled={!sessionLocked}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4 space-y-3">
|
||||
<div className="flex items-center gap-1 text-green-500">
|
||||
<span>✓</span>
|
||||
<span className="text-xs">{sessionLocked ? 'Locked' : 'Unlocked'}</span>
|
||||
<div className="p-4 space-y-3">
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Name</div>
|
||||
<div className="text-text-primary">{session?.name}</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<div className="text-xs text-text-muted">Provider</div>
|
||||
<select
|
||||
value={selectedProviderId || ''}
|
||||
onChange={handleProviderChange}
|
||||
disabled={isUpdatingProvider}
|
||||
className="w-full mt-1 px-2 py-1.5 bg-bg-tertiary border border-border-default rounded text-text-primary text-sm focus:border-brand focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{providers.map((provider) => (
|
||||
<option key={provider._id} value={provider._id}>
|
||||
{provider.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="text-xs text-text-muted">Model</div>
|
||||
<select
|
||||
value={selectedModelId || ''}
|
||||
onChange={handleModelChange}
|
||||
disabled={isUpdatingModel || !selectedProviderId}
|
||||
className="w-full mt-1 px-2 py-1.5 bg-bg-tertiary border border-border-default rounded text-text-primary text-sm focus:border-brand focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed truncate"
|
||||
>
|
||||
{!selectedProviderId ? (
|
||||
<option value="">Select provider first</option>
|
||||
) : (
|
||||
<>
|
||||
<option value="">-- Select Model --</option>
|
||||
{getSortedModels(selectedProviderId).map((model) => (
|
||||
<option key={model.id} value={model.id}>
|
||||
{truncateModelName(model.name)}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Mode</div>
|
||||
<select
|
||||
value={session?.mode || ChatSessionMode.Build}
|
||||
onChange={handleModeChange}
|
||||
disabled={isUpdatingMode}
|
||||
className="w-full mt-1 px-2 py-1.5 bg-bg-tertiary border border-border-default rounded text-text-primary text-sm focus:border-brand focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{Object.values(ChatSessionMode).map((mode) => (
|
||||
<option key={mode} value={mode}>
|
||||
{mode.charAt(0).toUpperCase() + mode.slice(1)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Name</div>
|
||||
<div className="text-text-primary">{session?.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Model</div>
|
||||
<div className="text-text-primary">{session?.selectedModel}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* PROJECT Panel */}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user