Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
8650533697
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,4 +3,5 @@ gadget-drone.*.log
|
||||
logfetch
|
||||
|
||||
.gadget
|
||||
gadget/
|
||||
node_modules
|
||||
|
||||
43
gadget-code/frontend/src/components/ProjectPanel.tsx
Normal file
43
gadget-code/frontend/src/components/ProjectPanel.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import type { Project } from '../lib/api';
|
||||
|
||||
interface ProjectPanelProps {
|
||||
project: Project | null;
|
||||
onNavigateToProject: () => void;
|
||||
}
|
||||
|
||||
export default function ProjectPanel({ project, onNavigateToProject }: ProjectPanelProps) {
|
||||
return (
|
||||
<div className="border-b border-border-subtle shrink-0">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-bg-tertiary">
|
||||
<h3 className="text-sm font-semibold text-text-secondary uppercase tracking-wider">
|
||||
Project
|
||||
</h3>
|
||||
<button
|
||||
title="Project Manager"
|
||||
onClick={onNavigateToProject}
|
||||
className="text-text-muted hover:text-text-primary transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 space-y-2">
|
||||
{project ? (
|
||||
<>
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Name</div>
|
||||
<div className="text-text-primary">{project.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Slug</div>
|
||||
<div className="font-mono text-xs text-text-secondary">{project.slug}</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-text-muted text-sm">Loading...</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
267
gadget-code/frontend/src/components/SessionPanel.tsx
Normal file
267
gadget-code/frontend/src/components/SessionPanel.tsx
Normal file
@ -0,0 +1,267 @@
|
||||
import { ChatSessionMode, type AiProvider, type ChatSession } from '../lib/api';
|
||||
import { WorkspaceMode } from '../lib/types';
|
||||
import WorkspaceModeIndicator from './WorkspaceModeIndicator';
|
||||
|
||||
interface SessionPanelProps {
|
||||
session: ChatSession | null;
|
||||
workspaceMode: WorkspaceMode;
|
||||
sessionLocked: boolean;
|
||||
providers: AiProvider[];
|
||||
selectedProviderId: string;
|
||||
selectedModelId: string;
|
||||
sessionReasoningEffort: string;
|
||||
isEditingProvider: boolean;
|
||||
editProviderId: string;
|
||||
editModelId: string;
|
||||
isUpdatingProvider: boolean;
|
||||
isEditingName: boolean;
|
||||
editName: string;
|
||||
isUpdatingName: boolean;
|
||||
isUpdatingMode: boolean;
|
||||
isUpdatingReasoning: boolean;
|
||||
onWorkspaceModeChange: (mode: WorkspaceMode) => void;
|
||||
onModeChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
onReasoningChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
onStartEditProvider: () => void;
|
||||
onSaveProvider: () => void;
|
||||
onCancelEditProvider: () => void;
|
||||
onEditProviderIdChange: (id: string) => void;
|
||||
onEditModelIdChange: (id: string) => void;
|
||||
onStartEditName: () => void;
|
||||
onSaveName: () => void;
|
||||
onCancelEditName: () => void;
|
||||
onEditNameChange: (name: string) => void;
|
||||
getProviderName: (id: string) => string;
|
||||
getModelName: (providerId: string, modelId: string) => string;
|
||||
getSortedModels: (providerId: string) => AiProvider['models'];
|
||||
truncateModelName: (name: string, maxLength?: number) => string;
|
||||
getSelectedModelCapabilities: () => { hasThinking: boolean } | null;
|
||||
}
|
||||
|
||||
export default function SessionPanel({
|
||||
session,
|
||||
workspaceMode,
|
||||
sessionLocked,
|
||||
providers,
|
||||
selectedProviderId,
|
||||
selectedModelId,
|
||||
sessionReasoningEffort,
|
||||
isEditingProvider,
|
||||
editProviderId,
|
||||
editModelId,
|
||||
isUpdatingProvider,
|
||||
isEditingName,
|
||||
editName,
|
||||
isUpdatingName,
|
||||
isUpdatingMode,
|
||||
isUpdatingReasoning,
|
||||
onWorkspaceModeChange,
|
||||
onModeChange,
|
||||
onReasoningChange,
|
||||
onStartEditProvider,
|
||||
onSaveProvider,
|
||||
onCancelEditProvider,
|
||||
onEditProviderIdChange,
|
||||
onEditModelIdChange,
|
||||
onStartEditName,
|
||||
onSaveName,
|
||||
onCancelEditName,
|
||||
onEditNameChange,
|
||||
getProviderName,
|
||||
getModelName,
|
||||
getSortedModels,
|
||||
truncateModelName,
|
||||
getSelectedModelCapabilities,
|
||||
}: SessionPanelProps) {
|
||||
return (
|
||||
<div className="border-b border-border-subtle shrink-0">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-bg-tertiary">
|
||||
<h3 className="text-sm font-semibold text-text-secondary uppercase tracking-wider">
|
||||
Session
|
||||
</h3>
|
||||
<WorkspaceModeIndicator
|
||||
mode={workspaceMode}
|
||||
onChange={onWorkspaceModeChange}
|
||||
disabled={!sessionLocked || isEditingProvider}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4 space-y-3">
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Name</div>
|
||||
{isEditingName ? (
|
||||
<div className="flex gap-2 mt-1">
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
onChange={(e) => onEditNameChange(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') { e.preventDefault(); onSaveName(); }
|
||||
if (e.key === 'Escape') { e.preventDefault(); onCancelEditName(); }
|
||||
}}
|
||||
className="flex-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"
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
title="Save"
|
||||
onClick={onSaveName}
|
||||
disabled={!editName.trim() || isUpdatingName}
|
||||
className="text-green-500 hover:text-green-400 disabled:opacity-30 disabled:cursor-not-allowed transition-colors self-center"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
title="Cancel"
|
||||
onClick={onCancelEditName}
|
||||
disabled={isUpdatingName}
|
||||
className="text-red-500 hover:text-red-400 disabled:opacity-30 transition-colors self-center"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-2 items-center mt-1">
|
||||
<div className="text-text-primary flex-1">{session?.name}</div>
|
||||
<button
|
||||
title="Edit name"
|
||||
onClick={onStartEditName}
|
||||
className="text-text-muted hover:text-text-primary transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isEditingProvider ? (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<div className="text-xs text-text-muted">Provider</div>
|
||||
<select
|
||||
value={editProviderId || ''}
|
||||
onChange={(e) => { onEditProviderIdChange(e.target.value); onEditModelIdChange(''); }}
|
||||
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"
|
||||
>
|
||||
<option value="">-- Select Provider --</option>
|
||||
{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={editModelId || ''}
|
||||
onChange={(e) => onEditModelIdChange(e.target.value)}
|
||||
disabled={!editProviderId}
|
||||
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"
|
||||
>
|
||||
{!editProviderId ? (
|
||||
<option value="">Select provider first</option>
|
||||
) : (
|
||||
<>
|
||||
<option value="">-- Select Model --</option>
|
||||
{getSortedModels(editProviderId).map((model) => (
|
||||
<option key={model.id} value={model.id}>
|
||||
{truncateModelName(model.name)}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-end gap-1 pb-1">
|
||||
<button
|
||||
title="Save"
|
||||
onClick={onSaveProvider}
|
||||
disabled={!editProviderId || !editModelId || isUpdatingProvider}
|
||||
className="text-green-500 hover:text-green-400 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
title="Cancel"
|
||||
onClick={onCancelEditProvider}
|
||||
disabled={isUpdatingProvider}
|
||||
className="text-red-500 hover:text-red-400 disabled:opacity-30 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<div className="text-xs text-text-muted">Provider</div>
|
||||
<div className="text-text-primary text-sm mt-1">{getProviderName(selectedProviderId)}</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="text-xs text-text-muted">Model</div>
|
||||
<div
|
||||
className="text-text-primary text-sm mt-1 truncate"
|
||||
title={getModelName(selectedProviderId, selectedModelId)}
|
||||
>
|
||||
{getModelName(selectedProviderId, selectedModelId)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Change provider/model..."
|
||||
onClick={onStartEditProvider}
|
||||
className="self-end pb-1 text-text-muted hover:text-text-primary transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Mode</div>
|
||||
<select
|
||||
value={session?.mode || ChatSessionMode.Build}
|
||||
onChange={onModeChange}
|
||||
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 className="text-xs text-text-muted">Reasoning</div>
|
||||
<select
|
||||
value={sessionReasoningEffort}
|
||||
onChange={onReasoningChange}
|
||||
disabled={isUpdatingReasoning || !getSelectedModelCapabilities()?.hasThinking}
|
||||
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"
|
||||
title={
|
||||
!getSelectedModelCapabilities()?.hasThinking
|
||||
? "Selected model does not support reasoning"
|
||||
: "Controls how much the model thinks before responding"
|
||||
}
|
||||
>
|
||||
<option value="off">Off</option>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -3,7 +3,8 @@ import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { socketClient } from '../lib/socket';
|
||||
import { chatSessionApi, projectApi, providerApi, type ChatSession, type ChatTurn, type ChatTurnBlock, ChatTurnStats, ChatSessionMode, type AiProvider, type Project } from '../lib/api';
|
||||
import { WorkspaceMode } from '../lib/types';
|
||||
import WorkspaceModeIndicator from '../components/WorkspaceModeIndicator';
|
||||
import SessionPanel from '../components/SessionPanel';
|
||||
import ProjectPanel from '../components/ProjectPanel';
|
||||
import FilesPanel from '../components/FilesPanel';
|
||||
import LogPanel from '../components/LogPanel';
|
||||
import ChatTurnComponent from '../components/ChatTurn';
|
||||
@ -1164,232 +1165,47 @@ export default function ChatSessionView() {
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="w-80 border-l border-border-subtle bg-bg-secondary flex flex-col">
|
||||
{/* SESSION Panel */}
|
||||
<div className="border-b border-border-subtle shrink-0">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-bg-tertiary">
|
||||
<h3 className="text-sm font-semibold text-text-secondary uppercase tracking-wider">
|
||||
Session
|
||||
</h3>
|
||||
<WorkspaceModeIndicator
|
||||
mode={workspaceMode}
|
||||
onChange={handleWorkspaceModeChange}
|
||||
disabled={!sessionLocked || isEditingProvider}
|
||||
<SessionPanel
|
||||
session={session}
|
||||
workspaceMode={workspaceMode}
|
||||
sessionLocked={sessionLocked}
|
||||
providers={providers}
|
||||
selectedProviderId={selectedProviderId}
|
||||
selectedModelId={selectedModelId}
|
||||
sessionReasoningEffort={sessionReasoningEffort}
|
||||
isEditingProvider={isEditingProvider}
|
||||
editProviderId={editProviderId}
|
||||
editModelId={editModelId}
|
||||
isUpdatingProvider={isUpdatingProvider}
|
||||
isEditingName={isEditingName}
|
||||
editName={editName}
|
||||
isUpdatingName={isUpdatingName}
|
||||
isUpdatingMode={isUpdatingMode}
|
||||
isUpdatingReasoning={isUpdatingReasoning}
|
||||
onWorkspaceModeChange={handleWorkspaceModeChange}
|
||||
onModeChange={handleModeChange}
|
||||
onReasoningChange={handleReasoningChange}
|
||||
onStartEditProvider={handleStartEditProvider}
|
||||
onSaveProvider={handleSaveProvider}
|
||||
onCancelEditProvider={handleCancelEditProvider}
|
||||
onEditProviderIdChange={setEditProviderId}
|
||||
onEditModelIdChange={setEditModelId}
|
||||
onStartEditName={handleStartEditName}
|
||||
onSaveName={handleSaveName}
|
||||
onCancelEditName={handleCancelEditName}
|
||||
onEditNameChange={setEditName}
|
||||
getProviderName={getProviderName}
|
||||
getModelName={getModelName}
|
||||
getSortedModels={getSortedModels}
|
||||
truncateModelName={truncateModelName}
|
||||
getSelectedModelCapabilities={getSelectedModelCapabilities}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4 space-y-3">
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Name</div>
|
||||
{isEditingName ? (
|
||||
<div className="flex gap-2 mt-1">
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
onChange={(e) => setEditName(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') { e.preventDefault(); handleSaveName(); }
|
||||
if (e.key === 'Escape') { e.preventDefault(); handleCancelEditName(); }
|
||||
}}
|
||||
className="flex-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"
|
||||
autoFocus
|
||||
|
||||
<ProjectPanel
|
||||
project={project}
|
||||
onNavigateToProject={() => navigate(`/projects/${project?.slug}`)}
|
||||
/>
|
||||
<button
|
||||
title="Save"
|
||||
onClick={handleSaveName}
|
||||
disabled={!editName.trim() || isUpdatingName}
|
||||
className="text-green-500 hover:text-green-400 disabled:opacity-30 disabled:cursor-not-allowed transition-colors self-center"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
title="Cancel"
|
||||
onClick={handleCancelEditName}
|
||||
disabled={isUpdatingName}
|
||||
className="text-red-500 hover:text-red-400 disabled:opacity-30 transition-colors self-center"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-2 items-center mt-1">
|
||||
<div className="text-text-primary flex-1">{session?.name}</div>
|
||||
<button
|
||||
title="Edit name"
|
||||
onClick={handleStartEditName}
|
||||
className="text-text-muted hover:text-text-primary transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isEditingProvider ? (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<div className="text-xs text-text-muted">Provider</div>
|
||||
<select
|
||||
value={editProviderId || ''}
|
||||
onChange={(e) => { setEditProviderId(e.target.value); setEditModelId(''); }}
|
||||
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"
|
||||
>
|
||||
<option value="">-- Select Provider --</option>
|
||||
{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={editModelId || ''}
|
||||
onChange={(e) => setEditModelId(e.target.value)}
|
||||
disabled={!editProviderId}
|
||||
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"
|
||||
>
|
||||
{!editProviderId ? (
|
||||
<option value="">Select provider first</option>
|
||||
) : (
|
||||
<>
|
||||
<option value="">-- Select Model --</option>
|
||||
{getSortedModels(editProviderId).map((model) => (
|
||||
<option key={model.id} value={model.id}>
|
||||
{truncateModelName(model.name)}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-end gap-1 pb-1">
|
||||
<button
|
||||
title="Save"
|
||||
onClick={handleSaveProvider}
|
||||
disabled={!editProviderId || !editModelId || isUpdatingProvider}
|
||||
className="text-green-500 hover:text-green-400 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
title="Cancel"
|
||||
onClick={handleCancelEditProvider}
|
||||
disabled={isUpdatingProvider}
|
||||
className="text-red-500 hover:text-red-400 disabled:opacity-30 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<div className="text-xs text-text-muted">Provider</div>
|
||||
<div className="text-text-primary text-sm mt-1">{getProviderName(selectedProviderId)}</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="text-xs text-text-muted">Model</div>
|
||||
<div
|
||||
className="text-text-primary text-sm mt-1 truncate"
|
||||
title={getModelName(selectedProviderId, selectedModelId)}
|
||||
>
|
||||
{getModelName(selectedProviderId, selectedModelId)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Change provider/model..."
|
||||
onClick={handleStartEditProvider}
|
||||
className="self-end pb-1 text-text-muted hover:text-text-primary transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</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 className="text-xs text-text-muted">Reasoning</div>
|
||||
<select
|
||||
value={sessionReasoningEffort}
|
||||
onChange={handleReasoningChange}
|
||||
disabled={isUpdatingReasoning || !getSelectedModelCapabilities()?.hasThinking}
|
||||
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"
|
||||
title={
|
||||
!getSelectedModelCapabilities()?.hasThinking
|
||||
? "Selected model does not support reasoning"
|
||||
: "Controls how much the model thinks before responding"
|
||||
}
|
||||
>
|
||||
<option value="off">Off</option>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* PROJECT Panel */}
|
||||
<div className="border-b border-border-subtle shrink-0">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-bg-tertiary">
|
||||
<h3 className="text-sm font-semibold text-text-secondary uppercase tracking-wider">
|
||||
Project
|
||||
</h3>
|
||||
<button
|
||||
title="Project Manager"
|
||||
onClick={() => navigate(`/projects/${project?.slug}`)}
|
||||
className="text-text-muted hover:text-text-primary transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 space-y-2">
|
||||
{project ? (
|
||||
<>
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Name</div>
|
||||
<div className="text-text-primary">{project.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-text-muted">Slug</div>
|
||||
<div className="font-mono text-xs text-text-secondary">{project.slug}</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-text-muted text-sm">Loading...</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* FILES Panel */}
|
||||
<FilesPanel
|
||||
workspaceMode={workspaceMode}
|
||||
onFileSelect={(path) => {
|
||||
|
||||
27
scripts/forever-sleep.js
Normal file
27
scripts/forever-sleep.js
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* forever-sleep.js
|
||||
* A script that sleeps, wakes up every 5 seconds, logs to stdout, then goes back to sleep.
|
||||
*/
|
||||
|
||||
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
async function main() {
|
||||
console.log('forever-sleep: Starting...');
|
||||
|
||||
while (true) {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`forever-sleep: ${timestamp} - Waking up (sleeping for 5 seconds)...`);
|
||||
|
||||
await sleep(5000);
|
||||
|
||||
const timestampAfter = new Date().toISOString();
|
||||
console.log(`forever-sleep: ${timestampAfter} - Back to sleep...`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('forever-sleep: Error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user