mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-13 05:07:40 +02:00
[Haik]: ckpt, view builder saving issue fixed via templates, system prompts, and skills. Also added a reset to default for modes.
This commit is contained in:
@@ -404,11 +404,6 @@ const AgentCard: React.FC<Props> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleCollapse = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
dispatch(collapseSession(session.id));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (session.status === 'running' || session.status === 'waiting_approval') {
|
||||
@@ -750,37 +745,20 @@ const AgentCard: React.FC<Props> = ({
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0, ml: 0.5 }}
|
||||
>
|
||||
{expanded ? (
|
||||
<Tooltip title="Collapse">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={handleCollapse}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
sx={{
|
||||
color: c.text.ghost,
|
||||
p: 0.5,
|
||||
'&:hover': { color: c.text.secondary, bgcolor: c.bg.secondary },
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip title={isDraft ? 'Remove' : 'Close chat'}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={handleRemove}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
sx={{
|
||||
color: c.text.ghost,
|
||||
p: 0.5,
|
||||
'&:hover': { color: c.status.error, bgcolor: `${c.status.errorBg}` },
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={isDraft ? 'Remove' : 'Close chat'}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={handleRemove}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
sx={{
|
||||
color: c.text.ghost,
|
||||
p: 0.5,
|
||||
'&:hover': { color: c.status.error, bgcolor: `${c.status.errorBg}` },
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||
import RestoreIcon from '@mui/icons-material/Restore';
|
||||
import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
|
||||
import QuestionAnswerOutlinedIcon from '@mui/icons-material/QuestionAnswerOutlined';
|
||||
import MapOutlinedIcon from '@mui/icons-material/MapOutlined';
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
createMode,
|
||||
updateMode,
|
||||
deleteMode,
|
||||
resetMode,
|
||||
Mode,
|
||||
} from '@/shared/state/modesSlice';
|
||||
import { fetchBuiltinTools, fetchTools } from '@/shared/state/toolsSlice';
|
||||
@@ -105,7 +107,7 @@ const ALL_BUILTIN_TOOL_NAMES = ['Read', 'Edit', 'Write', 'Bash', 'Glob', 'Grep',
|
||||
const Modes: React.FC = () => {
|
||||
const c = useClaudeTokens();
|
||||
const dispatch = useAppDispatch();
|
||||
const { items, loading } = useAppSelector((s) => s.modes);
|
||||
const { items, builtinDefaults, loading } = useAppSelector((s) => s.modes);
|
||||
const toolItems = useAppSelector((s) => s.tools.items);
|
||||
const modes = useMemo(() => Object.values(items), [items]);
|
||||
|
||||
@@ -174,6 +176,45 @@ const Modes: React.FC = () => {
|
||||
await dispatch(deleteMode(id));
|
||||
};
|
||||
|
||||
const editingIsBuiltin = editingId ? items[editingId]?.is_builtin ?? false : false;
|
||||
|
||||
const hasDiverged = useMemo(() => {
|
||||
if (!editingId || !editingIsBuiltin) return false;
|
||||
const defaults = builtinDefaults[editingId];
|
||||
if (!defaults) return false;
|
||||
const current = items[editingId];
|
||||
if (!current) return false;
|
||||
return (
|
||||
current.name !== defaults.name ||
|
||||
current.description !== defaults.description ||
|
||||
(current.system_prompt ?? '') !== (defaults.system_prompt ?? '') ||
|
||||
JSON.stringify(current.tools) !== JSON.stringify(defaults.tools) ||
|
||||
(current.default_next_mode ?? '') !== (defaults.default_next_mode ?? '') ||
|
||||
current.icon !== defaults.icon ||
|
||||
current.color !== defaults.color ||
|
||||
(current.default_folder ?? '') !== (defaults.default_folder ?? '')
|
||||
);
|
||||
}, [editingId, editingIsBuiltin, items, builtinDefaults]);
|
||||
|
||||
const handleReset = async () => {
|
||||
if (!editingId) return;
|
||||
const action = await dispatch(resetMode(editingId));
|
||||
if (resetMode.fulfilled.match(action)) {
|
||||
const m = action.payload;
|
||||
setForm({
|
||||
name: m.name,
|
||||
description: m.description,
|
||||
system_prompt: m.system_prompt ?? '',
|
||||
tools: m.tools ?? [],
|
||||
toolsEnabled: m.tools !== null,
|
||||
default_next_mode: m.default_next_mode ?? '',
|
||||
icon: m.icon,
|
||||
color: m.color,
|
||||
default_folder: m.default_folder ?? '',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const otherModes = modes.filter((m) => m.id !== editingId);
|
||||
|
||||
return (
|
||||
@@ -505,23 +546,46 @@ const Modes: React.FC = () => {
|
||||
</FormControl>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
<Button onClick={() => setDialogOpen(false)} sx={{ color: c.text.tertiary, textTransform: 'none' }}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSave}
|
||||
disabled={!form.name}
|
||||
sx={{
|
||||
bgcolor: c.accent.primary,
|
||||
'&:hover': { bgcolor: c.accent.pressed },
|
||||
textTransform: 'none',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
{editingId ? 'Save Changes' : 'Create Mode'}
|
||||
</Button>
|
||||
<DialogActions sx={{ px: 3, pb: 2, justifyContent: 'space-between' }}>
|
||||
<Box>
|
||||
{editingIsBuiltin && (
|
||||
<Tooltip title={hasDiverged ? 'Restore this mode to its original built-in defaults' : 'Mode matches built-in defaults'}>
|
||||
<span>
|
||||
<Button
|
||||
startIcon={<RestoreIcon sx={{ fontSize: 16 }} />}
|
||||
onClick={handleReset}
|
||||
disabled={!hasDiverged}
|
||||
sx={{
|
||||
color: hasDiverged ? c.text.muted : c.text.ghost,
|
||||
textTransform: 'none',
|
||||
fontSize: '0.82rem',
|
||||
'&:hover': hasDiverged ? { color: c.status.error, bgcolor: `${c.status.error}10` } : {},
|
||||
}}
|
||||
>
|
||||
Reset to Default
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button onClick={() => setDialogOpen(false)} sx={{ color: c.text.tertiary, textTransform: 'none' }}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSave}
|
||||
disabled={!form.name}
|
||||
sx={{
|
||||
bgcolor: c.accent.primary,
|
||||
'&:hover': { bgcolor: c.accent.pressed },
|
||||
textTransform: 'none',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
{editingId ? 'Save Changes' : 'Create Mode'}
|
||||
</Button>
|
||||
</Box>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
|
||||
@@ -381,7 +381,7 @@ interface FileTreeItemProps {
|
||||
c: ReturnType<typeof useClaudeTokens>;
|
||||
}
|
||||
|
||||
const PROTECTED_FILES = new Set(['index.html', 'schema.json', 'meta.json']);
|
||||
const PROTECTED_FILES = new Set(['index.html', 'schema.json', 'meta.json', 'SKILL.md']);
|
||||
|
||||
const FileTreeItem: React.FC<FileTreeItemProps> = ({ node, depth, activeFile, onSelect, onDelete, c }) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
@@ -732,6 +732,7 @@ const ViewEditor: React.FC<Props> = ({ output, onClose }) => {
|
||||
const outputFiles = { ...files };
|
||||
delete outputFiles['meta.json'];
|
||||
delete outputFiles['schema.json'];
|
||||
delete outputFiles['SKILL.md'];
|
||||
|
||||
return {
|
||||
name: name || 'Untitled App',
|
||||
@@ -948,7 +949,7 @@ const ViewEditor: React.FC<Props> = ({ output, onClose }) => {
|
||||
? `${SERVE_BASE}/workspace/${workspaceId}/serve/index.html`
|
||||
: undefined;
|
||||
|
||||
const filePaths = useMemo(() => Object.keys(files).filter(p => p !== 'meta.json').sort(), [files]);
|
||||
const filePaths = useMemo(() => Object.keys(files).filter(p => p !== 'meta.json' && p !== 'SKILL.md').sort(), [files]);
|
||||
const fileTree = useMemo(() => buildFileTree(filePaths), [filePaths]);
|
||||
|
||||
const updateFile = useCallback((path: string, content: string) => {
|
||||
|
||||
@@ -18,18 +18,19 @@ export interface Mode {
|
||||
|
||||
interface ModesState {
|
||||
items: Record<string, Mode>;
|
||||
builtinDefaults: Record<string, Mode>;
|
||||
loading: boolean;
|
||||
loaded: boolean;
|
||||
}
|
||||
|
||||
const initialState: ModesState = { items: {}, loading: false, loaded: false };
|
||||
const initialState: ModesState = { items: {}, builtinDefaults: {}, loading: false, loaded: false };
|
||||
|
||||
export const fetchModes = createAsyncThunk(
|
||||
'modes/fetch',
|
||||
async () => {
|
||||
const res = await fetch(`${MODES_API}/list`);
|
||||
const data = await res.json();
|
||||
return data.modes as Mode[];
|
||||
return { modes: data.modes as Mode[], builtinDefaults: (data.builtin_defaults ?? {}) as Record<string, Mode> };
|
||||
},
|
||||
{ condition: (_, { getState }) => !(getState() as { modes: ModesState }).modes.loading },
|
||||
);
|
||||
@@ -60,6 +61,15 @@ export const updateMode = createAsyncThunk(
|
||||
}
|
||||
);
|
||||
|
||||
export const resetMode = createAsyncThunk(
|
||||
'modes/reset',
|
||||
async (id: string) => {
|
||||
const res = await fetch(`${MODES_API}/${id}/reset`, { method: 'POST' });
|
||||
const data = await res.json();
|
||||
return data.mode as Mode;
|
||||
}
|
||||
);
|
||||
|
||||
export const deleteMode = createAsyncThunk('modes/delete', async (id: string) => {
|
||||
await fetch(`${MODES_API}/${id}`, { method: 'DELETE' });
|
||||
return id;
|
||||
@@ -76,11 +86,13 @@ const modesSlice = createSlice({
|
||||
state.loading = false;
|
||||
state.loaded = true;
|
||||
state.items = {};
|
||||
for (const m of action.payload) state.items[m.id] = m;
|
||||
for (const m of action.payload.modes) state.items[m.id] = m;
|
||||
state.builtinDefaults = action.payload.builtinDefaults;
|
||||
})
|
||||
.addCase(fetchModes.rejected, (state) => { state.loading = false; state.loaded = true; })
|
||||
.addCase(createMode.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; })
|
||||
.addCase(updateMode.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; })
|
||||
.addCase(resetMode.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; })
|
||||
.addCase(deleteMode.fulfilled, (state, action) => { delete state.items[action.payload]; });
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user