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(
|
requestSessionLock(
|
||||||
registration: any,
|
registration: any,
|
||||||
project: any,
|
project: any,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect, useRef, useContext, useCallback } from 'react';
|
import { useState, useEffect, useRef, useContext, useCallback } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { socketClient } from '../lib/socket';
|
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 { WorkspaceMode } from '../lib/types';
|
||||||
import WorkspaceModeIndicator from '../components/WorkspaceModeIndicator';
|
import WorkspaceModeIndicator from '../components/WorkspaceModeIndicator';
|
||||||
import FilesPanel from '../components/FilesPanel';
|
import FilesPanel from '../components/FilesPanel';
|
||||||
@ -30,9 +30,15 @@ export default function ChatSessionView() {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [sessionLocked, setSessionLocked] = useState(true);
|
const [sessionLocked, setSessionLocked] = useState(true);
|
||||||
const [workspaceMode, setWorkspaceMode] = useState<WorkspaceMode>(WorkspaceMode.Idle);
|
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 [logExpanded, setLogExpanded] = useState(false);
|
||||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||||
const [toast, setToast] = useState<string | null>(null);
|
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 messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||||
@ -81,6 +87,15 @@ export default function ChatSessionView() {
|
|||||||
const turnsData = await chatSessionApi.getTurns(sessionId);
|
const turnsData = await chatSessionApi.getTurns(sessionId);
|
||||||
setTurns(turnsData);
|
setTurns(turnsData);
|
||||||
setSessionLocked(true);
|
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) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to load session');
|
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) => {
|
const handleSubmitPrompt = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!promptInput.trim() || isProcessing || !socketClient.connected || !sessionLocked) return;
|
if (!promptInput.trim() || isProcessing || !socketClient.connected || !sessionLocked) return;
|
||||||
if (workspaceMode !== WorkspaceMode.Agent) 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 = {
|
const userTurn: ChatTurn = {
|
||||||
_id: `temp-${Date.now()}`,
|
_id: tempTurnId,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
user: session?.user?._id || '',
|
user: session?.user?._id || '',
|
||||||
project: session?.project?._id || projectId || '',
|
project: session?.project?._id || projectId || '',
|
||||||
@ -288,12 +381,33 @@ export default function ChatSessionView() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setTurns(prev => [...prev, userTurn]);
|
setTurns(prev => [...prev, userTurn]);
|
||||||
currentTurnIdRef.current = userTurn._id;
|
currentTurnIdRef.current = tempTurnId;
|
||||||
setPromptInput('');
|
setPromptInput('');
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
setError('');
|
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 = () => {
|
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
|
const promptPlaceholder = workspaceMode !== WorkspaceMode.Agent
|
||||||
? 'Select Agent mode to enter a prompt.'
|
? 'Select Agent mode to enter a prompt.'
|
||||||
|
: !session?.selectedModel
|
||||||
|
? 'Please select a model first.'
|
||||||
: 'Enter your prompt... (Shift+Enter for new line)';
|
: 'Enter your prompt... (Shift+Enter for new line)';
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@ -414,20 +530,66 @@ export default function ChatSessionView() {
|
|||||||
disabled={!sessionLocked}
|
disabled={!sessionLocked}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 space-y-3">
|
<div className="p-4 space-y-3">
|
||||||
<div className="flex items-center gap-1 text-green-500">
|
<div>
|
||||||
<span>✓</span>
|
<div className="text-xs text-text-muted">Name</div>
|
||||||
<span className="text-xs">{sessionLocked ? 'Locked' : 'Unlocked'}</span>
|
<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>
|
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
{/* PROJECT Panel */}
|
{/* PROJECT Panel */}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user