@tpitre/story-ui 4.1.2 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -336,6 +336,7 @@ const MCP_API = `${API_BASE}/mcp/generate-story`;
336
336
  const MCP_STREAM_API = `${API_BASE}/mcp/generate-story-stream`;
337
337
  const PROVIDERS_API = `${API_BASE}/mcp/providers`;
338
338
  const STORIES_API = `${API_BASE}/story-ui/stories`;
339
+ const ORPHAN_STORIES_API = `${API_BASE}/story-ui/orphan-stories`;
339
340
  const CONSIDERATIONS_API = `${API_BASE}/mcp/considerations`;
340
341
 
341
342
  function isEdgeMode(): boolean {
@@ -791,6 +792,8 @@ function StoryUIPanel({ mcpPort }: StoryUIPanelProps) {
791
792
  const [contextMenuId, setContextMenuId] = useState<string | null>(null);
792
793
  const [renamingChatId, setRenamingChatId] = useState<string | null>(null);
793
794
  const [renameValue, setRenameValue] = useState('');
795
+ const [orphanCount, setOrphanCount] = useState<number>(0);
796
+ const [isDeletingOrphans, setIsDeletingOrphans] = useState<boolean>(false);
794
797
  const chatEndRef = useRef<HTMLDivElement>(null);
795
798
  const inputRef = useRef<HTMLInputElement>(null);
796
799
  const fileInputRef = useRef<HTMLInputElement>(null);
@@ -1492,6 +1495,62 @@ function StoryUIPanel({ mcpPort }: StoryUIPanelProps) {
1492
1495
  }
1493
1496
  };
1494
1497
 
1498
+ // Check for orphan stories (stories without associated chats)
1499
+ const checkOrphanStories = useCallback(async () => {
1500
+ if (!state.connectionStatus.connected) return;
1501
+ try {
1502
+ const chatFileNames = state.recentChats.map(chat => chat.fileName);
1503
+ const response = await fetch(ORPHAN_STORIES_API, {
1504
+ method: 'POST',
1505
+ headers: { 'Content-Type': 'application/json' },
1506
+ body: JSON.stringify({ chatFileNames }),
1507
+ });
1508
+ if (response.ok) {
1509
+ const data = await response.json();
1510
+ setOrphanCount(data.count || 0);
1511
+ }
1512
+ } catch (error) {
1513
+ console.error('Failed to check orphan stories:', error);
1514
+ }
1515
+ }, [state.connectionStatus.connected, state.recentChats]);
1516
+
1517
+ // Delete all orphan stories
1518
+ const handleDeleteOrphans = async () => {
1519
+ if (orphanCount === 0) return;
1520
+ if (!confirm(`Delete ${orphanCount} orphan ${orphanCount === 1 ? 'story' : 'stories'}? These are generated story files without associated chats.`)) {
1521
+ return;
1522
+ }
1523
+ setIsDeletingOrphans(true);
1524
+ try {
1525
+ const chatFileNames = state.recentChats.map(chat => chat.fileName);
1526
+ const response = await fetch(ORPHAN_STORIES_API, {
1527
+ method: 'DELETE',
1528
+ headers: { 'Content-Type': 'application/json' },
1529
+ body: JSON.stringify({ chatFileNames }),
1530
+ });
1531
+ if (response.ok) {
1532
+ const data = await response.json();
1533
+ setOrphanCount(0);
1534
+ if (data.count > 0) {
1535
+ // Show success message briefly
1536
+ alert(`Deleted ${data.count} orphan ${data.count === 1 ? 'story' : 'stories'}.`);
1537
+ }
1538
+ } else {
1539
+ alert('Failed to delete orphan stories. Please try again.');
1540
+ }
1541
+ } catch (error) {
1542
+ console.error('Failed to delete orphan stories:', error);
1543
+ alert('Failed to delete orphan stories. Please try again.');
1544
+ } finally {
1545
+ setIsDeletingOrphans(false);
1546
+ }
1547
+ };
1548
+
1549
+ // Check for orphans when chats change or connection is established
1550
+ useEffect(() => {
1551
+ checkOrphanStories();
1552
+ }, [checkOrphanStories]);
1553
+
1495
1554
  const handleStartRename = (chatId: string, currentTitle: string, e?: React.MouseEvent) => {
1496
1555
  if (e) e.stopPropagation();
1497
1556
  setContextMenuId(null);
@@ -1677,6 +1736,30 @@ function StoryUIPanel({ mcpPort }: StoryUIPanelProps) {
1677
1736
  ))}
1678
1737
  </div>
1679
1738
 
1739
+ {/* Orphan Stories Footer */}
1740
+ {orphanCount > 0 && (
1741
+ <div className="sui-orphan-footer">
1742
+ <button
1743
+ className="sui-orphan-delete-btn"
1744
+ onClick={handleDeleteOrphans}
1745
+ disabled={isDeletingOrphans}
1746
+ title={`${orphanCount} story ${orphanCount === 1 ? 'file has' : 'files have'} no associated chat`}
1747
+ >
1748
+ {isDeletingOrphans ? (
1749
+ <>
1750
+ <span className="sui-orphan-spinner" />
1751
+ <span>Deleting...</span>
1752
+ </>
1753
+ ) : (
1754
+ <>
1755
+ {Icons.trash}
1756
+ <span>{orphanCount} orphan {orphanCount === 1 ? 'story' : 'stories'}</span>
1757
+ </>
1758
+ )}
1759
+ </button>
1760
+ </div>
1761
+ )}
1762
+
1680
1763
  </div>
1681
1764
  )}
1682
1765
  {!state.sidebarOpen && (