feat: separate guides into system and custom sections
Add system/custom guide separation with section headers, lock icons, delete functionality for custom guides, and usage counter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
7bf94d4c61
commit
549476274d
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { ArrowLeft, Lock, Trash2 } from 'lucide-react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import PaneHeader from './PaneHeader';
|
||||
@@ -10,6 +10,7 @@ import type { BasePaneProps } from './types';
|
||||
interface GuideMeta {
|
||||
name: string;
|
||||
description: string;
|
||||
immutable: boolean;
|
||||
}
|
||||
|
||||
interface Guide extends GuideMeta {
|
||||
@@ -27,6 +28,7 @@ export default function GuidesPane({
|
||||
const [guides, setGuides] = useState<GuideMeta[]>([]);
|
||||
const [selectedGuide, setSelectedGuide] = useState<Guide | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [deleting, setDeleting] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchGuides();
|
||||
@@ -62,10 +64,34 @@ export default function GuidesPane({
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteGuide = async (name: string, e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (!confirm(`Delete guide "${name}"?`)) return;
|
||||
|
||||
setDeleting(name);
|
||||
try {
|
||||
const res = await fetch(`/api/guides/${encodeURIComponent(name)}`, { method: 'DELETE' });
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
fetchGuides();
|
||||
if (selectedGuide?.name === name) {
|
||||
setSelectedGuide(null);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[GuidesPane] Failed to delete guide:', err);
|
||||
} finally {
|
||||
setDeleting(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
setSelectedGuide(null);
|
||||
};
|
||||
|
||||
const systemGuides = guides.filter(g => g.immutable);
|
||||
const userGuides = guides.filter(g => !g.immutable);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
height: '100%',
|
||||
@@ -75,7 +101,7 @@ export default function GuidesPane({
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<PaneHeader slot={slot} onCollapse={onCollapse} onSwapPanes={onSwapPanes} tabBar={tabBar}>
|
||||
{selectedGuide && (
|
||||
{selectedGuide ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<button
|
||||
onClick={handleBack}
|
||||
@@ -94,10 +120,15 @@ export default function GuidesPane({
|
||||
>
|
||||
<ArrowLeft size={16} />
|
||||
</button>
|
||||
<span style={{ color: '#ccc', fontSize: '13px', fontWeight: 500 }}>
|
||||
<span style={{ color: '#ccc', fontSize: '13px', fontWeight: 500, display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
{selectedGuide.immutable && <Lock size={12} style={{ color: '#22c55e' }} />}
|
||||
{selectedGuide.name}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span style={{ color: '#666', fontSize: '11px' }}>
|
||||
{userGuides.length} of 10 custom guides
|
||||
</span>
|
||||
)}
|
||||
</PaneHeader>
|
||||
|
||||
@@ -188,39 +219,36 @@ export default function GuidesPane({
|
||||
No guides found
|
||||
</div>
|
||||
) : (
|
||||
guides.map((guide) => (
|
||||
<button
|
||||
key={guide.name}
|
||||
onClick={() => handleSelectGuide(guide.name)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
padding: '12px',
|
||||
background: '#161616',
|
||||
border: '1px solid #222',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
e.currentTarget.style.background = '#1a1a1a';
|
||||
e.currentTarget.style.borderColor = '#333';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.background = '#161616';
|
||||
e.currentTarget.style.borderColor = '#222';
|
||||
}}
|
||||
>
|
||||
<span style={{ color: '#ddd', fontSize: '13px', fontWeight: 500 }}>
|
||||
{guide.name}
|
||||
</span>
|
||||
<span style={{ color: '#777', fontSize: '12px', lineHeight: '1.4' }}>
|
||||
{guide.description}
|
||||
</span>
|
||||
</button>
|
||||
))
|
||||
<>
|
||||
{systemGuides.length > 0 && (
|
||||
<div style={{ color: '#555', fontSize: '11px', textTransform: 'uppercase', letterSpacing: '0.5px', padding: '4px 0' }}>
|
||||
System Guides
|
||||
</div>
|
||||
)}
|
||||
{systemGuides.map((guide) => (
|
||||
<GuideCard
|
||||
key={guide.name}
|
||||
guide={guide}
|
||||
onSelect={handleSelectGuide}
|
||||
onDelete={handleDeleteGuide}
|
||||
deleting={deleting}
|
||||
/>
|
||||
))}
|
||||
{userGuides.length > 0 && (
|
||||
<div style={{ color: '#555', fontSize: '11px', textTransform: 'uppercase', letterSpacing: '0.5px', padding: '8px 0 4px 0' }}>
|
||||
Custom Guides
|
||||
</div>
|
||||
)}
|
||||
{userGuides.map((guide) => (
|
||||
<GuideCard
|
||||
key={guide.name}
|
||||
guide={guide}
|
||||
onSelect={handleSelectGuide}
|
||||
onDelete={handleDeleteGuide}
|
||||
deleting={deleting}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -228,3 +256,75 @@ export default function GuidesPane({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function GuideCard({
|
||||
guide,
|
||||
onSelect,
|
||||
onDelete,
|
||||
deleting,
|
||||
}: {
|
||||
guide: GuideMeta;
|
||||
onSelect: (name: string) => void;
|
||||
onDelete: (name: string, e: React.MouseEvent) => void;
|
||||
deleting: string | null;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => onSelect(guide.name)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
padding: '12px',
|
||||
background: '#161616',
|
||||
border: '1px solid #222',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
e.currentTarget.style.background = '#1a1a1a';
|
||||
e.currentTarget.style.borderColor = '#333';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.background = '#161616';
|
||||
e.currentTarget.style.borderColor = '#222';
|
||||
}}
|
||||
>
|
||||
{guide.immutable && (
|
||||
<Lock size={12} style={{ color: '#22c55e', flexShrink: 0 }} />
|
||||
)}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<span style={{ color: '#ddd', fontSize: '13px', fontWeight: 500 }}>
|
||||
{guide.name}
|
||||
</span>
|
||||
<span style={{ color: '#777', fontSize: '12px', lineHeight: '1.4', display: 'block', marginTop: '2px' }}>
|
||||
{guide.description}
|
||||
</span>
|
||||
</div>
|
||||
{!guide.immutable && (
|
||||
<button
|
||||
onClick={(e) => onDelete(guide.name, e)}
|
||||
disabled={deleting === guide.name}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: '#555',
|
||||
cursor: 'pointer',
|
||||
padding: '4px',
|
||||
borderRadius: '4px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexShrink: 0,
|
||||
opacity: deleting === guide.name ? 0.3 : 1,
|
||||
}}
|
||||
onMouseEnter={e => { e.currentTarget.style.color = '#ef4444'; }}
|
||||
onMouseLeave={e => { e.currentTarget.style.color = '#555'; }}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user