Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion apps/web/src/components/settings/ProjectSettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AsyncResult } from "effect/unstable/reactivity";
import { type EnvironmentId, type ProjectIconOverride } from "@t3tools/contracts";
import { useLocation, useNavigate } from "@tanstack/react-router";
import * as Cause from "effect/Cause";
import { InfoIcon, Trash2Icon } from "lucide-react";
import { DownloadIcon, InfoIcon, Trash2Icon } from "lucide-react";
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";

import { useComposerDraftStore } from "../../composerDraftStore";
Expand All @@ -20,6 +20,7 @@ import {
type SidebarProjectGroupMember,
type SidebarProjectSnapshot,
} from "../../sidebarProjectGrouping";
import { agentSessionImport } from "../../state/agentSessions";
import { useEnvironments, usePrimaryEnvironmentId } from "../../state/environments";
import { useThreadShells } from "../../state/entities";
import { projectEnvironment } from "../../state/projects";
Expand Down Expand Up @@ -178,6 +179,7 @@ function ProjectDetail({
const threads = useThreadShells();
const updateProject = useAtomCommand(projectEnvironment.update, { reportFailure: false });
const deleteProject = useAtomCommand(projectEnvironment.delete, { reportFailure: false });
const importSessions = useAtomCommand(agentSessionImport, { reportFailure: false });
const projectNameEditedRef = useRef(false);

const faviconPath = representative.faviconPath ?? null;
Expand Down Expand Up @@ -273,6 +275,52 @@ function ProjectDetail({
[group.memberProjects, updateAllMembers],
);

// Onboarding imports in bulk; this reruns the same idempotent import for a
// project that already exists, e.g. to pick up sessions started in a terminal.
const [isImportingSessions, setIsImportingSessions] = useState(false);
const importMemberSessions = async () => {
setIsImportingSessions(true);
let importedCount = 0;
let skippedCount = 0;
let succeededCount = 0;
try {
// One offline checkout must not block the others.
for (const member of group.memberProjects) {
const result = await importSessions({
environmentId: member.environmentId,
input: { projectId: member.id, expectedWorkspaceRoot: member.workspaceRoot },
});
if (result._tag === "Failure") {
reportFailure(
group.memberProjects.length > 1
? `Failed to import sessions on ${member.environmentLabel ?? "the current environment"}`
: "Failed to import sessions",
mapAtomCommandResult(result, () => undefined),
);
continue;
}
succeededCount += 1;
importedCount += result.value.importedCount;
skippedCount += result.value.skippedCount;
}
} finally {
setIsImportingSessions(false);
}
if (succeededCount === 0) return;
toastManager.add({
type: skippedCount > 0 ? "warning" : "success",
title: skippedCount > 0 ? "Some sessions could not be imported" : "Sessions imported",
description: [
`${importedCount} recent ${importedCount === 1 ? "session is" : "sessions are"} in this project.`,
...(skippedCount > 0
? [
`${skippedCount} ${skippedCount === 1 ? "session" : "sessions"} could not be imported.`,
]
: []),
].join(" "),
});
};

// ----- project icon -----
const [faviconPickerOpen, setFaviconPickerOpen] = useState(false);
const [iconPickerOpen, setIconPickerOpen] = useState(false);
Expand Down Expand Up @@ -488,6 +536,22 @@ function ProjectDetail({
</div>
}
/>
<SettingsRow
title="Import sessions"
description="Adds Claude Code and Codex conversations from the last 30 days that ran in this project's folder. Already imported conversations are skipped."
control={
<Button
size="sm"
variant="outline"
type="button"
disabled={isImportingSessions}
onClick={() => void importMemberSessions()}
>
<DownloadIcon />
{isImportingSessions ? "Importing…" : "Import"}
</Button>
}
/>
</SettingsSection>
<ProjectDefaultsSettings category="project" />
<ProjectActionsSettings />
Expand Down
6 changes: 5 additions & 1 deletion docs/user/project-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,15 @@ access. Source Control contains automatic pull, the default pull request merge m
generation. The same rows edit environment defaults or project overrides depending on the
project crumb.

The Project category, shown while a project is selected, holds the project's name, icon, actions,
The Project category, shown while a project is selected, holds the project's name, icon, session import, actions,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
checkouts and removal. Actions belong to a project: editing them creates the project's own list
on each selected environment, and reset returns to the environment's shared list. A project's
`t3.json` actions can be imported there.

To bring in Claude Code and Codex conversations that ran in the project's folders during the last
30 days, select **Import** in the **Import sessions** row. Run it again to pick up newer
conversations; ones already imported are skipped.

Settings a repository can also declare in `t3.json`, such as the workspace for new threads,
resolve in one order: a project override, then the environment setting, then `t3.json`, then the
built-in default. Leave a setting on **Inherit** to let the next tier decide.
Expand Down
3 changes: 3 additions & 0 deletions docs/user/welcome-wizard.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,8 @@ with up to 100,000 input records. Run import again to continue a large batch.
Completed conversations are not imported again. You can continue without the
remaining history.

To import conversations after setup, such as ones you started in a terminal, pick the
project in Settings and select **Import** in the **Import sessions** row on its **Project** page.

You can continue without configuring agents or importing projects, or return to an earlier step
using the setup progress bar. Navigation pauses while an import is running.
Loading