Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
bdc772d
feat(webapp): the dashboard agent's UI
kathiekiwi Aug 7, 2026
02fa5d0
style(webapp): use the agent's own logo in the Help & Feedback entry
kathiekiwi Aug 7, 2026
07a343c
fix(webapp): drop Keep digging once the turn has already kept digging
kathiekiwi Aug 7, 2026
8b4ebaa
feat(webapp): give Cmd-I and the CLI's help link back to Ask AI
kathiekiwi Aug 8, 2026
2972f16
test(webapp): give the untrustworthy report fixture the reason field …
kathiekiwi Aug 8, 2026
e7c850d
fix(webapp): give each copyable task snippet its own SDK import
kathiekiwi Aug 8, 2026
b2116b7
fix(webapp): drop a run diagnosis action that has nowhere to go
kathiekiwi Aug 8, 2026
66135b6
fix(webapp): stop Cmd-J opening Chrome's downloads
kathiekiwi Aug 8, 2026
5cc082d
fix(webapp): keep an empty chat open instead of showing a new one
kathiekiwi Aug 8, 2026
d182a8a
docs: say where the docs went now the header button is gone
kathiekiwi Aug 8, 2026
ef38ebf
perf(webapp): keep report blocks out of the investigation winner pass
kathiekiwi Aug 8, 2026
c61eefa
fix(webapp): give icon-only tooltip buttons an accessible name
kathiekiwi Aug 8, 2026
5a2957b
fix(webapp): stop a zero-limit queue reading as at capacity
kathiekiwi Aug 8, 2026
d5caf13
fix(webapp): stop the agent's retry duplicating the failed message
kathiekiwi Aug 8, 2026
744e8a3
docs(webapp): say who Cmd-I actually opens the agent for
kathiekiwi Aug 8, 2026
03c2af7
fix(webapp): time the report sparkline's bars from the report, not th…
kathiekiwi Aug 8, 2026
3e11f1f
fix(webapp): key a repeated view block by its own position
kathiekiwi Aug 8, 2026
6fa575d
fix(webapp): send a deep-linked question once
kathiekiwi Aug 8, 2026
094daea
fix(webapp): stop the URI resolver working after the panel closes
kathiekiwi Aug 8, 2026
a97b754
fix(webapp): announce the composer's counter and name the history button
kathiekiwi Aug 8, 2026
5c8a1f2
fix(webapp): keep the hero's heading reachable when it outgrows the p…
kathiekiwi Aug 8, 2026
639abd4
fix(webapp): pin the AI-help redirect to the dashboard's own origin
kathiekiwi Aug 8, 2026
07fc7e5
fix(webapp): read the agent's page label from the left of the path
kathiekiwi Aug 8, 2026
c44c211
fix(webapp): step the composer's live region instead of reading every…
kathiekiwi Aug 8, 2026
0c9e58e
fix(webapp): key an investigation's action rows by position, not by kind
kathiekiwi Aug 8, 2026
3dd9612
fix(webapp): treat a transcript re-read that isn't a list as a failed…
kathiekiwi Aug 8, 2026
3f8bbd7
fix(webapp): stop the Ask AI host remounting the dashboard, and four …
kathiekiwi Aug 9, 2026
2481567
fix(webapp): stop the CLI's AI help link dead-ending without an AI su…
kathiekiwi Aug 9, 2026
fecb1eb
feat(dashboard-agent): move the watch data layer in from the base PR
kathiekiwi Aug 8, 2026
455ddb3
feat(webapp): tell the user later — the dashboard agent's Watch
kathiekiwi Aug 6, 2026
3002b19
fix(webapp): one watch button per answer, not two
kathiekiwi Aug 7, 2026
cb5f902
fix(webapp): stop saying error twice when naming a watched error
kathiekiwi Aug 7, 2026
b64bcfc
fix(webapp): name a watched error in full, not truncated
kathiekiwi Aug 7, 2026
f9ea65e
fix(webapp): give a watch email the same headline the chat shows
kathiekiwi Aug 7, 2026
4340b16
docs(dashboard-agent): say where each agent button appears, and on wh…
kathiekiwi Aug 7, 2026
dda12a0
fix(dashboard-agent): stop reporting a queue as missing when only its…
kathiekiwi Aug 7, 2026
9c24da0
fix(webapp): no watch offer on a paused queue, in the button or the c…
kathiekiwi Aug 7, 2026
bc8ca91
fix(webapp): a paused queue offers no backlog chips either
kathiekiwi Aug 7, 2026
ff14617
fix(dashboard-agent): say a queue is paused before reciting its metrics
kathiekiwi Aug 7, 2026
735c1fd
fix(dashboard-agent): name the state that explains the numbers, and s…
kathiekiwi Aug 7, 2026
21e006f
fix(webapp): let the agent read a queue's own row, not only its metrics
kathiekiwi Aug 7, 2026
0f036d2
test(webapp): pin every tool's API against the scope its route author…
kathiekiwi Aug 7, 2026
891b1ba
fix(dashboard-agent): mint the environment JWT with the scopes the to…
kathiekiwi Aug 7, 2026
d0e8ce7
feat(webapp): mark a chat unread when work finished behind a closed p…
kathiekiwi Aug 7, 2026
3c1a58c
feat(webapp): poll for unread work while the panel is closed
kathiekiwi Aug 7, 2026
7bf296f
fix(webapp): keep the wake poll running for a turn started behind a c…
kathiekiwi Aug 7, 2026
32da31b
fix(dashboard-agent): tell get_queue who feeds a custom queue
kathiekiwi Aug 7, 2026
d9bf19a
refactor(dashboard-agent): factor the repeated shapes out of render_v…
kathiekiwi Aug 7, 2026
3bb01be
test(webapp): regenerate the watch wording snapshot for the untruncat…
kathiekiwi Aug 7, 2026
341f3e2
test(webapp): give the watch container tests a timeout that fits boot…
kathiekiwi Aug 7, 2026
c44f173
fix(webapp): send a task queue's stored name to the watch it offers
kathiekiwi Aug 7, 2026
93e79f4
fix(webapp): claim the alert dispatch before the watch sweep fans out
kathiekiwi Aug 7, 2026
100496d
fix(webapp): normalise an error watch's fingerprint before its identity
kathiekiwi Aug 7, 2026
2e9d776
fix(webapp): let a wait-time watch end when its queue is gone or unre…
kathiekiwi Aug 7, 2026
e931aef
fix(dashboard-agent): fail a watch investigation whose card refuses t…
kathiekiwi Aug 7, 2026
0283bc9
fix(webapp): count unread chats from the list instead of subtracting …
kathiekiwi Aug 8, 2026
f00a51f
fix(webapp): drop a watch draft when the panel changes chat
kathiekiwi Aug 8, 2026
e90b989
fix(webapp): keep the delete-chat confirmation alive outside the hist…
kathiekiwi Aug 8, 2026
844e663
fix(dashboard-agent): tie a watch's investigation to the watch, not t…
kathiekiwi Aug 8, 2026
cae3b44
feat(webapp): count wake-feed requests
kathiekiwi Aug 8, 2026
8aa0b13
test(webapp): pin read:queues on both of the agent's scope allowlists
kathiekiwi Aug 8, 2026
13a2934
fix(webapp): cancel a dashboard agent turn when the user leaves the page
kathiekiwi Aug 8, 2026
6f808ae
fix(webapp): scope a turn's navigation to the page it was asked on
kathiekiwi Aug 8, 2026
d5054cd
fix(webapp): send every prompt the user clicks, never just fill the c…
kathiekiwi Aug 8, 2026
8632746
fix(dashboard-agent): read the report's untrustworthy reason under it…
kathiekiwi Aug 8, 2026
b9f8a64
fix(dashboard-agent): report the line read_file actually reached when…
kathiekiwi Aug 8, 2026
1f6a277
fix(dashboard-agent): refuse a view carrying two investigations inste…
kathiekiwi Aug 8, 2026
753f0bf
fix(dashboard-agent-db): require a role on the messages the append-on…
kathiekiwi Aug 8, 2026
a076e83
test(dashboard-agent): assert the chart validation window outside the…
kathiekiwi Aug 8, 2026
9637545
refactor(webapp): type the query scope ceiling by credential kind, no…
kathiekiwi Aug 8, 2026
fb3a97c
test(webapp): close the watch suites' agent-db pool and give the slow…
kathiekiwi Aug 8, 2026
1c0f244
docs(dashboard-agent): say that the health verbs rewrite the environm…
kathiekiwi Aug 8, 2026
015e58e
fix(dashboard-agent): read the alerts API's real response shape
kathiekiwi Aug 8, 2026
256b0d7
fix(dashboard-agent): stop a run of failed watch checks nesting forever
kathiekiwi Aug 8, 2026
964187e
fix(webapp): decide the one-watch-button rule per turn, not per rende…
kathiekiwi Aug 8, 2026
eabdec6
fix(webapp): don't re-toast a watch wake another browser already read
kathiekiwi Aug 8, 2026
bbd830f
fix(webapp): put the watch chip's tooltip back in the tab order
kathiekiwi Aug 8, 2026
711734c
fix(webapp): guard the scenario kit's app origin like its other hosts
kathiekiwi Aug 8, 2026
e2429a8
fix(dashboard-agent-db): backfill last_read_at so existing chats star…
kathiekiwi Aug 8, 2026
f5bd4ed
test(webapp): let the chat-ordering test load where the route reaches…
kathiekiwi Aug 8, 2026
1882513
test(webapp): close the watch tenancy suite's agent db pool
kathiekiwi Aug 8, 2026
d5e8768
fix(webapp): restore the soft-delete import a rebase dropped
kathiekiwi Aug 8, 2026
22ffd9d
test(webapp): match the per-turn watch rule by shape, not by line breaks
kathiekiwi Aug 8, 2026
1e94c49
fix(webapp): treat a corrupt watch-activity key as nothing known
kathiekiwi Aug 8, 2026
e61544a
fix(webapp): keep the environments a watch-alert channel already covers
kathiekiwi Aug 8, 2026
6d86199
fix(dashboard-agent): tell a queue that can't be read apart from one …
kathiekiwi Aug 8, 2026
51c9196
fix(webapp): stop the launcher dot counting the chat the user is reading
kathiekiwi Aug 8, 2026
5256672
fix(dashboard-agent): summarise what the transcript recorded about a …
kathiekiwi Aug 8, 2026
f251664
test(webapp): prove a dropped navigation stays dropped
kathiekiwi Aug 8, 2026
da093fa
test(webapp): pin the query string where it can actually change
kathiekiwi Aug 8, 2026
4122ca5
test(webapp): default SESSION_SECRET in setup, not at module scope
kathiekiwi Aug 8, 2026
a1f5c3b
fix(webapp): cap a public API key's query scope like a public access …
kathiekiwi Aug 8, 2026
394f003
fix(webapp): strip every task/ prefix from a stored queue name
kathiekiwi Aug 8, 2026
8b37c69
docs(dashboard-agent): count the watch kinds the spec union has
kathiekiwi Aug 8, 2026
4fe3334
test(webapp): give the contract test the watch block's fixture
kathiekiwi Aug 8, 2026
7648b78
chore(webapp): drop the watch scenario kit
kathiekiwi Aug 8, 2026
cb54195
docs(dashboard-agent): state what makes a watch fire, not how to repr…
kathiekiwi Aug 8, 2026
ea9e48f
fix(webapp,dashboard-agent-db): settle read state, the dot and unavai…
kathiekiwi Aug 9, 2026
0e534d5
feat(webapp): let the agent's environment JWT read a queue's own row
kathiekiwi Aug 9, 2026
90f1a05
fix(webapp,dashboard-agent): settle the work count on the chat on scr…
kathiekiwi Aug 9, 2026
004f088
style(webapp): drop an import the chat no longer uses
kathiekiwi Aug 9, 2026
e169088
feat(webapp): add the agent storybook gallery
kathiekiwi Aug 5, 2026
7452153
chore(webapp): drop the gallery screenshot script
kathiekiwi Aug 5, 2026
70983c1
fix(webapp): point the filtered-runs demo intent at the runs collection
kathiekiwi Aug 6, 2026
91be174
fix(webapp): pin the dismissed prompt in the gallery and drop the dea…
kathiekiwi Aug 6, 2026
5227f73
fix(webapp): repair the agent gallery's fixtures after the rebase, an…
kathiekiwi Aug 8, 2026
868d64d
test(webapp): let the demo isolation check see side-effect and dynami…
kathiekiwi Aug 8, 2026
f820ea1
fix(webapp): name only demo resources in the gallery's fixtures
kathiekiwi Aug 8, 2026
ec43fb7
test(webapp): keep the gallery's fixtures out of the demo barrel
kathiekiwi Aug 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/quiet-hounds-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/core": patch
---

The current-worker API now reports each task's queue, so you can see which tasks write to a given queue.
6 changes: 5 additions & 1 deletion .server-changes/dashboard-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ Meet the dashboard agent: a chat in every environment that answers questions abo

**Investigate** on a failed run, an error, a backed-up queue or a run that hasn't started gets you a worked-through answer — what happened, why, and how to fix it, with every claim linked to the runs, errors and deploys behind it.

**Watch…** on a run, queue, error or the health report tells you when things change: a run finishes, a queue clears or grows past a number you pick, an error comes back, an environment recovers. The answer arrives in the chat and, if you want, by email, Slack or webhook — and the agent can look into bad news on its own. A watch reaches you on any browser you sign in from, without opening the chat first.

The agent works on preview and dev branches, with that branch's own data.

The health report reads the same everywhere — dashboard, terminal, editor. A very long chat keeps working: the agent summarises the earlier part and carries on. The agent's replies no longer show images.

A sample of conversations is scored automatically so the agent keeps getting better. Only the score and a one-line summary are kept, never your messages, data or code, and we can switch it off for your organization on request.

Separately: a queue's wait times, peak depth, throughput and throttling can now be read from the API, and the Docs button has been removed from page headers.
The Docs button is gone from page headers — ask the agent instead, or open Documentation from Help & Feedback.

Separately: a queue's wait times, peak depth, throughput and throttling can now be read from the API.
78 changes: 31 additions & 47 deletions apps/webapp/app/components/AskAI.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @deprecated Superseded by the dashboard agent (`components/dashboard-agent`). Nothing mounts
* this any more — every Ask AI entry point now opens Ask Trigger. Kept until the agent has
* shipped, then removed along with `@kapaai/react-sdk` and `KAPA_AI_WEBSITE_ID`.
* Mostly superseded by the dashboard agent (`components/dashboard-agent`), which owns every
* entry point except two: ⌘I and the CLI's `?aiHelp=` link still open Ask AI. `AskAIRoot` is
* mounted by the `_app` layout for those; the `AskAI` button below is mounted nowhere.
*/

import {
Expand All @@ -17,13 +17,17 @@ import { useSearchParams } from "@remix-run/react";
import DOMPurify from "dompurify";
import { motion } from "framer-motion";
import { marked } from "marked";
import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
import { useTypedRouteLoaderData } from "remix-typedjson";
import { useCallback, useEffect, useRef, useState } from "react";
import { AISparkleIcon } from "~/assets/icons/AISparkleIcon";
import { SparkleListIcon } from "~/assets/icons/SparkleListIcon";
import { useFeatures } from "~/hooks/useFeatures";
import { useAskAiAvailability } from "~/hooks/useAskAiAvailability";
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
import { type loader } from "~/root";
import {
ASK_AI_DEEP_LINK_PARAM,
ASK_AI_SHORTCUT,
askAiCanOpen,
} from "./dashboard-agent/ask-ai-channels";
import { useAskAiHost } from "./dashboard-agent/askAiOpenRequest";
import { Button } from "./primitives/Buttons";
import { Callout } from "./primitives/Callout";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./primitives/Dialog";
Expand All @@ -40,11 +44,6 @@ import {
} from "./primitives/Tooltip";
import { ClientOnly } from "remix-utils/client-only";

function useKapaWebsiteId() {
const routeMatch = useTypedRouteLoaderData<typeof loader>("root");
return routeMatch?.kapa.websiteId;
}

/** Open/close state for the Ask AI dialog, including the `?aiHelp=` deep-link handling. */
function useAskAIState() {
const [isOpen, setIsOpen] = useState(false);
Expand All @@ -67,14 +66,14 @@ function useAskAIState() {

// Handle URL param functionality
useEffect(() => {
const aiHelp = searchParams.get("aiHelp");
const aiHelp = searchParams.get(ASK_AI_DEEP_LINK_PARAM);
if (aiHelp) {
// Delay to avoid hCaptcha bot detection
window.setTimeout(() => openAskAI(aiHelp), 1000);

// Clone instead of mutating in place
const next = new URLSearchParams(searchParams);
next.delete("aiHelp");
next.delete(ASK_AI_DEEP_LINK_PARAM);
setSearchParams(next);
}
}, [searchParams, openAskAI]);
Expand All @@ -83,45 +82,30 @@ function useAskAIState() {
}

/**
* Hosts Ask AI (Kapa provider, ⌘I shortcut, dialog) for a menu that renders its own trigger. Wrap
* it around the popover, not inside, so the dialog and shortcut survive the popover closing.
* `children` receives the open function, or undefined when Ask AI is unavailable (self-hosted, no
* Kapa website id, or SSR).
*
* @deprecated See the note at the top of this file.
* Hosts Ask AI (Kapa provider, ⌘I shortcut, dialog). It renders no page content and wraps
* nothing: entry points reach it through `requestAskAi`, so the Kapa provider mounting after
* hydration can never remount the app around it.
*/
export function AskAIRoot({
children,
}: {
children: (openAskAI: (() => void) | undefined) => ReactNode;
}) {
const { isManagedCloud } = useFeatures();
const websiteId = useKapaWebsiteId();
export function AskAIRoot() {
const availability = useAskAiAvailability();

if (!isManagedCloud || !websiteId) {
return <>{children(undefined)}</>;
if (!askAiCanOpen(availability)) {
return null;
}

return (
<ClientOnly fallback={<>{children(undefined)}</>}>
{() => <AskAIRootProvider websiteId={websiteId}>{children}</AskAIRootProvider>}
</ClientOnly>
);
const websiteId = availability.kapaWebsiteId!;

return <ClientOnly>{() => <AskAIRootProvider websiteId={websiteId} />}</ClientOnly>;
}

function AskAIRootProvider({
websiteId,
children,
}: {
websiteId: string;
children: (openAskAI: () => void) => ReactNode;
}) {
function AskAIRootProvider({ websiteId }: { websiteId: string }) {
const { isOpen, setIsOpen, initialQuery, openAskAI, closeAskAI } = useAskAIState();

useShortcutKeys({
shortcut: { modifiers: ["mod"], key: "i", enabledOnInputElements: true },
shortcut: ASK_AI_SHORTCUT,
action: () => openAskAI(),
});
useAskAiHost(openAskAI);

return (
<KapaProvider
Expand All @@ -134,7 +118,6 @@ function AskAIRootProvider({
}}
botProtectionMechanism="hcaptcha"
>
{children(() => openAskAI())}
<AskAIDialog
initialQuery={initialQuery}
isOpen={isOpen}
Expand All @@ -145,15 +128,16 @@ function AskAIRootProvider({
);
}

/** @deprecated See the note at the top of this file. */
/** @deprecated Mounted nowhere: the sidebar's AI entry point is the dashboard agent. */
export function AskAI({ isCollapsed = false }: { isCollapsed?: boolean }) {
const { isManagedCloud } = useFeatures();
const websiteId = useKapaWebsiteId();
const availability = useAskAiAvailability();

if (!isManagedCloud || !websiteId) {
if (!askAiCanOpen(availability)) {
return null;
}

const websiteId = availability.kapaWebsiteId!;

return (
<ClientOnly
fallback={
Expand Down
105 changes: 58 additions & 47 deletions apps/webapp/app/components/BlankStatePanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
v3NewProjectAlertPath,
v3NewSchedulePath,
} from "~/utils/pathBuilder";
import { AskAI } from "./AskAI";
import { AskAgentButton } from "./dashboard-agent/AskAgentButton";
import { CodeBlock } from "./code/CodeBlock";
import { InlineCode } from "./code/InlineCode";
import { environmentFullTitle, EnvironmentIcon } from "./environments/EnvironmentLabel";
Expand Down Expand Up @@ -62,6 +62,55 @@ import {
import { StepContentContainer } from "./StepContentContainer";
import { V4Badge } from "./V4Badge";

/**
* What the agent is asked when it's opened from a deployment setup panel. The panel is the docs
* answer; the agent is for the part the docs can't answer — this project, this environment.
*/
const ASK_AGENT_DEPLOY_PROMPT =
"I'm trying to deploy my tasks to this environment. Walk me through it and tell me if anything about this project or environment is going to get in the way.";

/** The docs links the deployment panels offer to anyone without the agent. */
function DeployDocsLinks() {
return (
<>
<SimpleTooltip
asChild
tabbable
button={
// Span wrapper: LinkButton drops the pointer-event props Radix injects via asChild, so
// the tooltip trigger has to be a plain element (same pattern as FavoritePageButton).
<span className="flex">
<LinkButton
variant="small-menu-item"
LeadingIcon={BookOpenIcon}
leadingIconClassName="text-blue-500"
to={docsPath("deployment/overview")}
aria-label="Deploy docs"
/>
</span>
}
content="Deploy docs"
/>
<SimpleTooltip
asChild
tabbable
button={
<span className="flex">
<LinkButton
variant="small-menu-item"
LeadingIcon={QuestionMarkCircleIcon}
leadingIconClassName="text-blue-500"
to={docsPath("troubleshooting#deployment")}
aria-label="Troubleshooting docs"
/>
</span>
}
content="Troubleshooting docs"
/>
</>
);
}

export function HasNoTasksDev() {
return (
<PackageManagerProvider>
Expand Down Expand Up @@ -270,29 +319,10 @@ export function DeploymentsNoneDev() {
<Header1>Deploy your tasks</Header1>
</div>
<div className="flex items-center">
<SimpleTooltip
button={
<LinkButton
variant="small-menu-item"
LeadingIcon={BookOpenIcon}
leadingIconClassName="text-blue-500"
to={docsPath("deployment/overview")}
/>
}
content="Deploy docs"
/>
<SimpleTooltip
button={
<LinkButton
variant="small-menu-item"
LeadingIcon={QuestionMarkCircleIcon}
leadingIconClassName="text-blue-500"
to={docsPath("troubleshooting#deployment")}
/>
}
content="Troubleshooting docs"
/>
<AskAI />
{/* One entry point instead of two: the docs links were a guess at which page you
needed, and the agent can look at this project and answer for it. Someone with no
agent still gets the links. */}
<AskAgentButton prompt={ASK_AGENT_DEPLOY_PROMPT} fallback={<DeployDocsLinks />} />
</div>
</div>
<StepNumber stepNumber="→" title="Switch to a deployed environment" />
Expand Down Expand Up @@ -658,29 +688,10 @@ function DeploymentOnboardingSteps() {
</Header1>
</div>
<div className="flex items-center">
<SimpleTooltip
button={
<LinkButton
variant="small-menu-item"
LeadingIcon={BookOpenIcon}
leadingIconClassName="text-blue-500"
to={docsPath("deployment/overview")}
/>
}
content="Deploy docs"
/>
<SimpleTooltip
button={
<LinkButton
variant="small-menu-item"
LeadingIcon={QuestionMarkCircleIcon}
leadingIconClassName="text-blue-500"
to={docsPath("troubleshooting#deployment")}
/>
}
content="Troubleshooting docs"
/>
<AskAI />
{/* One entry point instead of two: the docs links were a guess at which page you
needed, and the agent can look at this project and answer for it. Someone with no
agent still gets the links. */}
<AskAgentButton prompt={ASK_AGENT_DEPLOY_PROMPT} fallback={<DeployDocsLinks />} />
</div>
</div>
<ClientTabs defaultValue="github">
Expand Down
54 changes: 50 additions & 4 deletions apps/webapp/app/components/Shortcuts.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { KeyboardIcon } from "~/assets/icons/KeyboardIcon";
import { useState } from "react";
import { ASK_AGENT_LABEL } from "~/components/dashboard-agent/agent-identity";
import { type AiShortcutRow, aiShortcutRows } from "~/components/dashboard-agent/ai-entry-points";
import { ASK_AI_SHORTCUT, askAiCanOpen } from "~/components/dashboard-agent/ask-ai-channels";
import { useDashboardAgentAvailable } from "~/components/dashboard-agent/dashboardAgentOpenRequest";
import { NEW_CHAT_SHORTCUT } from "~/components/dashboard-agent/DashboardAgentHeader";
import { TOGGLE_PANEL_SHORTCUT } from "~/components/dashboard-agent/dashboardAgentLauncher";
import { useAskAiAvailability } from "~/hooks/useAskAiAvailability";
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
import { Header3 } from "./primitives/Headers";
import { SideMenuItemButton } from "./navigation/SideMenuItem";
Expand Down Expand Up @@ -41,6 +48,11 @@ export function ShortcutsAutoOpen() {
}

function ShortcutContent() {
const agent = useDashboardAgentAvailable();
const askAi = askAiCanOpen(useAskAiAvailability());
const rows = aiShortcutRows({ agent, askAi });
const shows = (row: AiShortcutRow) => rows.includes(row);

return (
<SheetContent>
<SheetHeader>
Expand All @@ -62,10 +74,27 @@ function ShortcutContent() {
<ShortcutKey shortcut={{ modifiers: ["mod"] }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "enter" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Ask AI">
<ShortcutKey shortcut={{ modifiers: ["mod"] }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "i" }} variant="medium/bright" />
</Shortcut>
{shows("agent-toggle") && (
<Shortcut name={ASK_AGENT_LABEL}>
<ShortcutKey
shortcut={{ modifiers: TOGGLE_PANEL_SHORTCUT.modifiers }}
variant="medium/bright"
/>
<ShortcutKey
shortcut={{ key: TOGGLE_PANEL_SHORTCUT.key }}
variant="medium/bright"
/>
</Shortcut>
)}
{shows("ask-ai") && (
<Shortcut name="Ask AI">
<ShortcutKey
shortcut={{ modifiers: ASK_AI_SHORTCUT.modifiers }}
variant="medium/bright"
/>
<ShortcutKey shortcut={{ key: ASK_AI_SHORTCUT.key }} variant="medium/bright" />
</Shortcut>
)}
<Shortcut name="Filter">
<ShortcutKey shortcut={{ key: "f" }} variant="medium/bright" />
</Shortcut>
Expand Down Expand Up @@ -94,6 +123,23 @@ function ShortcutContent() {
<ShortcutKey shortcut={{ key: "h" }} variant="medium/bright" />
</Shortcut>
</div>
{shows("agent-new-chat") && (
<div className="space-y-3">
<Header3>Chat</Header3>
<Shortcut name="New chat">
<ShortcutKey
shortcut={{ modifiers: NEW_CHAT_SHORTCUT.modifiers }}
variant="medium/bright"
/>
<ShortcutKey shortcut={{ key: NEW_CHAT_SHORTCUT.key }} variant="medium/bright" />
</Shortcut>
{shows("agent-close-chat") && (
<Shortcut name="Close chat">
<ShortcutKey shortcut={{ key: "esc" }} variant="medium/bright" />
</Shortcut>
)}
</div>
)}
<div className="space-y-3">
<Header3>Runs page</Header3>
<Shortcut name="Bulk action: Cancel runs">
Expand Down
Loading
Loading