@agent-native/core 0.178.0 → 0.178.1-nightly-20260909221943
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/clips/.agents/skills/dictate/SKILL.md +1 -0
- package/corpus/templates/clips/DESIGN.md +10 -7
- package/corpus/templates/clips/actions/list-organization-state.ts +56 -3
- package/corpus/templates/clips/actions/list-recordings.ts +2 -0
- package/corpus/templates/clips/actions/search-dictations.ts +81 -0
- package/corpus/templates/clips/app/components/agent-destination-logos.tsx +50 -0
- package/corpus/templates/clips/app/components/bug-report/bug-report-dialog.tsx +60 -0
- package/corpus/templates/clips/app/components/bug-report/bug-report-form.tsx +206 -0
- package/corpus/templates/clips/app/components/clips-command-menu.tsx +653 -0
- package/corpus/templates/clips/app/components/library/empty-state.tsx +52 -17
- package/corpus/templates/clips/app/components/library/folder-card.tsx +34 -0
- package/corpus/templates/clips/app/components/library/folder-tree.tsx +109 -64
- package/corpus/templates/clips/app/components/library/library-action-hrefs.ts +27 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +295 -103
- package/corpus/templates/clips/app/components/library/library-layout.tsx +381 -203
- package/corpus/templates/clips/app/components/library/library-primary-actions.tsx +48 -0
- package/corpus/templates/clips/app/components/library/page-header.tsx +48 -8
- package/corpus/templates/clips/app/components/library/recording-card.tsx +58 -55
- package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -19
- package/corpus/templates/clips/app/components/library/sidebar-feedback-button.tsx +48 -0
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +2 -2
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +344 -260
- package/corpus/templates/clips/app/components/player/viewer-controls.tsx +2 -2
- package/corpus/templates/clips/app/components/recorder/camera-visualizer.tsx +0 -33
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -1
- package/corpus/templates/clips/app/components/ui/tabs.tsx +1 -1
- package/corpus/templates/clips/app/hooks/use-command-search.ts +53 -0
- package/corpus/templates/clips/app/hooks/use-navigation-state.ts +32 -13
- package/corpus/templates/clips/app/hooks/use-upload-video-picker.tsx +1 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
- package/corpus/templates/clips/app/lib/agent-share.ts +16 -0
- package/corpus/templates/clips/app/lib/command-events.ts +7 -0
- package/corpus/templates/clips/app/root.tsx +6 -82
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +25 -5
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +2 -30
- package/corpus/templates/clips/app/routes/_app.library.folder.$folderId.tsx +13 -2
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +4 -17
- package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +111 -114
- package/corpus/templates/clips/app/routes/_app.spaces.$spaceId.folder.$folderId.tsx +25 -2
- package/corpus/templates/clips/app/routes/_app.spaces.$spaceId.tsx +14 -61
- package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +34 -24
- package/corpus/templates/clips/app/routes/_app.trash.tsx +1 -1
- package/corpus/templates/clips/app/routes/_app.tsx +2 -4
- package/corpus/templates/clips/app/routes/bug-report.tsx +11 -189
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +148 -123
- package/corpus/templates/clips/changelog/2026-09-08-comments-and-desktop-window-controls.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-08-global-command-search.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-09-agent-sidebar-fits-below-toolbar.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-09-desktop-switches-stay-clear-in-dark-mode.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-09-recording-agent-uses-global-panel.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +2 -2
- package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +5 -3
- package/corpus/templates/clips/desktop/src/components/SourceRow.tsx +1 -0
- package/corpus/templates/clips/desktop/src/components/Switch.tsx +2 -5
- package/corpus/templates/clips/desktop/src/components/ui/switch.tsx +21 -3
- package/corpus/templates/clips/desktop/src/hooks/useMediaDevices.ts +72 -52
- package/corpus/templates/clips/desktop/src/styles.css +18 -19
- package/corpus/templates/clips/desktop/src/tailwind.css +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +18 -10
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +3 -5
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +14 -3
- package/corpus/templates/design/AGENTS.md +2 -1
- package/corpus/templates/design/actions/export-png.ts +119 -0
- package/corpus/templates/design/actions/take-design-screenshot.ts +47 -13
- package/corpus/templates/design/server/plugins/agent-chat.ts +3 -1
- package/corpus/templates/factory/actions/dispatch-factory-item.ts +51 -22
- package/corpus/templates/factory/actions/list-triage-items.ts +21 -11
- package/corpus/templates/factory/actions/poll-slack-channel.ts +7 -2
- package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-keeps-claimed-markers-after-poll.md +6 -0
- package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-preserves-started-claimed-threads.md +6 -0
- package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-skips-claimed-parents.md +6 -0
- package/corpus/templates/factory/server/lib/factory-automation-config.ts +1 -1
- package/corpus/templates/factory/server/lib/slack-feedback-prompt.ts +47 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +5 -2
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +5 -4
- package/corpus/templates/factory/server/triage/review-skill-alignment.ts +12 -7
- package/corpus/templates/factory/server/triage/slack-review-window.ts +39 -0
- package/dist/client/CommandMenu.js +13 -1
- package/dist/client/analytics.js +10 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/vite/client.js +25 -7
- package/dist/vite/sentry-source-maps.d.ts +12 -0
- package/dist/vite/sentry-source-maps.js +105 -0
- package/package.json +2 -1
package/corpus/README.md
CHANGED
|
@@ -40,6 +40,7 @@ Dictate captures **mic only** — system audio is never recorded for dictations.
|
|
|
40
40
|
| Action | What it does |
|
|
41
41
|
| -------------------- | ------------------------------------------------------------------------------------------- |
|
|
42
42
|
| `list-dictations` | Past dictations, scoped via `accessFilter` |
|
|
43
|
+
| `search-dictations` | Search native or cleaned dictation text, with matching snippets |
|
|
43
44
|
| `cleanup-dictation` | Polish a single dictation's text (writes `cleanedText`) |
|
|
44
45
|
| `cleanup-transcript` | Shared cleanup pipeline (also used by Clips + Meetings); resolves credentials per the order below |
|
|
45
46
|
|
|
@@ -54,10 +54,11 @@ transcript, agent, insights, and settings.
|
|
|
54
54
|
|
|
55
55
|
## Progressive disclosure
|
|
56
56
|
|
|
57
|
-
The viewer presents jobs, not inventories.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
The viewer presents jobs, not inventories. Human sharing and agent continuity
|
|
58
|
+
are separate jobs: Share owns invitations, durable access policy, password,
|
|
59
|
+
expiry, social destinations, and embed publishing; a quiet adjacent Send to
|
|
60
|
+
agent action owns ephemeral handoff to an agent destination. Secondary sharing
|
|
61
|
+
destinations replace the Share body as focused views. The
|
|
61
62
|
overflow leads with recording cleanup actions, while maintenance and
|
|
62
63
|
document-generation commands live in named submenus. A generic AI-tools
|
|
63
64
|
launcher does not compete with Share. The editor opens in transcript mode and
|
|
@@ -65,9 +66,11 @@ reveals the precision timeline as a peer mode instead of stacking both
|
|
|
65
66
|
workspaces under the player.
|
|
66
67
|
|
|
67
68
|
Share is the viewer toolbar's sole labeled primary action and the product-led
|
|
68
|
-
growth entry point.
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
growth entry point. Send to agent is an accessible secondary icon action that
|
|
70
|
+
opens one compact handoff menu; it must not become a second sharing/settings dialog. Copy
|
|
71
|
+
link stays inside Share, never as a competing toolbar button. Edit, download,
|
|
72
|
+
and overflow use equal compact icon controls with accessible names and tooltips;
|
|
73
|
+
their visual weight must not rival Share.
|
|
71
74
|
|
|
72
75
|
Viewer identity uses one avatar grammar everywhere. People use profile images or
|
|
73
76
|
initials; agents use the same circular avatar shape with the assistant mark.
|
|
@@ -16,10 +16,24 @@ import {
|
|
|
16
16
|
} from "@agent-native/core/org";
|
|
17
17
|
import { isEmailDerivedName } from "@agent-native/core/user-profile";
|
|
18
18
|
import { getUserProfiles } from "@agent-native/core/user-profile/server";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
and,
|
|
21
|
+
asc,
|
|
22
|
+
desc,
|
|
23
|
+
eq,
|
|
24
|
+
isNotNull,
|
|
25
|
+
isNull,
|
|
26
|
+
notInArray,
|
|
27
|
+
or,
|
|
28
|
+
sql,
|
|
29
|
+
} from "drizzle-orm";
|
|
20
30
|
import { z } from "zod";
|
|
21
31
|
|
|
22
32
|
import { getDb, schema } from "../server/db/index.js";
|
|
33
|
+
import {
|
|
34
|
+
agentRecordingAccessFilter,
|
|
35
|
+
isAgentRecordingCaller,
|
|
36
|
+
} from "../server/lib/agent-recording-access.js";
|
|
23
37
|
import {
|
|
24
38
|
getCurrentOwnerEmail,
|
|
25
39
|
ownerEmailMatches,
|
|
@@ -38,7 +52,7 @@ export default defineAction({
|
|
|
38
52
|
),
|
|
39
53
|
}),
|
|
40
54
|
http: { method: "GET" },
|
|
41
|
-
run: async (args) => {
|
|
55
|
+
run: async (args, ctx) => {
|
|
42
56
|
const db = getDb();
|
|
43
57
|
const ownerEmail = getCurrentOwnerEmail();
|
|
44
58
|
|
|
@@ -127,7 +141,12 @@ export default defineAction({
|
|
|
127
141
|
createdAt: Number(i.createdAt),
|
|
128
142
|
}));
|
|
129
143
|
|
|
130
|
-
const
|
|
144
|
+
const resolvedDb = await Promise.resolve(db);
|
|
145
|
+
const meetingRecordingIds = resolvedDb
|
|
146
|
+
.select({ id: schema.meetings.recordingId })
|
|
147
|
+
.from(schema.meetings)
|
|
148
|
+
.where(isNotNull(schema.meetings.recordingId));
|
|
149
|
+
const [spaces, folders, folderRecordingCountRows] = await Promise.all([
|
|
131
150
|
db
|
|
132
151
|
.select()
|
|
133
152
|
.from(schema.spaces)
|
|
@@ -146,7 +165,39 @@ export default defineAction({
|
|
|
146
165
|
),
|
|
147
166
|
)
|
|
148
167
|
.orderBy(asc(schema.folders.position)),
|
|
168
|
+
resolvedDb
|
|
169
|
+
.select({
|
|
170
|
+
folderId: schema.recordings.folderId,
|
|
171
|
+
recordingCount: sql<number>`COUNT(1)`,
|
|
172
|
+
})
|
|
173
|
+
.from(schema.recordings)
|
|
174
|
+
.where(
|
|
175
|
+
and(
|
|
176
|
+
agentRecordingAccessFilter(
|
|
177
|
+
schema.recordings,
|
|
178
|
+
schema.recordingShares,
|
|
179
|
+
schema.recordingViewers,
|
|
180
|
+
{
|
|
181
|
+
agentOnly: isAgentRecordingCaller(ctx?.caller),
|
|
182
|
+
userEmail: ctx?.userEmail,
|
|
183
|
+
},
|
|
184
|
+
),
|
|
185
|
+
eq(schema.recordings.organizationId, organizationId),
|
|
186
|
+
isNotNull(schema.recordings.folderId),
|
|
187
|
+
isNull(schema.recordings.archivedAt),
|
|
188
|
+
isNull(schema.recordings.trashedAt),
|
|
189
|
+
notInArray(schema.recordings.id, meetingRecordingIds),
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
.groupBy(schema.recordings.folderId),
|
|
149
193
|
]);
|
|
194
|
+
const recordingCountByFolder = new Map(
|
|
195
|
+
folderRecordingCountRows.flatMap((row) =>
|
|
196
|
+
row.folderId
|
|
197
|
+
? [[row.folderId, Number(row.recordingCount ?? 0)] as const]
|
|
198
|
+
: [],
|
|
199
|
+
),
|
|
200
|
+
);
|
|
150
201
|
|
|
151
202
|
return {
|
|
152
203
|
currentUserEmail: ownerEmail,
|
|
@@ -173,6 +224,7 @@ export default defineAction({
|
|
|
173
224
|
spaceId: f.spaceId,
|
|
174
225
|
ownerEmail: f.ownerEmail,
|
|
175
226
|
position: f.position,
|
|
227
|
+
recordingCount: recordingCountByFolder.get(f.id) ?? 0,
|
|
176
228
|
})),
|
|
177
229
|
personalFolders: folders
|
|
178
230
|
.filter((f) => f.spaceId === null)
|
|
@@ -180,6 +232,7 @@ export default defineAction({
|
|
|
180
232
|
id: f.id,
|
|
181
233
|
name: f.name,
|
|
182
234
|
parentId: f.parentId,
|
|
235
|
+
recordingCount: recordingCountByFolder.get(f.id) ?? 0,
|
|
183
236
|
})),
|
|
184
237
|
invitations,
|
|
185
238
|
};
|
|
@@ -228,6 +228,8 @@ export default defineAction({
|
|
|
228
228
|
if (args.view === "library" || args.view === "space") {
|
|
229
229
|
if (args.folderId !== undefined && args.folderId !== null) {
|
|
230
230
|
whereClauses.push(eq(schema.recordings.folderId, args.folderId));
|
|
231
|
+
} else {
|
|
232
|
+
whereClauses.push(isNull(schema.recordings.folderId));
|
|
231
233
|
}
|
|
232
234
|
}
|
|
233
235
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Search dictation history by its native or cleaned transcript text. */
|
|
2
|
+
|
|
3
|
+
import { defineAction } from "@agent-native/core/action";
|
|
4
|
+
import { buildDeepLink } from "@agent-native/core/server";
|
|
5
|
+
import { accessFilter } from "@agent-native/core/sharing";
|
|
6
|
+
import { and, desc, sql } from "drizzle-orm";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
10
|
+
import { buildCaseInsensitiveSearchPattern } from "./search-recordings-utils.js";
|
|
11
|
+
|
|
12
|
+
const SNIPPET_RADIUS = 80;
|
|
13
|
+
|
|
14
|
+
function buildSnippet(text: string, query: string): string | null {
|
|
15
|
+
const index = text.toLowerCase().indexOf(query.toLowerCase());
|
|
16
|
+
if (index === -1) return null;
|
|
17
|
+
const start = Math.max(0, index - SNIPPET_RADIUS);
|
|
18
|
+
const end = Math.min(text.length, index + query.length + SNIPPET_RADIUS);
|
|
19
|
+
return `${start > 0 ? "…" : ""}${text.slice(start, end).replace(/\s+/g, " ").trim()}${end < text.length ? "…" : ""}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default defineAction({
|
|
23
|
+
description:
|
|
24
|
+
"Search dictations by their native or cleaned transcript text. Results are scoped to dictations the current user can access and include a short matching snippet.",
|
|
25
|
+
schema: z.object({
|
|
26
|
+
query: z.string().min(1).describe("Search text"),
|
|
27
|
+
limit: z.coerce.number().int().min(1).max(100).default(30),
|
|
28
|
+
}),
|
|
29
|
+
http: { method: "GET" },
|
|
30
|
+
run: async (args) => {
|
|
31
|
+
const db = getDb();
|
|
32
|
+
const pattern = buildCaseInsensitiveSearchPattern(args.query);
|
|
33
|
+
const rows = await db
|
|
34
|
+
.select({
|
|
35
|
+
id: schema.dictations.id,
|
|
36
|
+
fullText: schema.dictations.fullText,
|
|
37
|
+
cleanedText: schema.dictations.cleanedText,
|
|
38
|
+
durationMs: schema.dictations.durationMs,
|
|
39
|
+
source: schema.dictations.source,
|
|
40
|
+
targetApp: schema.dictations.targetApp,
|
|
41
|
+
startedAt: schema.dictations.startedAt,
|
|
42
|
+
createdAt: schema.dictations.createdAt,
|
|
43
|
+
})
|
|
44
|
+
.from(schema.dictations)
|
|
45
|
+
.where(
|
|
46
|
+
and(
|
|
47
|
+
accessFilter(schema.dictations, schema.dictationShares),
|
|
48
|
+
sql`(lower(${schema.dictations.fullText}) LIKE ${pattern} ESCAPE '\\' OR lower(coalesce(${schema.dictations.cleanedText}, '')) LIKE ${pattern} ESCAPE '\\')`,
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
.orderBy(desc(schema.dictations.startedAt))
|
|
52
|
+
.limit(args.limit);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
query: args.query,
|
|
56
|
+
dictations: rows.map((dictation) => ({
|
|
57
|
+
...dictation,
|
|
58
|
+
snippet:
|
|
59
|
+
buildSnippet(dictation.cleanedText ?? "", args.query) ??
|
|
60
|
+
buildSnippet(dictation.fullText, args.query),
|
|
61
|
+
})),
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
link: ({ result }) => {
|
|
65
|
+
if (!result || typeof result !== "object") return null;
|
|
66
|
+
const dictations = (result as { dictations?: unknown }).dictations;
|
|
67
|
+
if (!Array.isArray(dictations) || dictations.length === 0) return null;
|
|
68
|
+
const first = dictations[0] as { id?: string };
|
|
69
|
+
if (!first.id) return null;
|
|
70
|
+
return {
|
|
71
|
+
url: buildDeepLink({
|
|
72
|
+
app: "clips",
|
|
73
|
+
view: "dictate",
|
|
74
|
+
params: { dictationId: first.id },
|
|
75
|
+
to: "/dictate",
|
|
76
|
+
}),
|
|
77
|
+
label: "Open Dictate in Clips",
|
|
78
|
+
view: "dictate",
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { IconBrandOpenai } from "@tabler/icons-react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
interface AgentDestinationLogoProps {
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ClaudeLogo({ className }: AgentDestinationLogoProps) {
|
|
10
|
+
return (
|
|
11
|
+
<svg
|
|
12
|
+
aria-hidden="true"
|
|
13
|
+
className={cn("shrink-0", className)}
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
viewBox="0 0 100 100"
|
|
16
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
17
|
+
>
|
|
18
|
+
<path d="m19.6 66.5 19.7-11 .3-1-.3-.5h-1l-3.3-.2-11.2-.3L14 53l-9.5-.5-2.4-.5L0 49l.2-1.5 2-1.3 2.9.2 6.3.5 9.5.6 6.9.4L38 49.1h1.6l.2-.7-.5-.4-.4-.4L29 41l-10.6-7-5.6-4.1-3-2-1.5-2-.6-4.2 2.7-3 3.7.3.9.2 3.7 2.9 8 6.1L37 36l1.5 1.2.6-.4.1-.3-.7-1.1L33 25l-6-10.4-2.7-4.3-.7-2.6c-.3-1-.4-2-.4-3l3-4.2L28 0l4.2.6L33.8 2l2.6 6 4.1 9.3L47 29.9l2 3.8 1 3.4.3 1h.7v-.5l.5-7.2 1-8.7 1-11.2.3-3.2 1.6-3.8 3-2L61 2.6l2 2.9-.3 1.8-1.1 7.7L59 27.1l-1.5 8.2h.9l1-1.1 4.1-5.4 6.9-8.6 3-3.5L77 13l2.3-1.8h4.3l3.1 4.7-1.4 4.9-4.4 5.6-3.7 4.7-5.3 7.1-3.2 5.7.3.4h.7l12-2.6 6.4-1.1 7.6-1.3 3.5 1.6.4 1.6-1.4 3.4-8.2 2-9.6 2-14.3 3.3-.2.1.2.3 6.4.6 2.8.2h6.8l12.6 1 3.3 2 1.9 2.7-.3 2-5.1 2.6-6.8-1.6-16-3.8-5.4-1.3h-.8v.4l4.6 4.5 8.3 7.5L89 80.1l.5 2.4-1.3 2-1.4-.2-9.2-7-3.6-3-8-6.8h-.5v.7l1.8 2.7 9.8 14.7.5 4.5-.7 1.4-2.6 1-2.7-.6-5.8-8-6-9-4.7-8.2-.5.4-2.9 30.2-1.3 1.5-3 1.2-2.5-2-1.4-3 1.4-6.2 1.6-8 1.3-6.4 1.2-7.9.7-2.6v-.2H49L43 72l-9 12.3-7.2 7.6-1.7.7-3-1.5.3-2.8L24 86l10-12.8 6-7.9 4-4.6-.1-.5h-.3L17.2 77.4l-4.7.6-2-2 .2-3 1-1 8-5.5Z" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ClaudeCodeLogo({ className }: AgentDestinationLogoProps) {
|
|
24
|
+
return (
|
|
25
|
+
<svg
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
className={cn("shrink-0", className)}
|
|
28
|
+
fill="none"
|
|
29
|
+
viewBox="0 0 20 20"
|
|
30
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
31
|
+
>
|
|
32
|
+
<g
|
|
33
|
+
stroke="currentColor"
|
|
34
|
+
strokeLinecap="round"
|
|
35
|
+
strokeLinejoin="round"
|
|
36
|
+
strokeWidth="1.2"
|
|
37
|
+
>
|
|
38
|
+
<path d="m13.75 13.5 3.5-3.5-3.5-3.5" />
|
|
39
|
+
<path d="M9 16 11 4" />
|
|
40
|
+
<path d="m6.25 13.5-3.5-3.5 3.5-3.5" />
|
|
41
|
+
</g>
|
|
42
|
+
</svg>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function CodexLogo({ className }: AgentDestinationLogoProps) {
|
|
47
|
+
return (
|
|
48
|
+
<IconBrandOpenai aria-hidden="true" className={cn("shrink-0", className)} />
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import type { BugReportContext } from "@shared/bug-report";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
import { BugReportForm } from "@/components/bug-report/bug-report-form";
|
|
6
|
+
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
|
7
|
+
import { OPEN_BUG_REPORT_EVENT } from "@/lib/command-events";
|
|
8
|
+
|
|
9
|
+
function currentBugReportContext(): BugReportContext {
|
|
10
|
+
return {
|
|
11
|
+
projectId: null,
|
|
12
|
+
title: null,
|
|
13
|
+
description: null,
|
|
14
|
+
severity: "normal",
|
|
15
|
+
sourceUrl: window.location.href,
|
|
16
|
+
pageTitle: document.title || null,
|
|
17
|
+
appVersion: null,
|
|
18
|
+
environment: null,
|
|
19
|
+
reporterEmail: null,
|
|
20
|
+
reporterName: null,
|
|
21
|
+
reporterId: null,
|
|
22
|
+
metadata: null,
|
|
23
|
+
returnUrl: window.location.href,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function BugReportDialog() {
|
|
28
|
+
const t = useT();
|
|
29
|
+
const [open, setOpen] = useState(false);
|
|
30
|
+
const [initialContext, setInitialContext] = useState<BugReportContext | null>(
|
|
31
|
+
null,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const handleOpen = () => {
|
|
36
|
+
setInitialContext(currentBugReportContext());
|
|
37
|
+
setOpen(true);
|
|
38
|
+
};
|
|
39
|
+
window.addEventListener(OPEN_BUG_REPORT_EVENT, handleOpen);
|
|
40
|
+
return () => window.removeEventListener(OPEN_BUG_REPORT_EVENT, handleOpen);
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
45
|
+
<DialogContent className="w-[calc(100%-2rem)] max-w-2xl gap-0 p-0">
|
|
46
|
+
<DialogTitle className="sr-only">
|
|
47
|
+
{t("bugReportRoute.title")}
|
|
48
|
+
</DialogTitle>
|
|
49
|
+
{open ? (
|
|
50
|
+
<div className="max-h-[min(760px,calc(100vh-2rem))] overflow-y-auto p-5 sm:p-6">
|
|
51
|
+
<BugReportForm
|
|
52
|
+
initialContext={initialContext}
|
|
53
|
+
onRecordingStarted={() => setOpen(false)}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
) : null}
|
|
57
|
+
</DialogContent>
|
|
58
|
+
</Dialog>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { appBasePath } from "@agent-native/core/client/api-path";
|
|
2
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import {
|
|
4
|
+
bugReportContextToSearchParams,
|
|
5
|
+
type BugReportContext,
|
|
6
|
+
type BugReportSeverity,
|
|
7
|
+
} from "@shared/bug-report";
|
|
8
|
+
import { IconBug, IconShieldCheck } from "@tabler/icons-react";
|
|
9
|
+
import { useEffect, useState } from "react";
|
|
10
|
+
|
|
11
|
+
import { Button } from "@/components/ui/button";
|
|
12
|
+
import { Input } from "@/components/ui/input";
|
|
13
|
+
import { Label } from "@/components/ui/label";
|
|
14
|
+
import {
|
|
15
|
+
Select,
|
|
16
|
+
SelectContent,
|
|
17
|
+
SelectItem,
|
|
18
|
+
SelectTrigger,
|
|
19
|
+
SelectValue,
|
|
20
|
+
} from "@/components/ui/select";
|
|
21
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
22
|
+
import { cn } from "@/lib/utils";
|
|
23
|
+
|
|
24
|
+
interface BugReportFormProps {
|
|
25
|
+
className?: string;
|
|
26
|
+
initialContext?: BugReportContext | null;
|
|
27
|
+
onRecorderOpened?: (recorderWindow: Window | null) => void;
|
|
28
|
+
onRecordingStarted?: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function openRecorder(url: string): Window | null {
|
|
32
|
+
const opened = window.open(
|
|
33
|
+
url,
|
|
34
|
+
"agent-native-clips-bug-report",
|
|
35
|
+
"popup,width=1120,height=820",
|
|
36
|
+
);
|
|
37
|
+
if (!opened) {
|
|
38
|
+
window.location.href = url;
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
opened.focus();
|
|
42
|
+
return opened;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function BugReportForm({
|
|
46
|
+
className,
|
|
47
|
+
initialContext,
|
|
48
|
+
onRecorderOpened,
|
|
49
|
+
onRecordingStarted,
|
|
50
|
+
}: BugReportFormProps) {
|
|
51
|
+
const t = useT();
|
|
52
|
+
const [title, setTitle] = useState(initialContext?.title ?? "");
|
|
53
|
+
const [description, setDescription] = useState(
|
|
54
|
+
initialContext?.description ?? "",
|
|
55
|
+
);
|
|
56
|
+
const [reporterEmail, setReporterEmail] = useState(
|
|
57
|
+
initialContext?.reporterEmail ?? "",
|
|
58
|
+
);
|
|
59
|
+
const [severity, setSeverity] = useState<BugReportSeverity>(
|
|
60
|
+
initialContext?.severity ?? "normal",
|
|
61
|
+
);
|
|
62
|
+
const [referrer, setReferrer] = useState<string | null>(null);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
setReferrer(document.referrer || null);
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
const sourceUrl = initialContext?.sourceUrl ?? referrer;
|
|
69
|
+
const pageTitle = initialContext?.pageTitle ?? null;
|
|
70
|
+
const sourceLabel =
|
|
71
|
+
pageTitle || sourceUrl || t("bugReportRoute.sourceUnknown");
|
|
72
|
+
|
|
73
|
+
const startRecording = () => {
|
|
74
|
+
const context: BugReportContext = {
|
|
75
|
+
projectId: initialContext?.projectId ?? null,
|
|
76
|
+
title: title.trim() || initialContext?.title || null,
|
|
77
|
+
description: description.trim() || initialContext?.description || null,
|
|
78
|
+
severity,
|
|
79
|
+
sourceUrl,
|
|
80
|
+
pageTitle,
|
|
81
|
+
appVersion: initialContext?.appVersion ?? null,
|
|
82
|
+
environment: initialContext?.environment ?? null,
|
|
83
|
+
reporterEmail:
|
|
84
|
+
reporterEmail.trim() || initialContext?.reporterEmail || null,
|
|
85
|
+
reporterName: initialContext?.reporterName ?? null,
|
|
86
|
+
reporterId: initialContext?.reporterId ?? null,
|
|
87
|
+
metadata: initialContext?.metadata ?? null,
|
|
88
|
+
returnUrl: initialContext?.returnUrl ?? sourceUrl,
|
|
89
|
+
};
|
|
90
|
+
const params = bugReportContextToSearchParams(context);
|
|
91
|
+
params.set("intent", "bug-report");
|
|
92
|
+
params.set("mode", "screen");
|
|
93
|
+
params.set("surface", "browser");
|
|
94
|
+
const recorderWindow = openRecorder(
|
|
95
|
+
`${appBasePath()}/record?${params.toString()}`,
|
|
96
|
+
);
|
|
97
|
+
onRecorderOpened?.(recorderWindow);
|
|
98
|
+
onRecordingStarted?.();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className={cn("space-y-4", className)}>
|
|
103
|
+
<div className="mb-5 flex items-start gap-3">
|
|
104
|
+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-primary text-primary-foreground">
|
|
105
|
+
<IconBug size={20} />
|
|
106
|
+
</div>
|
|
107
|
+
<div className="min-w-0">
|
|
108
|
+
<h2 className="text-xl font-semibold leading-tight">
|
|
109
|
+
{t("bugReportRoute.title")}
|
|
110
|
+
</h2>
|
|
111
|
+
<p className="mt-2 text-sm leading-6 text-muted-foreground">
|
|
112
|
+
{t("bugReportRoute.description")}
|
|
113
|
+
</p>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="space-y-4">
|
|
118
|
+
<div className="space-y-2">
|
|
119
|
+
<Label htmlFor="bug-title">
|
|
120
|
+
{t("bugReportRoute.issueTitleLabel")}
|
|
121
|
+
</Label>
|
|
122
|
+
<Input
|
|
123
|
+
id="bug-title"
|
|
124
|
+
value={title}
|
|
125
|
+
onChange={(event) => setTitle(event.target.value)}
|
|
126
|
+
placeholder={t("bugReportRoute.issueTitlePlaceholder")}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div className="space-y-2">
|
|
131
|
+
<Label htmlFor="bug-details">
|
|
132
|
+
{t("bugReportRoute.detailsLabel")}
|
|
133
|
+
</Label>
|
|
134
|
+
<Textarea
|
|
135
|
+
id="bug-details"
|
|
136
|
+
value={description}
|
|
137
|
+
onChange={(event) => setDescription(event.target.value)}
|
|
138
|
+
placeholder={t("bugReportRoute.detailsPlaceholder")}
|
|
139
|
+
className="min-h-24 resize-none"
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div className="grid gap-4 sm:grid-cols-[1fr_180px]">
|
|
144
|
+
<div className="space-y-2">
|
|
145
|
+
<Label htmlFor="bug-email">{t("bugReportRoute.emailLabel")}</Label>
|
|
146
|
+
<Input
|
|
147
|
+
id="bug-email"
|
|
148
|
+
type="email"
|
|
149
|
+
value={reporterEmail}
|
|
150
|
+
onChange={(event) => setReporterEmail(event.target.value)}
|
|
151
|
+
placeholder={t("bugReportRoute.emailPlaceholder")}
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
<div className="space-y-2">
|
|
155
|
+
<Label>{t("bugReportRoute.severityLabel")}</Label>
|
|
156
|
+
<Select
|
|
157
|
+
value={severity}
|
|
158
|
+
onValueChange={(value) => setSeverity(value as BugReportSeverity)}
|
|
159
|
+
>
|
|
160
|
+
<SelectTrigger>
|
|
161
|
+
<SelectValue />
|
|
162
|
+
</SelectTrigger>
|
|
163
|
+
<SelectContent>
|
|
164
|
+
<SelectItem value="low">
|
|
165
|
+
{t("bugReportRoute.severityLow")}
|
|
166
|
+
</SelectItem>
|
|
167
|
+
<SelectItem value="normal">
|
|
168
|
+
{t("bugReportRoute.severityNormal")}
|
|
169
|
+
</SelectItem>
|
|
170
|
+
<SelectItem value="high">
|
|
171
|
+
{t("bugReportRoute.severityHigh")}
|
|
172
|
+
</SelectItem>
|
|
173
|
+
<SelectItem value="urgent">
|
|
174
|
+
{t("bugReportRoute.severityUrgent")}
|
|
175
|
+
</SelectItem>
|
|
176
|
+
</SelectContent>
|
|
177
|
+
</Select>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<div className="rounded-md border bg-muted/40 px-3 py-2">
|
|
182
|
+
<p className="text-xs font-medium text-muted-foreground">
|
|
183
|
+
{t("bugReportRoute.sourceLabel")}
|
|
184
|
+
</p>
|
|
185
|
+
<p
|
|
186
|
+
className={cn(
|
|
187
|
+
"mt-1 truncate text-sm",
|
|
188
|
+
!sourceUrl && "text-muted-foreground",
|
|
189
|
+
)}
|
|
190
|
+
>
|
|
191
|
+
{sourceLabel}
|
|
192
|
+
</p>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<Button className="h-11 w-full" onClick={startRecording}>
|
|
196
|
+
{t("bugReportRoute.startRecording")}
|
|
197
|
+
</Button>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div className="mt-5 flex items-start gap-2 rounded-md bg-muted/50 p-3 text-xs leading-5 text-muted-foreground">
|
|
201
|
+
<IconShieldCheck size={16} className="mt-0.5 shrink-0" />
|
|
202
|
+
<p>{t("bugReportRoute.privacyNote")}</p>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
}
|