Import sesssions (#9474)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -608,9 +608,32 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
||||
[intl]
|
||||
);
|
||||
|
||||
const handleImportClick = useCallback(() => {
|
||||
const handleImportClick = useCallback(async () => {
|
||||
const native = window.electron?.selectImportSessionFile;
|
||||
if (typeof native === 'function') {
|
||||
try {
|
||||
const result = await native();
|
||||
if (!result) return;
|
||||
if (result.error) {
|
||||
toast.error(intl.formatMessage(i18n.importFailed, { error: result.error }));
|
||||
return;
|
||||
}
|
||||
await importSession({
|
||||
body: { json: result.contents },
|
||||
throwOnError: true,
|
||||
});
|
||||
toast.success(intl.formatMessage(i18n.importSuccess));
|
||||
await loadSessions();
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
intl.formatMessage(i18n.importFailed, { error: errorMessage(error, 'Unknown error') })
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Fallback for non-Electron contexts (tests, web build).
|
||||
fileInputRef.current?.click();
|
||||
}, []);
|
||||
}, [intl, loadSessions]);
|
||||
|
||||
const handleImportNostrLink = useCallback(async () => {
|
||||
const deeplink = nostrImportLink.trim();
|
||||
@@ -1081,7 +1104,7 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".json"
|
||||
accept=".json,.jsonl,application/json,application/x-ndjson"
|
||||
onChange={handleImportSession}
|
||||
className="hidden"
|
||||
/>
|
||||
|
||||
@@ -1879,6 +1879,33 @@ ipcMain.handle('select-file-or-directory', async (_event, defaultPath?: string)
|
||||
return null;
|
||||
});
|
||||
|
||||
// Native picker tailored for session imports: shows hidden files (so users can
|
||||
// reach `~/.claude/projects/...` or `~/.pi/agent/sessions/...`), filters for
|
||||
// .json/.jsonl, and returns the file's contents inline so the renderer doesn't
|
||||
// need a separate read step.
|
||||
ipcMain.handle('select-import-session-file', async () => {
|
||||
const result = (await dialog.showOpenDialog({
|
||||
title: 'Import session',
|
||||
defaultPath: os.homedir(),
|
||||
properties: ['openFile', 'showHiddenFiles'],
|
||||
filters: [
|
||||
{ name: 'Session files', extensions: ['json', 'jsonl'] },
|
||||
{ name: 'All files', extensions: ['*'] },
|
||||
],
|
||||
})) as unknown as OpenDialogReturnValue;
|
||||
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const filePath = result.filePaths[0];
|
||||
try {
|
||||
const contents = await fs.readFile(filePath, 'utf8');
|
||||
return { filePath, contents };
|
||||
} catch (err) {
|
||||
return { filePath, contents: '', error: errorMessage(err) };
|
||||
}
|
||||
});
|
||||
|
||||
// ── Mesh-LLM lifecycle (see mesh.ts) ────────────────────────────────
|
||||
|
||||
ipcMain.handle('check-mesh', () => mesh.check());
|
||||
|
||||
@@ -126,6 +126,11 @@ type ElectronAPI = {
|
||||
startMesh: (args: string[]) => Promise<{ started: boolean; error?: string; pid?: number }>;
|
||||
stopMesh: () => Promise<{ stopped: boolean }>;
|
||||
selectFileOrDirectory: (defaultPath?: string) => Promise<string | null>;
|
||||
selectImportSessionFile: () => Promise<{
|
||||
filePath: string;
|
||||
contents: string;
|
||||
error?: string;
|
||||
} | null>;
|
||||
getBinaryPath: (binaryName: string) => Promise<string>;
|
||||
readFile: (directory: string) => Promise<FileResponse>;
|
||||
writeFile: (directory: string, content: string) => Promise<boolean>;
|
||||
@@ -223,6 +228,7 @@ const electronAPI: ElectronAPI = {
|
||||
|
||||
selectFileOrDirectory: (defaultPath?: string) =>
|
||||
ipcRenderer.invoke('select-file-or-directory', defaultPath),
|
||||
selectImportSessionFile: () => ipcRenderer.invoke('select-import-session-file'),
|
||||
getBinaryPath: (binaryName: string) => ipcRenderer.invoke('get-binary-path', binaryName),
|
||||
readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath),
|
||||
writeFile: (filePath: string, content: string) =>
|
||||
|
||||
Reference in New Issue
Block a user