From 186b634c18e816366533b818ad2d93ef0bc7e97e Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:57:35 +0200 Subject: [PATCH 01/10] feat: Show the Conversation Pull Request in the Sidebar List Add a batch lookup route and a sidebar row mark: the state icon with the CI dot, opening the same card beside the row on hover. The row keeps the conversation's own title. --- .../__test-utils__/convos-route-mocks.js | 1 + api/server/routes/convos.js | 14 +- .../src/components/Chat/PullRequest/Chip.tsx | 47 +--- .../src/components/Chat/PullRequest/CiDot.tsx | 25 +++ .../src/components/Chat/PullRequest/Panel.tsx | 58 +++++ .../components/Chat/PullRequest/RowMark.tsx | 71 ++++++ .../PullRequest/__tests__/RowMark.test.tsx | 212 ++++++++++++++++++ .../src/components/Chat/PullRequest/index.ts | 1 + client/src/components/Conversations/Convo.tsx | 24 +- .../__tests__/Convo.pullRequest.spec.tsx | 139 ++++++++++++ .../data-provider/PullRequest/batch.test.ts | 108 +++++++++ client/src/data-provider/PullRequest/batch.ts | 88 ++++++++ client/src/data-provider/PullRequest/index.ts | 1 + .../src/data-provider/PullRequest/queries.ts | 36 ++- librechat.example.yaml | 2 + packages/api/src/pulls/handler.spec.ts | 202 ++++++++++++++++- packages/api/src/pulls/handler.ts | 179 +++++++++++++-- packages/data-provider/src/api-endpoints.ts | 2 + packages/data-provider/src/config.spec.ts | 5 + packages/data-provider/src/config.ts | 3 + packages/data-provider/src/data-service.ts | 6 + .../data-provider/src/types/pullRequest.ts | 14 ++ .../src/methods/conversation.spec.ts | 62 +++++ .../data-schemas/src/methods/conversation.ts | 34 +++ 24 files changed, 1263 insertions(+), 71 deletions(-) create mode 100644 client/src/components/Chat/PullRequest/CiDot.tsx create mode 100644 client/src/components/Chat/PullRequest/Panel.tsx create mode 100644 client/src/components/Chat/PullRequest/RowMark.tsx create mode 100644 client/src/components/Chat/PullRequest/__tests__/RowMark.test.tsx create mode 100644 client/src/components/Conversations/__tests__/Convo.pullRequest.spec.tsx create mode 100644 client/src/data-provider/PullRequest/batch.test.ts create mode 100644 client/src/data-provider/PullRequest/batch.ts diff --git a/api/server/routes/__test-utils__/convos-route-mocks.js b/api/server/routes/__test-utils__/convos-route-mocks.js index ce6a630d933..6a98fb6e6fb 100644 --- a/api/server/routes/__test-utils__/convos-route-mocks.js +++ b/api/server/routes/__test-utils__/convos-route-mocks.js @@ -149,6 +149,7 @@ module.exports = { createBackgroundTaskPolicyMiddleware: jest.fn(() => (_req, _res, next) => next()), createGitHubPullRequestSource: jest.fn(() => ({ find: jest.fn() })), createPullRequestLookup: jest.fn(() => jest.fn()), + createConversationPullRequestsHandler: jest.fn(() => jest.fn()), createConversationPullRequestHandler: jest.fn( () => (_req, res) => res.status(200).json({ pullRequest: null }), ), diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 4c05abb57f6..d9a083e296a 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -22,6 +22,7 @@ const { createConversationPullRequestHandler, createGitHubPullRequestSource, createPullRequestLookup, + createConversationPullRequestsHandler, backgroundTaskRegistry, createSubagentThreadViewHandler, createGeneratedTitleHandler, @@ -170,10 +171,20 @@ const backgroundTaskIndexHandler = createBackgroundTaskIndexHandler({ const backgroundTaskCancelHandler = createBackgroundTaskCancelHandler({ registry: backgroundTaskRegistry, }); +/** One lookup, so the header's single route and the sidebar's batch route share its cache. */ +const pullRequestLookup = createPullRequestLookup({ + source: createGitHubPullRequestSource({ fetchFn: fetch }), +}); const conversationPullRequestHandler = createConversationPullRequestHandler({ getConvoLaneGit: db.getConvoLaneGit, getAppConfig, - lookup: createPullRequestLookup({ source: createGitHubPullRequestSource({ fetchFn: fetch }) }), + lookup: pullRequestLookup, + env: process.env, +}); +const conversationPullRequestsHandler = createConversationPullRequestsHandler({ + getConvosLaneGit: db.getConvosLaneGit, + getAppConfig, + lookup: pullRequestLookup, env: process.env, }); router.use(requireJwtAuth); @@ -248,6 +259,7 @@ router.post( subagentControlHandler, ); router.get('/:parentConversationId/subagents', parentSubagentIndexHandler); +router.post('/pull-requests', conversationPullRequestsHandler); router.get('/:conversationId/pull-request', conversationPullRequestHandler); router.get('/:conversationId/background-tasks', backgroundTaskPolicy, backgroundTaskIndexHandler); router.post( diff --git a/client/src/components/Chat/PullRequest/Chip.tsx b/client/src/components/Chat/PullRequest/Chip.tsx index 18c3bde4c15..fbd5763748c 100644 --- a/client/src/components/Chat/PullRequest/Chip.tsx +++ b/client/src/components/Chat/PullRequest/Chip.tsx @@ -5,31 +5,10 @@ import { useAgentsMapContext, useChatContext } from '~/Providers'; import { TONE_DOT_CLASS, presentPullRequest } from './status'; import { URLIcon } from '~/components/Endpoints/URLIcon'; import { summarizePullRequest } from './summary'; +import PullRequestPanel from './Panel'; import { useLocalize } from '~/hooks'; import PullRequestIcon from './Icon'; -import PullRequestCard from './Card'; -import { cn } from '~/utils'; - -/** Slides in from the chip's side, left to right, and fades; reduced motion only fades. */ -const cardClass = cn( - 'border-border-light bg-surface-secondary text-text-primary z-[200] w-80 max-w-[calc(100vw-2rem)] rounded-xl border shadow-lg focus:outline-none', - 'origin-left -translate-x-3 opacity-0 transition duration-200 ease-out', - 'data-[enter]:translate-x-0 data-[enter]:opacity-100', - 'data-[leave]:-translate-x-3 data-[leave]:opacity-0', - 'motion-reduce:translate-x-0 motion-reduce:transition-opacity', -); - -function CiDot({ dotClass }: { dotClass: string }) { - return ( - - ); -} +import CiDot from './CiDot'; /** The agent's picture with the CI dot on its corner. */ function AgentAvatar({ @@ -72,7 +51,6 @@ function PullRequestChip({ conversationId }: { conversationId: string }) { hideTimeout: 150, }); const open = Ariakit.useStoreState(hovercard, 'open'); - const cardElement = Ariakit.useStoreState(hovercard, 'contentElement'); const { data, isError, refetch } = useConversationPullRequestQuery(conversationId); const pullRequest = data?.pullRequest; @@ -108,21 +86,12 @@ function PullRequestChip({ conversationId }: { conversationId: string }) { } /> - - void refetch()} - portalElement={cardElement} - /> - + void refetch()} + /> ); } diff --git a/client/src/components/Chat/PullRequest/CiDot.tsx b/client/src/components/Chat/PullRequest/CiDot.tsx new file mode 100644 index 00000000000..0e1e522c376 --- /dev/null +++ b/client/src/components/Chat/PullRequest/CiDot.tsx @@ -0,0 +1,25 @@ +import { cn } from '~/utils'; + +/** + * The CI status dot that sits on the corner of the picture or icon it describes. The ring is + * painted in the surface the dot sits on, so the caller names that surface. + */ +export default function CiDot({ + dotClass, + ringClassName = 'ring-presentation', +}: { + dotClass: string; + ringClassName?: string; +}) { + return ( +