mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 04:07:44 +02:00
[eric] share: chats copy their answer and export as .swarm from the card menu and dock tiles
This commit is contained in:
@@ -26,6 +26,7 @@ import { ackRun, runWorkflowNow } from '@/shared/state/workflowsSlice';
|
||||
import { setPendingBrowserUrl } from '@/shared/state/tempStateSlice';
|
||||
import { fetchOutputs } from '@/shared/state/outputsSlice';
|
||||
import UpdateReadyPill from '@/app/components/Layout/UpdateReadyPill';
|
||||
import ShareRequestHost from '@/app/components/share/ShareRequestHost';
|
||||
import { findBrowserByWebContentsId } from '@/shared/browserRegistry';
|
||||
import { byPreviewRecency } from '@/shared/previewOrder';
|
||||
import { useClaudeTokens, useThemeAccent, useThemeWash } from '@/shared/styles/ThemeContext';
|
||||
@@ -574,6 +575,8 @@ const AppShell: React.FC = () => {
|
||||
<Settings />
|
||||
</React.Suspense>
|
||||
|
||||
<ShareRequestHost />
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ShareModal from './ShareModal';
|
||||
import type { ShareTarget } from './shareTypes';
|
||||
|
||||
export const SHARE_REQUEST_EVENT = 'openswarm:share-entity';
|
||||
|
||||
export function requestShare(target: ShareTarget): void {
|
||||
window.dispatchEvent(new CustomEvent(SHARE_REQUEST_EVENT, { detail: target }));
|
||||
}
|
||||
|
||||
/** One global mount that turns share requests from stateless menu rows (card context menu, dock
|
||||
* tiles) into the ShareModal; the row can't own modal state because the menu unmounts on click. */
|
||||
const ShareRequestHost: React.FC = () => {
|
||||
const [target, setTarget] = useState<ShareTarget | null>(null);
|
||||
useEffect(() => {
|
||||
const onShare = (e: Event): void => {
|
||||
const detail = (e as CustomEvent).detail as ShareTarget | undefined;
|
||||
if (detail && detail.kind && detail.id) setTarget(detail);
|
||||
};
|
||||
window.addEventListener(SHARE_REQUEST_EVENT, onShare);
|
||||
return () => window.removeEventListener(SHARE_REQUEST_EVENT, onShare);
|
||||
}, []);
|
||||
if (!target) return null;
|
||||
return <ShareModal target={target} open onClose={() => setTarget(null)} />;
|
||||
};
|
||||
|
||||
export default ShareRequestHost;
|
||||
@@ -1,6 +1,6 @@
|
||||
// Shared types for the .swarm share/import UI. The *Response shapes mirror the backend pydantic models in backend/apps/swarm/models.py; keep them in sync.
|
||||
|
||||
export type ShareKind = 'skill' | 'app' | 'workflow' | 'dashboard';
|
||||
export type ShareKind = 'skill' | 'app' | 'workflow' | 'dashboard' | 'session';
|
||||
|
||||
export interface ShareTarget {
|
||||
kind: ShareKind;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { clearSessionMessages, deleteSession, duplicateSession, expandSession, collapseSession, stopAgent, fetchSession, type AgentSession } from '@/shared/state/agentsSlice';
|
||||
import { placeCard, bringToFront } from '@/shared/state/dashboardLayoutSlice';
|
||||
import { setClipboardCards } from '@/shared/dashboardClipboard';
|
||||
import { copySessionResponse } from '@/shared/copySessionResponse';
|
||||
import { requestShare } from '@/app/components/share/ShareRequestHost';
|
||||
import { displaySessionName } from '@/shared/state/sessionDisplay';
|
||||
import { handleSlashCommand } from '@/app/pages/AgentChat/ChatInput/hooks/slashCommands';
|
||||
import type { AppDispatch } from '@/shared/state/store';
|
||||
import type { CardMenuRow } from '../desktop/openCardContextMenu';
|
||||
@@ -46,6 +49,8 @@ export function agentCardMenuRows({ session, dispatch, expanded, tileZone, expan
|
||||
x: card.x, y: card.y, width: card.width, height: card.height, expanded,
|
||||
}]),
|
||||
},
|
||||
{ label: 'Copy response', onClick: () => { copySessionResponse(session.id); } },
|
||||
{ label: 'Share as .swarm…', onClick: () => requestShare({ kind: 'session', id: session.id, name: displaySessionName(session.name) }) },
|
||||
{ kind: 'separator' },
|
||||
{ kind: 'header', label: 'Session' },
|
||||
{ label: 'Stop turn', disabled: !running, onClick: () => { void dispatch(stopAgent({ sessionId: session.id })); } },
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { bringToFront, recordClosedCard, removeCard, removeWorkflowCard } from '@/shared/state/dashboardLayoutSlice';
|
||||
import { closeSession } from '@/shared/state/agentsSlice';
|
||||
import { copySessionResponse } from '@/shared/copySessionResponse';
|
||||
import { requestShare } from '@/app/components/share/ShareRequestHost';
|
||||
import { removeBrowserCardCleanly } from '@/shared/browserTeardown';
|
||||
import { removeViewCardCleanly } from '@/shared/viewTeardown';
|
||||
import type { AppDispatch } from '@/shared/state/store';
|
||||
@@ -10,6 +12,10 @@ export function dockTileMenuRows(entry: DockEntry, dispatch: AppDispatch, onFocu
|
||||
return [
|
||||
{ label: 'Show on canvas', onClick: onFocus },
|
||||
{ label: 'Bring to front', onClick: () => dispatch(bringToFront({ id: entry.id, type: entry.kind })) },
|
||||
...(entry.kind === 'agent' ? [
|
||||
{ label: 'Copy response', onClick: () => { copySessionResponse(entry.id); } } as CardMenuRow,
|
||||
{ label: 'Share as .swarm…', onClick: () => requestShare({ kind: 'session', id: entry.id, name: entry.label }) } as CardMenuRow,
|
||||
] : []),
|
||||
{ kind: 'separator' },
|
||||
{
|
||||
label: 'Close',
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { store } from '@/shared/state/store';
|
||||
|
||||
/** Copy the chat's latest assistant answer to the clipboard; true when something was copied.
|
||||
* Menu rows (card context menu, dock tiles) call this statelessly. */
|
||||
export function copySessionResponse(sessionId: string): boolean {
|
||||
const session = store.getState().agents.sessions[sessionId];
|
||||
const last = [...(session?.messages ?? [])]
|
||||
.reverse()
|
||||
.find((m) => m.role === 'assistant' && typeof m.content === 'string' && m.content.trim());
|
||||
if (!last) return false;
|
||||
void navigator.clipboard.writeText(last.content as string);
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user