@agent-native/core 0.175.4 → 0.175.5-nightly-20260827014436
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/templates/analytics/actions/get-explorer-dashboard.ts +1 -0
- package/corpus/templates/analytics/app/components/auth/AuthProvider.tsx +8 -6
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +32 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +66 -7
- package/corpus/templates/analytics/app/lib/prefetch-keys.ts +10 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +9 -7
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +10 -11
- package/corpus/templates/analytics/changelog/2026-08-24-feature-flag-changes-recover-from-temporary-workspace-direct.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-26-analytics-distinguishes-daily-from-weekly-activity-shows-sta.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-26-dashboard-metadata-now-shows-the-original-creator-separately.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-26-dashboard-names-stay-editable-when-the-analytics-sidebar-ref.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-26-fixed-the-agent-re-asking-dashboard-scope-questions-you-had-.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +2 -0
- package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +8 -2
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +5 -0
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +6 -4
- package/corpus/templates/analytics/server/lib/migrate-analytics-artifacts.ts +19 -1
- package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +40 -9
- package/corpus/templates/analytics/server/plugins/db.ts +10 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +1 -0
- package/corpus/templates/analytics/shared/feature-flags.ts +11 -1
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +2 -9
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +8 -7
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +9 -8
- package/corpus/templates/clips/actions/import-loom-recording.ts +1 -1
- package/corpus/templates/clips/actions/lib/loom-import-job.ts +52 -35
- package/corpus/templates/clips/actions/request-transcript.ts +6 -183
- package/corpus/templates/clips/app/components/clips-avatar.tsx +46 -0
- package/corpus/templates/clips/app/components/library/recording-card.tsx +8 -7
- package/corpus/templates/clips/app/components/library/space-card.tsx +8 -5
- package/corpus/templates/clips/app/components/meetings/attendee-stack.tsx +7 -8
- package/corpus/templates/clips/app/components/meetings/transcript-bubbles.tsx +19 -12
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +128 -94
- package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +25 -104
- package/corpus/templates/clips/app/components/workspace/members-list.tsx +8 -6
- package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +8 -6
- package/corpus/templates/clips/app/components/workspace/top-creators-table.tsx +8 -6
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +8 -7
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +0 -26
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -4
- package/corpus/templates/clips/changelog/2026-08-26-detected-calls-no-longer-duplicate-meetings-or-strand-the-prompt.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +8 -10
- package/corpus/templates/clips/desktop/src/components/live-waveform.tsx +99 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +248 -55
- package/corpus/templates/clips/desktop/src/lib/agent-steps.ts +477 -0
- package/corpus/templates/clips/desktop/src/lib/ask-sheet-layout.ts +71 -0
- package/corpus/templates/clips/desktop/src/lib/meeting-ask.ts +297 -0
- package/corpus/templates/clips/desktop/src/lib/url.ts +24 -0
- package/corpus/templates/clips/desktop/src/overlays/ask-steps.tsx +155 -0
- package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +7 -81
- package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +29 -2
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +80 -22
- package/corpus/templates/clips/desktop/src/overlays/record-pill.tsx +13 -53
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +1017 -82
- package/corpus/templates/clips/desktop/src/styles.css +796 -57
- package/corpus/templates/clips/desktop/src/tailwind.css +23 -2
- package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +1765 -219
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +45 -30
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +5 -3
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +91 -0
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +123 -27
- package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +16 -0
- package/corpus/templates/clips/shared/audio-meter.ts +145 -0
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +0 -1
- package/corpus/templates/clips/shared/live-waveform.tsx +119 -0
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +44 -22
- package/corpus/templates/content/app/components/ui/avatar.tsx +1 -0
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
- package/corpus/templates/design/app/hooks/use-question-flow.ts +9 -2
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +111 -37
- package/corpus/templates/design/changelog/2026-08-26-design-no-longer-asks-users-to-confirm-guided-questions-they-have-already-answered.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-26-signing-out-goes-straight-to-the-sign-in-page.md +5 -0
- package/corpus/templates/dispatch/server/lib/org-apps-directory.ts +96 -47
- package/corpus/templates/dispatch/server/plugins/org-apps-directory.ts +73 -29
- package/corpus/templates/factory/.env.example +6 -2
- package/corpus/templates/factory/AGENTS.md +3 -3
- package/corpus/templates/factory/README.md +4 -2
- package/corpus/templates/factory/server/connectors/credentials.ts +70 -4
- package/corpus/templates/mail/actions/get-email.ts +68 -21
- package/corpus/templates/mail/actions/get-thread.ts +92 -51
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +2 -9
- package/corpus/templates/mail/app/hooks/use-emails.ts +7 -4
- package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +7 -4
- package/corpus/templates/plan/app/pages/PlansPage.tsx +23 -15
- package/corpus/templates/slides/actions/add-deck.ts +20 -0
- package/corpus/templates/slides/actions/create-deck.ts +17 -7
- package/corpus/templates/slides/actions/delete-deck.ts +45 -5
- package/corpus/templates/slides/actions/get-deck.ts +77 -11
- package/corpus/templates/slides/actions/patch-deck.ts +14 -2
- package/corpus/templates/slides/actions/save-deck.ts +20 -0
- package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +18 -7
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +38 -14
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +2 -2
- package/corpus/templates/slides/app/components/ui/avatar.tsx +1 -0
- package/corpus/templates/slides/server/handlers/decks.ts +10 -0
- package/corpus/templates/slides/shared/slide-ids.ts +133 -0
- package/corpus/templates/tasks/DEVELOPING.md +8 -0
- package/corpus/templates/tasks/drizzle.config.ts +3 -1
- package/corpus/templates/tasks/drizzle.local.config.ts +6 -0
- package/corpus/templates/tasks/package.json +2 -1
- package/corpus/templates/tasks/server/db/migrations/meta/0000_snapshot.json +234 -0
- package/corpus/templates/tasks/server/db/migrations/meta/_journal.json +13 -0
- package/corpus/templates/tasks/server/db/migrations.ts +110 -0
- package/corpus/templates/tasks/server/plugins/db.ts +3 -87
- package/dist/client/AssistantChat.js +3 -10
- package/dist/client/guided-questions.d.ts +11 -1
- package/dist/client/guided-questions.js +24 -7
- package/dist/client/hooks/index.d.ts +2 -1
- package/dist/client/hooks/index.js +2 -1
- package/dist/client/org/OrgSwitcher.js +3 -17
- package/dist/client/review/ReviewThreadPanel.js +4 -2
- package/dist/client/sign-out.d.ts +15 -0
- package/dist/client/sign-out.js +82 -0
- package/dist/client/use-action.d.ts +2 -0
- package/dist/client/use-action.js +4 -2
- package/dist/client/use-session.d.ts +23 -1
- package/dist/client/use-session.js +46 -0
- package/dist/collab/client.js +20 -5
- package/dist/collab/presence.d.ts +3 -1
- package/dist/collab/presence.js +48 -45
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/drizzle-config.d.ts +7 -0
- package/dist/db/drizzle-config.js +35 -9
- package/dist/db/drizzle-migrations.d.ts +28 -0
- package/dist/db/drizzle-migrations.js +81 -0
- package/dist/db/index.d.ts +1 -1
- package/dist/db/migrations.d.ts +13 -1
- package/dist/db/migrations.js +60 -31
- package/dist/db/schema.js +4 -1
- package/dist/deploy/build.d.ts +6 -0
- package/dist/deploy/build.js +41 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/org-directory.d.ts +19 -7
- package/dist/mcp/org-directory.js +92 -68
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/org/context.d.ts +6 -0
- package/dist/org/context.js +22 -0
- package/dist/org/index.d.ts +1 -1
- package/dist/org/index.js +1 -1
- package/dist/resources/store.d.ts +12 -0
- package/dist/resources/store.js +55 -0
- package/dist/server/agent-discovery.d.ts +18 -2
- package/dist/server/agent-discovery.js +191 -22
- package/dist/server/auth.js +3 -2
- package/dist/server/builder-browser.d.ts +0 -4
- package/dist/server/builder-browser.js +23 -8
- package/dist/server/google-oauth.d.ts +18 -0
- package/dist/server/google-oauth.js +3 -3
- package/dist/server/onboarding-html.js +3 -3
- package/dist/server/poll.d.ts +4 -2
- package/dist/server/poll.js +8 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/short-lived-token.d.ts +1 -1
- package/dist/server/short-lived-token.js +1 -1
- package/dist/shared/environment-lanes.d.ts +1 -0
- package/dist/shared/environment-lanes.js +1 -0
- package/dist/shared/google-profile-image.d.ts +2 -0
- package/dist/shared/google-profile-image.js +24 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/styles/agent-conversation.css +13 -0
- package/docs/content/creating-templates.mdx +10 -1
- package/docs/content/server-database.mdx +49 -3
- package/docs/content/server-plugins.mdx +39 -3
- package/docs/content/webmcp.mdx +2 -0
- package/package.json +2 -1
- package/corpus/templates/analytics/data/explorer-configs/_autosave.json +0 -18
- package/corpus/templates/analytics/data/explorer-configs/steve-chart.json +0 -18
- package/corpus/templates/analytics/data/explorer-dashboards/dashboard-1773260837897.json +0 -10
- package/corpus/templates/analytics/data/sync-config.json +0 -8
- package/corpus/templates/brain/data/sync-config.json +0 -1
- package/corpus/templates/calendar/data/availability.json +0 -32
- package/corpus/templates/calendar/data/settings.json +0 -6
- package/corpus/templates/calendar/data/sync-config.json +0 -10
- package/corpus/templates/chat/data/sync-config.json +0 -1
- package/corpus/templates/clips/data/sync-config.json +0 -1
- package/corpus/templates/clips/desktop/src/lib/audio-meter.ts +0 -80
- package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +0 -113
- package/corpus/templates/design/data/sync-config.json +0 -1
- package/corpus/templates/factory/data/sync-config.json +0 -1
- package/corpus/templates/forms/data/settings.json +0 -8
- package/corpus/templates/forms/data/sync-config.json +0 -3
- package/corpus/templates/macros/data/sync-config.json +0 -4
- package/corpus/templates/mail/data/emails.defaults.json +0 -1
- package/corpus/templates/mail/data/labels.defaults.json +0 -59
- package/corpus/templates/mail/data/settings.defaults.json +0 -9
- package/corpus/templates/mail/data/sync-config.json +0 -8
- package/corpus/templates/slides/data/sync-config.json +0 -8
- package/corpus/templates/tasks/data/sync-config.json +0 -1
- package/dist/templates/chat/data/.gitkeep +0 -0
- package/dist/templates/chat/data/sync-config.json +0 -1
- package/dist/templates/default/data/.gitkeep +0 -0
- package/dist/templates/default/data/sync-config.json +0 -1
- package/src/templates/chat/data/.gitkeep +0 -0
- package/src/templates/chat/data/sync-config.json +0 -1
- package/src/templates/default/data/.gitkeep +0 -0
- package/src/templates/default/data/sync-config.json +0 -1
|
@@ -17,9 +17,9 @@ import {
|
|
|
17
17
|
import { useCallback, useMemo, useRef, useState } from "react";
|
|
18
18
|
import { Link } from "react-router";
|
|
19
19
|
|
|
20
|
+
import { ClipsAvatar } from "@/components/clips-avatar";
|
|
20
21
|
import { AgentViewCount } from "@/components/player/recording-views-badge";
|
|
21
22
|
import { ViewedByPopover } from "@/components/sharing/viewed-by-popover";
|
|
22
|
-
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
23
23
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
24
24
|
import {
|
|
25
25
|
DropdownMenu,
|
|
@@ -340,12 +340,13 @@ export function RecordingCard({
|
|
|
340
340
|
</div>
|
|
341
341
|
)}
|
|
342
342
|
<div className="mt-1 flex min-w-0 items-center gap-1.5 text-[11px] text-muted-foreground">
|
|
343
|
-
<
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
343
|
+
<ClipsAvatar
|
|
344
|
+
email={recording.ownerEmail}
|
|
345
|
+
alt={recording.ownerEmail}
|
|
346
|
+
fallback={ownerInitials}
|
|
347
|
+
className="h-4 w-4 shrink-0"
|
|
348
|
+
fallbackClassName="bg-primary/15 text-[8px] text-primary"
|
|
349
|
+
/>
|
|
349
350
|
<span className="min-w-0 truncate">{recording.ownerEmail}</span>
|
|
350
351
|
<span aria-hidden>•</span>
|
|
351
352
|
<span className="shrink-0">{relative}</span>
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { useState } from "react";
|
|
10
10
|
import { Link } from "react-router";
|
|
11
11
|
|
|
12
|
+
import { ClipsAvatar } from "@/components/clips-avatar";
|
|
12
13
|
import {
|
|
13
14
|
ContextMenu,
|
|
14
15
|
ContextMenuContent,
|
|
@@ -104,13 +105,15 @@ export function SpaceCard({
|
|
|
104
105
|
.slice(0, 2)
|
|
105
106
|
.toUpperCase();
|
|
106
107
|
return (
|
|
107
|
-
<
|
|
108
|
+
<ClipsAvatar
|
|
108
109
|
key={email}
|
|
110
|
+
email={email}
|
|
111
|
+
alt={email}
|
|
112
|
+
fallback={initials}
|
|
113
|
+
className="h-5 w-5 border border-background"
|
|
114
|
+
fallbackClassName="bg-primary/15 text-[9px] font-medium text-primary"
|
|
109
115
|
title={email}
|
|
110
|
-
|
|
111
|
-
>
|
|
112
|
-
{initials}
|
|
113
|
-
</div>
|
|
116
|
+
/>
|
|
114
117
|
);
|
|
115
118
|
})}
|
|
116
119
|
{members.length > 5 && (
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* gets a shadcn Tooltip showing name/email. Used on both list cards and
|
|
6
6
|
* detail headers.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { ClipsAvatar } from "@/components/clips-avatar";
|
|
9
9
|
import {
|
|
10
10
|
Tooltip,
|
|
11
11
|
TooltipContent,
|
|
@@ -58,14 +58,13 @@ export function AttendeeStack({
|
|
|
58
58
|
{visible.map((p, i) => (
|
|
59
59
|
<Tooltip key={`${p.email}-${i}`}>
|
|
60
60
|
<TooltipTrigger asChild>
|
|
61
|
-
<
|
|
61
|
+
<ClipsAvatar
|
|
62
|
+
email={p.email}
|
|
63
|
+
alt={p.name || p.email}
|
|
64
|
+
fallback={attendeeInitials(p)}
|
|
65
|
+
fallbackClassName="font-medium"
|
|
62
66
|
className={`${sizeClass} ring-2 ring-background cursor-default`}
|
|
63
|
-
|
|
64
|
-
<AvatarImage alt={p.name || p.email} />
|
|
65
|
-
<AvatarFallback className="font-medium">
|
|
66
|
-
{attendeeInitials(p)}
|
|
67
|
-
</AvatarFallback>
|
|
68
|
-
</Avatar>
|
|
67
|
+
/>
|
|
69
68
|
</TooltipTrigger>
|
|
70
69
|
<TooltipContent side="top" className="text-xs">
|
|
71
70
|
<div className="font-medium">{p.name || p.email}</div>
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import type { KeyboardEvent, ReactNode } from "react";
|
|
10
10
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { ClipsAvatar } from "@/components/clips-avatar";
|
|
13
13
|
import { Button } from "@/components/ui/button";
|
|
14
14
|
import { Input } from "@/components/ui/input";
|
|
15
15
|
import {
|
|
@@ -59,6 +59,7 @@ export interface SpeakerIdentity {
|
|
|
59
59
|
key: string;
|
|
60
60
|
label: string | null;
|
|
61
61
|
initialsSource: AttendeeStackParticipant | string;
|
|
62
|
+
email?: string | null;
|
|
62
63
|
isOwner: boolean;
|
|
63
64
|
accentClass: string;
|
|
64
65
|
/** The capture could not tell speakers apart, so this transcript names
|
|
@@ -291,6 +292,7 @@ export function resolveSpeaker(
|
|
|
291
292
|
key,
|
|
292
293
|
label,
|
|
293
294
|
initialsSource: participant ?? label ?? (source === "mic" ? "Me" : "Them"),
|
|
295
|
+
email: participant?.email ?? (source === "mic" ? ownerEmail : null),
|
|
294
296
|
isOwner: source === "mic",
|
|
295
297
|
accentClass: accentForSpeaker(key, source === "mic"),
|
|
296
298
|
};
|
|
@@ -641,21 +643,26 @@ export function TranscriptBubbles({
|
|
|
641
643
|
>
|
|
642
644
|
{!group.speaker.unattributed && (
|
|
643
645
|
<div className="flex h-6 items-center gap-2">
|
|
644
|
-
<
|
|
646
|
+
<ClipsAvatar
|
|
647
|
+
email={group.speaker.email}
|
|
648
|
+
alt={
|
|
649
|
+
group.speaker.label ||
|
|
650
|
+
(group.speaker.isOwner
|
|
651
|
+
? t("transcriptBubbles.me")
|
|
652
|
+
: t("transcriptBubbles.them"))
|
|
653
|
+
}
|
|
654
|
+
fallback={attendeeInitials(
|
|
655
|
+
group.speaker.initialsSource,
|
|
656
|
+
)}
|
|
645
657
|
className={cn(
|
|
646
658
|
"size-6 shrink-0",
|
|
647
659
|
group.speaker.accentClass,
|
|
648
660
|
)}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
)}
|
|
655
|
-
>
|
|
656
|
-
{attendeeInitials(group.speaker.initialsSource)}
|
|
657
|
-
</AvatarFallback>
|
|
658
|
-
</Avatar>
|
|
661
|
+
fallbackClassName={cn(
|
|
662
|
+
"text-[9px] font-semibold",
|
|
663
|
+
group.speaker.accentClass,
|
|
664
|
+
)}
|
|
665
|
+
/>
|
|
659
666
|
<div className="flex min-h-6 items-center">
|
|
660
667
|
<span
|
|
661
668
|
className={cn(
|
|
@@ -32,6 +32,8 @@ export interface TranscriptSegment {
|
|
|
32
32
|
startMs: number;
|
|
33
33
|
endMs: number;
|
|
34
34
|
text: string;
|
|
35
|
+
source?: "mic" | "system";
|
|
36
|
+
speaker?: string | null;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export interface TranscriptPanelProps {
|
|
@@ -42,12 +44,6 @@ export interface TranscriptPanelProps {
|
|
|
42
44
|
onSeek: (ms: number) => void;
|
|
43
45
|
status?: "pending" | "ready" | "failed";
|
|
44
46
|
failureReason?: string | null;
|
|
45
|
-
cleanup?: {
|
|
46
|
-
status?: string | null;
|
|
47
|
-
provider?: string | null;
|
|
48
|
-
failureReason?: string | null;
|
|
49
|
-
updatedAt?: string | null;
|
|
50
|
-
} | null;
|
|
51
47
|
recordingTitle?: string;
|
|
52
48
|
/** Called when the user asks us to retry transcription after fixing an error. */
|
|
53
49
|
onRetry?: () => void;
|
|
@@ -56,6 +52,87 @@ export interface TranscriptPanelProps {
|
|
|
56
52
|
isRegenerating?: boolean;
|
|
57
53
|
}
|
|
58
54
|
|
|
55
|
+
const DISPLAY_MAX_WORDS = 36;
|
|
56
|
+
const DISPLAY_MIN_WORDS_BEFORE_SENTENCE_BREAK = 18;
|
|
57
|
+
const DISPLAY_MAX_GAP_MS = 3_500;
|
|
58
|
+
|
|
59
|
+
export function mergeTranscriptSegmentsForDisplay(
|
|
60
|
+
segments: TranscriptSegment[],
|
|
61
|
+
): TranscriptSegment[] {
|
|
62
|
+
const merged: TranscriptSegment[] = [];
|
|
63
|
+
let current: TranscriptSegment | null = null;
|
|
64
|
+
|
|
65
|
+
for (const segment of segments) {
|
|
66
|
+
if (!current) {
|
|
67
|
+
current = { ...segment };
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const currentWords = countWords(current.text);
|
|
72
|
+
const segmentWords = countWords(segment.text);
|
|
73
|
+
const sameSpeaker =
|
|
74
|
+
current.source === segment.source && current.speaker === segment.speaker;
|
|
75
|
+
const shouldBreak =
|
|
76
|
+
!sameSpeaker ||
|
|
77
|
+
segment.startMs - current.endMs > DISPLAY_MAX_GAP_MS ||
|
|
78
|
+
(currentWords >= DISPLAY_MIN_WORDS_BEFORE_SENTENCE_BREAK &&
|
|
79
|
+
endsSentence(current.text)) ||
|
|
80
|
+
currentWords + segmentWords > DISPLAY_MAX_WORDS;
|
|
81
|
+
|
|
82
|
+
if (shouldBreak) {
|
|
83
|
+
merged.push(current);
|
|
84
|
+
current = { ...segment };
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
current = {
|
|
89
|
+
...current,
|
|
90
|
+
endMs: segment.endMs,
|
|
91
|
+
text: `${current.text} ${segment.text}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (current) merged.push(current);
|
|
96
|
+
return merged;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function countWords(text: string): number {
|
|
100
|
+
return text.split(/\s+/).filter(Boolean).length;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function endsSentence(text: string): boolean {
|
|
104
|
+
return /[.!?]["')\]}]*$/.test(text.trim());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getTranscriptSeekMs(
|
|
108
|
+
displaySegment: TranscriptSegment,
|
|
109
|
+
query: string,
|
|
110
|
+
segments: TranscriptSegment[],
|
|
111
|
+
): number {
|
|
112
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
113
|
+
if (!normalizedQuery || segments.length === 0) return displaySegment.startMs;
|
|
114
|
+
|
|
115
|
+
const displaySegments = segments.filter(
|
|
116
|
+
(segment) =>
|
|
117
|
+
segment.startMs >= displaySegment.startMs &&
|
|
118
|
+
segment.endMs <= displaySegment.endMs,
|
|
119
|
+
);
|
|
120
|
+
const searchableText = displaySegments
|
|
121
|
+
.map((segment) => segment.text)
|
|
122
|
+
.join(" ")
|
|
123
|
+
.toLowerCase();
|
|
124
|
+
const matchIndex = searchableText.indexOf(normalizedQuery);
|
|
125
|
+
if (matchIndex < 0) return displaySegment.startMs;
|
|
126
|
+
|
|
127
|
+
let offset = 0;
|
|
128
|
+
for (const segment of displaySegments) {
|
|
129
|
+
if (matchIndex < offset + segment.text.length) return segment.startMs;
|
|
130
|
+
offset += segment.text.length + 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return displaySegment.startMs;
|
|
134
|
+
}
|
|
135
|
+
|
|
59
136
|
export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
60
137
|
const t = useT();
|
|
61
138
|
const {
|
|
@@ -66,7 +143,6 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
66
143
|
onSeek,
|
|
67
144
|
status,
|
|
68
145
|
failureReason,
|
|
69
|
-
cleanup,
|
|
70
146
|
recordingTitle,
|
|
71
147
|
onRetry,
|
|
72
148
|
onRegenerate,
|
|
@@ -76,7 +152,7 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
76
152
|
const [copied, setCopied] = useState(false);
|
|
77
153
|
|
|
78
154
|
const displaySegments = useMemo<TranscriptSegment[]>(() => {
|
|
79
|
-
if (segments.length > 0) return segments;
|
|
155
|
+
if (segments.length > 0) return mergeTranscriptSegmentsForDisplay(segments);
|
|
80
156
|
const text = fullText?.trim();
|
|
81
157
|
if (!text) return [];
|
|
82
158
|
return [
|
|
@@ -110,7 +186,7 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
110
186
|
}
|
|
111
187
|
|
|
112
188
|
function downloadSrt() {
|
|
113
|
-
const srt = toSrt(displaySegments);
|
|
189
|
+
const srt = toSrt(segments.length > 0 ? segments : displaySegments);
|
|
114
190
|
const blob = new Blob([srt], { type: "text/srt;charset=utf-8" });
|
|
115
191
|
const url = URL.createObjectURL(blob);
|
|
116
192
|
const a = document.createElement("a");
|
|
@@ -133,7 +209,7 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
133
209
|
if (status === "failed" && builderCreditsPaused) {
|
|
134
210
|
return (
|
|
135
211
|
<div className="p-4">
|
|
136
|
-
<BuilderCreditsPausedNotice
|
|
212
|
+
<BuilderCreditsPausedNotice onRetry={onRetry} />
|
|
137
213
|
</div>
|
|
138
214
|
);
|
|
139
215
|
}
|
|
@@ -219,7 +295,7 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
219
295
|
|
|
220
296
|
return (
|
|
221
297
|
<div className="flex flex-col h-full">
|
|
222
|
-
<div className="flex items-center gap-2
|
|
298
|
+
<div className="flex items-center gap-2 border-b border-border p-3">
|
|
223
299
|
<div className="relative flex-1">
|
|
224
300
|
<IconSearch className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
225
301
|
<Input
|
|
@@ -229,62 +305,49 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
229
305
|
className="pl-8 h-8 text-xs"
|
|
230
306
|
/>
|
|
231
307
|
</div>
|
|
232
|
-
<
|
|
233
|
-
<TooltipTrigger asChild>
|
|
234
|
-
<Button variant="ghost" size="icon" onClick={copyAll}>
|
|
235
|
-
{copied ? (
|
|
236
|
-
<IconCheck className="h-4 w-4 text-green-600" />
|
|
237
|
-
) : (
|
|
238
|
-
<IconCopy className="h-4 w-4" />
|
|
239
|
-
)}
|
|
240
|
-
</Button>
|
|
241
|
-
</TooltipTrigger>
|
|
242
|
-
<TooltipContent>{t("transcriptPanel.copyTranscript")}</TooltipContent>
|
|
243
|
-
</Tooltip>
|
|
244
|
-
<Tooltip>
|
|
245
|
-
<TooltipTrigger asChild>
|
|
246
|
-
<Button variant="ghost" size="icon" onClick={downloadSrt}>
|
|
247
|
-
<IconDownload className="h-4 w-4" />
|
|
248
|
-
</Button>
|
|
249
|
-
</TooltipTrigger>
|
|
250
|
-
<TooltipContent>{t("transcriptPanel.downloadSrt")}</TooltipContent>
|
|
251
|
-
</Tooltip>
|
|
252
|
-
{onRegenerate ? (
|
|
308
|
+
<div className="flex items-center gap-0.5">
|
|
253
309
|
<Tooltip>
|
|
254
310
|
<TooltipTrigger asChild>
|
|
255
|
-
<Button
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
>
|
|
262
|
-
<IconRefresh
|
|
263
|
-
className={cn("h-4 w-4", isRegenerating && "animate-spin")}
|
|
264
|
-
/>
|
|
311
|
+
<Button variant="ghost" size="icon" onClick={copyAll}>
|
|
312
|
+
{copied ? (
|
|
313
|
+
<IconCheck className="h-4 w-4 text-muted-foreground" />
|
|
314
|
+
) : (
|
|
315
|
+
<IconCopy className="h-4 w-4" />
|
|
316
|
+
)}
|
|
265
317
|
</Button>
|
|
266
318
|
</TooltipTrigger>
|
|
267
|
-
<TooltipContent>
|
|
319
|
+
<TooltipContent>
|
|
320
|
+
{t("transcriptPanel.copyTranscript")}
|
|
321
|
+
</TooltipContent>
|
|
268
322
|
</Tooltip>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
323
|
+
<Tooltip>
|
|
324
|
+
<TooltipTrigger asChild>
|
|
325
|
+
<Button variant="ghost" size="icon" onClick={downloadSrt}>
|
|
326
|
+
<IconDownload className="h-4 w-4" />
|
|
327
|
+
</Button>
|
|
328
|
+
</TooltipTrigger>
|
|
329
|
+
<TooltipContent>{t("transcriptPanel.downloadSrt")}</TooltipContent>
|
|
330
|
+
</Tooltip>
|
|
331
|
+
{onRegenerate ? (
|
|
332
|
+
<Tooltip>
|
|
333
|
+
<TooltipTrigger asChild>
|
|
334
|
+
<Button
|
|
335
|
+
variant="ghost"
|
|
336
|
+
size="icon"
|
|
337
|
+
onClick={onRegenerate}
|
|
338
|
+
disabled={isRegenerating}
|
|
339
|
+
aria-label={t("transcriptPanel.regenerate")}
|
|
340
|
+
>
|
|
341
|
+
<IconRefresh
|
|
342
|
+
className={cn("h-4 w-4", isRegenerating && "animate-spin")}
|
|
343
|
+
/>
|
|
344
|
+
</Button>
|
|
345
|
+
</TooltipTrigger>
|
|
346
|
+
<TooltipContent>{t("transcriptPanel.regenerate")}</TooltipContent>
|
|
347
|
+
</Tooltip>
|
|
348
|
+
) : null}
|
|
286
349
|
</div>
|
|
287
|
-
|
|
350
|
+
</div>
|
|
288
351
|
|
|
289
352
|
<div className="flex-1 overflow-y-auto">
|
|
290
353
|
{filtered.length === 0 ? (
|
|
@@ -297,6 +360,7 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
297
360
|
<ul className="py-1">
|
|
298
361
|
{filtered.map((seg) => {
|
|
299
362
|
const isActive = displaySegments[activeIndex] === seg;
|
|
363
|
+
const seekMs = getTranscriptSeekMs(seg, query, segments);
|
|
300
364
|
return (
|
|
301
365
|
<li key={seg.startMs}>
|
|
302
366
|
<TranscriptSegmentRow
|
|
@@ -305,12 +369,12 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
305
369
|
gutter="panel"
|
|
306
370
|
onClick={(event) => {
|
|
307
371
|
if (hasSelectionWithin(event.currentTarget)) return;
|
|
308
|
-
onSeek(
|
|
372
|
+
onSeek(seekMs);
|
|
309
373
|
}}
|
|
310
374
|
onKeyDown={(event) => {
|
|
311
375
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
312
376
|
event.preventDefault();
|
|
313
|
-
onSeek(
|
|
377
|
+
onSeek(seekMs);
|
|
314
378
|
}}
|
|
315
379
|
>
|
|
316
380
|
<span
|
|
@@ -382,17 +446,14 @@ function sanitizeFilename(s: string): string {
|
|
|
382
446
|
|
|
383
447
|
const BUILDER_CREDITS_FEATURE_LABELS = [
|
|
384
448
|
"builderCredits.featureBackupTranscription",
|
|
385
|
-
"builderCredits.featureCleanup",
|
|
386
449
|
"builderCredits.featureSummaries",
|
|
387
450
|
"builderCredits.featureTitles",
|
|
388
451
|
] as const;
|
|
389
452
|
|
|
390
453
|
function BuilderCreditsPausedNotice({
|
|
391
|
-
mode,
|
|
392
454
|
onRetry,
|
|
393
455
|
className,
|
|
394
456
|
}: {
|
|
395
|
-
mode: "transcription" | "cleanup";
|
|
396
457
|
onRetry?: () => void;
|
|
397
458
|
className?: string;
|
|
398
459
|
}) {
|
|
@@ -414,9 +475,7 @@ function BuilderCreditsPausedNotice({
|
|
|
414
475
|
{t("builderCredits.pausedTitle")}
|
|
415
476
|
</p>
|
|
416
477
|
<p className="mt-1 text-xs leading-relaxed text-amber-900/80 dark:text-amber-100/80">
|
|
417
|
-
{
|
|
418
|
-
? t("builderCredits.cleanupDescription")
|
|
419
|
-
: t("builderCredits.transcriptionDescription")}
|
|
478
|
+
{t("builderCredits.transcriptionDescription")}
|
|
420
479
|
</p>
|
|
421
480
|
</div>
|
|
422
481
|
<div className="flex flex-wrap gap-1.5">
|
|
@@ -543,31 +602,6 @@ function friendlyTranscriptFailure(
|
|
|
543
602
|
return reason;
|
|
544
603
|
}
|
|
545
604
|
|
|
546
|
-
function friendlyCleanupFailure(
|
|
547
|
-
reason: string | null | undefined,
|
|
548
|
-
t: ReturnType<typeof useT>,
|
|
549
|
-
): string {
|
|
550
|
-
if (!reason) return t("transcriptPanel.cleanupKept");
|
|
551
|
-
const normalized = reason.toLowerCase();
|
|
552
|
-
if (
|
|
553
|
-
normalized.includes("is connected, but") ||
|
|
554
|
-
normalized.includes("returned no text") ||
|
|
555
|
-
normalized.includes("service failed")
|
|
556
|
-
) {
|
|
557
|
-
return t("transcriptPanel.cleanupBuilderFailed");
|
|
558
|
-
}
|
|
559
|
-
if (
|
|
560
|
-
normalized.includes("incomplete") ||
|
|
561
|
-
isBuilderCreditsExhaustedMessage(reason) ||
|
|
562
|
-
normalized.includes("connect builder") ||
|
|
563
|
-
normalized.includes("not configured") ||
|
|
564
|
-
normalized.includes("api key")
|
|
565
|
-
) {
|
|
566
|
-
return t("transcriptPanel.cleanupPaused");
|
|
567
|
-
}
|
|
568
|
-
return t("transcriptPanel.cleanupKept");
|
|
569
|
-
}
|
|
570
|
-
|
|
571
605
|
/**
|
|
572
606
|
* Inline card shown when transcription needs a provider set up.
|
|
573
607
|
*
|