mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-16 10:50:38 +09:00
feat(inbox): add operator search and keyboard controls
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
36376968af
commit
3ab7d52f00
25 changed files with 1340 additions and 114 deletions
|
|
@ -1,10 +1,4 @@
|
|||
import type {
|
||||
Approval,
|
||||
DashboardSummary,
|
||||
HeartbeatRun,
|
||||
Issue,
|
||||
JoinRequest,
|
||||
} from "@paperclipai/shared";
|
||||
import type { Approval, DashboardSummary, HeartbeatRun, Issue, JoinRequest } from "@paperclipai/shared";
|
||||
|
||||
export const RECENT_ISSUES_LIMIT = 100;
|
||||
export const FAILED_RUN_STATUSES = new Set(["failed", "timed_out"]);
|
||||
|
|
@ -12,8 +6,12 @@ export const ACTIONABLE_APPROVAL_STATUSES = new Set(["pending", "revision_reques
|
|||
export const DISMISSED_KEY = "paperclip:inbox:dismissed";
|
||||
export const READ_ITEMS_KEY = "paperclip:inbox:read-items";
|
||||
export const INBOX_LAST_TAB_KEY = "paperclip:inbox:last-tab";
|
||||
export const INBOX_ISSUE_COLUMNS_KEY = "paperclip:inbox:issue-columns";
|
||||
export type InboxTab = "mine" | "recent" | "unread" | "all";
|
||||
export type InboxApprovalFilter = "all" | "actionable" | "resolved";
|
||||
export const inboxIssueColumns = ["status", "id", "assignee", "project", "workspace", "labels", "updated"] as const;
|
||||
export type InboxIssueColumn = (typeof inboxIssueColumns)[number];
|
||||
export const DEFAULT_INBOX_ISSUE_COLUMNS: InboxIssueColumn[] = ["status", "id", "updated"];
|
||||
export type InboxWorkItem =
|
||||
| {
|
||||
kind: "issue";
|
||||
|
|
@ -79,6 +77,80 @@ export function saveReadInboxItems(ids: Set<string>) {
|
|||
}
|
||||
}
|
||||
|
||||
export function normalizeInboxIssueColumns(columns: Iterable<string | InboxIssueColumn>): InboxIssueColumn[] {
|
||||
const selected = new Set(columns);
|
||||
return inboxIssueColumns.filter((column) => selected.has(column));
|
||||
}
|
||||
|
||||
export function getAvailableInboxIssueColumns(enableWorkspaceColumn: boolean): InboxIssueColumn[] {
|
||||
if (enableWorkspaceColumn) return [...inboxIssueColumns];
|
||||
return inboxIssueColumns.filter((column) => column !== "workspace");
|
||||
}
|
||||
|
||||
export function loadInboxIssueColumns(): InboxIssueColumn[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(INBOX_ISSUE_COLUMNS_KEY);
|
||||
if (raw === null) return DEFAULT_INBOX_ISSUE_COLUMNS;
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!Array.isArray(parsed)) return DEFAULT_INBOX_ISSUE_COLUMNS;
|
||||
return normalizeInboxIssueColumns(parsed);
|
||||
} catch {
|
||||
return DEFAULT_INBOX_ISSUE_COLUMNS;
|
||||
}
|
||||
}
|
||||
|
||||
export function saveInboxIssueColumns(columns: InboxIssueColumn[]) {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
INBOX_ISSUE_COLUMNS_KEY,
|
||||
JSON.stringify(normalizeInboxIssueColumns(columns)),
|
||||
);
|
||||
} catch {
|
||||
// Ignore localStorage failures.
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveIssueWorkspaceName(
|
||||
issue: Pick<Issue, "executionWorkspaceId" | "projectId" | "projectWorkspaceId">,
|
||||
{
|
||||
executionWorkspaceById,
|
||||
projectWorkspaceById,
|
||||
defaultProjectWorkspaceIdByProjectId,
|
||||
}: {
|
||||
executionWorkspaceById?: ReadonlyMap<string, {
|
||||
name: string;
|
||||
mode: "shared_workspace" | "isolated_workspace" | "operator_branch" | "adapter_managed" | "cloud_sandbox";
|
||||
projectWorkspaceId: string | null;
|
||||
}>;
|
||||
projectWorkspaceById?: ReadonlyMap<string, { name: string }>;
|
||||
defaultProjectWorkspaceIdByProjectId?: ReadonlyMap<string, string>;
|
||||
},
|
||||
): string | null {
|
||||
const defaultProjectWorkspaceId = issue.projectId
|
||||
? defaultProjectWorkspaceIdByProjectId?.get(issue.projectId) ?? null
|
||||
: null;
|
||||
|
||||
if (issue.executionWorkspaceId) {
|
||||
const executionWorkspace = executionWorkspaceById?.get(issue.executionWorkspaceId) ?? null;
|
||||
const linkedProjectWorkspaceId =
|
||||
executionWorkspace?.projectWorkspaceId ?? issue.projectWorkspaceId ?? null;
|
||||
const isDefaultSharedExecutionWorkspace =
|
||||
executionWorkspace?.mode === "shared_workspace" && linkedProjectWorkspaceId === defaultProjectWorkspaceId;
|
||||
if (isDefaultSharedExecutionWorkspace) return null;
|
||||
|
||||
const workspaceName = executionWorkspace?.name;
|
||||
if (workspaceName) return workspaceName;
|
||||
}
|
||||
|
||||
if (issue.projectWorkspaceId) {
|
||||
if (issue.projectWorkspaceId === defaultProjectWorkspaceId) return null;
|
||||
const workspaceName = projectWorkspaceById?.get(issue.projectWorkspaceId)?.name;
|
||||
if (workspaceName) return workspaceName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function loadLastInboxTab(): InboxTab {
|
||||
try {
|
||||
const raw = localStorage.getItem(INBOX_LAST_TAB_KEY);
|
||||
|
|
@ -145,6 +217,9 @@ export function normalizeTimestamp(value: string | Date | null | undefined): num
|
|||
}
|
||||
|
||||
export function issueLastActivityTimestamp(issue: Issue): number {
|
||||
const lastActivityAt = normalizeTimestamp(issue.lastActivityAt);
|
||||
if (lastActivityAt > 0) return lastActivityAt;
|
||||
|
||||
const lastExternalCommentAt = normalizeTimestamp(issue.lastExternalCommentAt);
|
||||
if (lastExternalCommentAt > 0) return lastExternalCommentAt;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue