@agent-native/core 0.79.7 → 0.79.10
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 +2 -2
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/run-manager.ts +9 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +24 -16
- package/corpus/core/src/client/session-replay.ts +67 -13
- package/corpus/core/src/onboarding/default-steps.ts +3 -3
- package/corpus/core/src/server/action-routes.ts +6 -2
- package/corpus/core/src/server/agent-chat-plugin.ts +82 -162
- package/corpus/core/src/server/agent-run-context.ts +204 -0
- package/corpus/core/src/server/credential-provider.ts +20 -4
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +14 -4
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-uploads-now-accept-compressed-payloads-so-lar.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-sql-chart-tooltips-now-stay-stable-while-moving-across-dense.md +6 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +56 -8
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +44 -16
- package/corpus/templates/clips/app/i18n/ar-SA.ts +12 -9
- package/corpus/templates/clips/app/i18n/de-DE.ts +12 -9
- package/corpus/templates/clips/app/i18n/en-US.ts +19 -16
- package/corpus/templates/clips/app/i18n/es-ES.ts +12 -9
- package/corpus/templates/clips/app/i18n/fr-FR.ts +12 -9
- package/corpus/templates/clips/app/i18n/hi-IN.ts +13 -9
- package/corpus/templates/clips/app/i18n/ja-JP.ts +12 -9
- package/corpus/templates/clips/app/i18n/ko-KR.ts +12 -9
- package/corpus/templates/clips/app/i18n/pt-BR.ts +12 -9
- package/corpus/templates/clips/app/i18n/zh-CN.ts +19 -14
- package/corpus/templates/clips/app/i18n/zh-TW.ts +19 -14
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -0
- package/corpus/templates/clips/app/routes/record.tsx +2 -2
- package/corpus/templates/clips/changelog/2026-06-26-desktop-recordings-recover-more-reliably-when-macos-reports-.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-26-slack-unfurls-now-recognize-recording-dashboard-links-pasted.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-26-storage-setup-now-explains-why-clips-needs-connected-storage.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +24 -3
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +28 -2
- package/corpus/templates/clips/chrome-extension/src/popup.html +16 -0
- package/corpus/templates/clips/chrome-extension/src/popup.ts +59 -7
- package/corpus/templates/clips/chrome-extension/src/sentry.ts +28 -1
- package/corpus/templates/clips/chrome-extension/src/styles.css +74 -0
- package/corpus/templates/clips/desktop/src/app.tsx +85 -18
- package/corpus/templates/clips/desktop/src/styles.css +55 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +88 -51
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +12 -3
- package/corpus/templates/clips/server/lib/slack-unfurls.ts +5 -2
- package/corpus/templates/mail/app/hooks/use-emails.ts +28 -4
- package/corpus/templates/mail/app/root.tsx +13 -7
- package/corpus/templates/mail/changelog/2026-06-26-mail-avoids-unnecessary-message-list-reloads-after-backgroun.md +6 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +12 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +15 -8
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +55 -14
- package/dist/client/session-replay.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +6 -6
- package/dist/onboarding/default-steps.js +3 -3
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +6 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +69 -154
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-run-context.d.ts +35 -0
- package/dist/server/agent-run-context.d.ts.map +1 -0
- package/dist/server/agent-run-context.js +137 -0
- package/dist/server/agent-run-context.js.map +1 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +15 -4
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
- package/corpus/templates/.retired/README.md +0 -9
- package/corpus/templates/.retired/calls/package.json +0 -8
- package/corpus/templates/.retired/code/package.json +0 -8
- package/corpus/templates/.retired/contracts/package.json +0 -8
- package/corpus/templates/.retired/images/package.json +0 -8
- package/corpus/templates/.retired/issues/package.json +0 -8
- package/corpus/templates/.retired/meeting-notes/package.json +0 -8
- package/corpus/templates/.retired/migration/package.json +0 -8
- package/corpus/templates/.retired/recruiting/package.json +0 -8
- package/corpus/templates/.retired/scheduling/package.json +0 -8
- package/corpus/templates/.retired/visual-plans/package.json +0 -8
- package/corpus/templates/.retired/voice/package.json +0 -8
- package/corpus/templates/.retired/workbench/package.json +0 -8
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { createError, getHeader, type H3Event } from "h3";
|
|
2
|
+
|
|
3
|
+
import { resolveOrgIdForEmail, getOrgContext } from "../org/context.js";
|
|
4
|
+
import { getSession } from "./auth.js";
|
|
5
|
+
import {
|
|
6
|
+
runWithRequestContext,
|
|
7
|
+
type RequestContext,
|
|
8
|
+
} from "./request-context.js";
|
|
9
|
+
|
|
10
|
+
export type AgentRunOwnerContext = {
|
|
11
|
+
owner: string;
|
|
12
|
+
anonymous: boolean;
|
|
13
|
+
name?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const AGENT_RUN_OWNER_CONTEXT_KEY = "__agentNativeOwnerContext";
|
|
17
|
+
|
|
18
|
+
type EventWithAgentRunContext = H3Event & {
|
|
19
|
+
context?: Record<string, unknown>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type AnonymousOwnerResolver = (
|
|
23
|
+
event: H3Event,
|
|
24
|
+
) => string | null | Promise<string | null>;
|
|
25
|
+
|
|
26
|
+
type OrgIdResolver = (
|
|
27
|
+
event: H3Event,
|
|
28
|
+
) => string | null | undefined | Promise<string | null | undefined>;
|
|
29
|
+
|
|
30
|
+
function eventContext(
|
|
31
|
+
event: EventWithAgentRunContext,
|
|
32
|
+
): Record<string, unknown> {
|
|
33
|
+
event.context = event.context ?? {};
|
|
34
|
+
return event.context;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeId(value: string | null | undefined): string | undefined {
|
|
38
|
+
return typeof value === "string" && value.trim().length > 0
|
|
39
|
+
? value.trim()
|
|
40
|
+
: undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readHeaderValue(event: any, name: string): unknown {
|
|
44
|
+
try {
|
|
45
|
+
const value = getHeader(event, name);
|
|
46
|
+
if (value !== undefined && value !== null) return value;
|
|
47
|
+
} catch {
|
|
48
|
+
// Unit tests and a few framework internals pass lightweight event shims.
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const headers = event?.headers;
|
|
52
|
+
if (headers && typeof headers.get === "function") {
|
|
53
|
+
return headers.get(name) ?? headers.get(name.toLowerCase()) ?? undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const reqHeaders = event?.node?.req?.headers ?? event?.req?.headers;
|
|
57
|
+
if (reqHeaders && typeof reqHeaders === "object") {
|
|
58
|
+
return reqHeaders[name] ?? reqHeaders[name.toLowerCase()];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function readAgentRunTimezone(event: H3Event): string | undefined {
|
|
65
|
+
const raw = readHeaderValue(event, "x-user-timezone");
|
|
66
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
67
|
+
return typeof value === "string" &&
|
|
68
|
+
value.trim().length > 0 &&
|
|
69
|
+
value.trim().length < 64
|
|
70
|
+
? value.trim()
|
|
71
|
+
: undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function seedAgentRunOwnerContext(
|
|
75
|
+
event: H3Event,
|
|
76
|
+
ownerContext: AgentRunOwnerContext,
|
|
77
|
+
): AgentRunOwnerContext {
|
|
78
|
+
eventContext(event as EventWithAgentRunContext)[AGENT_RUN_OWNER_CONTEXT_KEY] =
|
|
79
|
+
ownerContext;
|
|
80
|
+
return ownerContext;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function seedBackgroundAgentRunOwnerContext(
|
|
84
|
+
event: H3Event,
|
|
85
|
+
runId: string,
|
|
86
|
+
): Promise<AgentRunOwnerContext | null> {
|
|
87
|
+
try {
|
|
88
|
+
const { getRunOwnerEmail } = await import("../agent/run-store.js");
|
|
89
|
+
const owner = await getRunOwnerEmail(runId);
|
|
90
|
+
if (!owner) return null;
|
|
91
|
+
return seedAgentRunOwnerContext(event, { owner, anonymous: false });
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function resolveAgentRunOwnerContext(
|
|
98
|
+
event: H3Event,
|
|
99
|
+
options: { anonymousOwner?: AnonymousOwnerResolver } = {},
|
|
100
|
+
): Promise<AgentRunOwnerContext> {
|
|
101
|
+
const ctx = eventContext(event as EventWithAgentRunContext);
|
|
102
|
+
const seeded = ctx[AGENT_RUN_OWNER_CONTEXT_KEY] as
|
|
103
|
+
| AgentRunOwnerContext
|
|
104
|
+
| undefined;
|
|
105
|
+
if (seeded) return seeded;
|
|
106
|
+
|
|
107
|
+
const session = await getSession(event);
|
|
108
|
+
if (session?.email) {
|
|
109
|
+
return seedAgentRunOwnerContext(event, {
|
|
110
|
+
owner: session.email,
|
|
111
|
+
anonymous: false,
|
|
112
|
+
name: session.name,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const anonymousOwner = await options.anonymousOwner?.(event);
|
|
117
|
+
if (anonymousOwner) {
|
|
118
|
+
return seedAgentRunOwnerContext(event, {
|
|
119
|
+
owner: anonymousOwner,
|
|
120
|
+
anonymous: true,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw createError({
|
|
125
|
+
statusCode: 401,
|
|
126
|
+
statusMessage: "Unauthenticated",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function resolveAgentRunOrgId(options: {
|
|
131
|
+
event: H3Event;
|
|
132
|
+
ownerContext: AgentRunOwnerContext;
|
|
133
|
+
resolveOrgId?: OrgIdResolver;
|
|
134
|
+
}): Promise<string | undefined> {
|
|
135
|
+
let resolvedOrgId: string | undefined;
|
|
136
|
+
|
|
137
|
+
if (options.resolveOrgId) {
|
|
138
|
+
resolvedOrgId = normalizeId(await options.resolveOrgId(options.event));
|
|
139
|
+
} else {
|
|
140
|
+
try {
|
|
141
|
+
const session = await getSession(options.event);
|
|
142
|
+
resolvedOrgId = normalizeId(session?.orgId);
|
|
143
|
+
} catch {
|
|
144
|
+
// Session not available.
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!resolvedOrgId) {
|
|
148
|
+
try {
|
|
149
|
+
const orgContext = await getOrgContext(options.event);
|
|
150
|
+
resolvedOrgId = normalizeId(orgContext.orgId);
|
|
151
|
+
} catch {
|
|
152
|
+
// Org tables may not exist yet on first boot.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (
|
|
158
|
+
!resolvedOrgId &&
|
|
159
|
+
options.ownerContext.owner &&
|
|
160
|
+
!options.ownerContext.anonymous
|
|
161
|
+
) {
|
|
162
|
+
try {
|
|
163
|
+
resolvedOrgId = normalizeId(
|
|
164
|
+
await resolveOrgIdForEmail(options.ownerContext.owner),
|
|
165
|
+
);
|
|
166
|
+
} catch {
|
|
167
|
+
// Org tables may not exist yet on first boot.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return resolvedOrgId;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function resolveAgentRunRequestContext(options: {
|
|
175
|
+
event: H3Event;
|
|
176
|
+
ownerContext: AgentRunOwnerContext;
|
|
177
|
+
resolveOrgId?: OrgIdResolver;
|
|
178
|
+
isBackgroundWorker?: boolean;
|
|
179
|
+
}): Promise<RequestContext> {
|
|
180
|
+
const orgId = await resolveAgentRunOrgId(options);
|
|
181
|
+
const timezone = readAgentRunTimezone(options.event);
|
|
182
|
+
return {
|
|
183
|
+
userEmail: options.ownerContext.owner,
|
|
184
|
+
userName: options.ownerContext.name,
|
|
185
|
+
orgId,
|
|
186
|
+
timezone,
|
|
187
|
+
...(options.isBackgroundWorker
|
|
188
|
+
? { run: { isBackgroundWorker: true } }
|
|
189
|
+
: {}),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function runWithAgentRunContext<T>(
|
|
194
|
+
options: {
|
|
195
|
+
event: H3Event;
|
|
196
|
+
ownerContext: AgentRunOwnerContext;
|
|
197
|
+
resolveOrgId?: OrgIdResolver;
|
|
198
|
+
isBackgroundWorker?: boolean;
|
|
199
|
+
},
|
|
200
|
+
fn: () => T | Promise<T>,
|
|
201
|
+
): Promise<T> {
|
|
202
|
+
const requestContext = await resolveAgentRunRequestContext(options);
|
|
203
|
+
return await runWithRequestContext(requestContext, fn);
|
|
204
|
+
}
|
|
@@ -80,7 +80,7 @@ export function readDeployCredentialEnv(key: string): string | undefined {
|
|
|
80
80
|
* key does not silently power another tenant's chat.
|
|
81
81
|
*/
|
|
82
82
|
export function isDeployCredentialFallbackAllowed(): boolean {
|
|
83
|
-
if (
|
|
83
|
+
if (!isProductionLikeRuntime()) return true;
|
|
84
84
|
return isLocalDatabase();
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -121,6 +121,22 @@ function isHostedWorkspaceRuntime(): boolean {
|
|
|
121
121
|
);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
function isProductionLikeRuntime(): boolean {
|
|
125
|
+
return (
|
|
126
|
+
process.env.NODE_ENV === "production" ||
|
|
127
|
+
/^(1|true)$/i.test(process.env.NETLIFY ?? "") ||
|
|
128
|
+
/^(1|true)$/i.test(process.env.VERCEL ?? "") ||
|
|
129
|
+
/^(1|true)$/i.test(process.env.CF_PAGES ?? "") ||
|
|
130
|
+
Boolean(
|
|
131
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME ||
|
|
132
|
+
process.env.AWS_EXECUTION_ENV ||
|
|
133
|
+
process.env.FUNCTIONS_WORKER_RUNTIME ||
|
|
134
|
+
process.env.K_SERVICE ||
|
|
135
|
+
process.env.RENDER,
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
124
140
|
/**
|
|
125
141
|
* Whether deployment-level Builder env keys may back the current request.
|
|
126
142
|
*
|
|
@@ -143,18 +159,18 @@ function canUseBuilderDeployCredentialFallbackForRequest(): boolean {
|
|
|
143
159
|
const email = getRequestUserEmail();
|
|
144
160
|
if (!email) return true;
|
|
145
161
|
|
|
146
|
-
const
|
|
162
|
+
const isProductionRuntime = isProductionLikeRuntime();
|
|
147
163
|
// Local dogfooding escape hatch: lets the env / root-`.env` Builder key back
|
|
148
164
|
// a signed-in user so running the app locally doesn't require completing the
|
|
149
165
|
// Builder connect flow first. Non-prod only.
|
|
150
166
|
const localDevOptIn =
|
|
151
|
-
!
|
|
167
|
+
!isProductionRuntime &&
|
|
152
168
|
/^(1|true)$/i.test(process.env.AGENT_NATIVE_LOCAL_BUILDER_ENV ?? "");
|
|
153
169
|
|
|
154
170
|
if (isHostedWorkspaceRuntime() && !localDevOptIn) return false;
|
|
155
171
|
|
|
156
172
|
// Deploy fallback is safe in non-prod or on a local/single-tenant database.
|
|
157
|
-
return !
|
|
173
|
+
return !isProductionRuntime || isLocalDatabase();
|
|
158
174
|
}
|
|
159
175
|
|
|
160
176
|
function shouldTraceCredentialResolve(): boolean {
|
|
@@ -593,8 +593,18 @@ function ChartTooltip({
|
|
|
593
593
|
left: number;
|
|
594
594
|
top: number;
|
|
595
595
|
} | null>(null);
|
|
596
|
-
const items =
|
|
597
|
-
|
|
596
|
+
const items = useMemo(
|
|
597
|
+
() =>
|
|
598
|
+
sortTooltipPayloadItems(
|
|
599
|
+
payload?.filter((item) => item.value != null && item.value !== "") ??
|
|
600
|
+
[],
|
|
601
|
+
),
|
|
602
|
+
[payload],
|
|
603
|
+
);
|
|
604
|
+
const portalVisible = portalPosition !== null;
|
|
605
|
+
const clearPortalPosition = useCallback(
|
|
606
|
+
() => setPortalPosition((prev) => (prev === null ? prev : null)),
|
|
607
|
+
[],
|
|
598
608
|
);
|
|
599
609
|
|
|
600
610
|
const labelText =
|
|
@@ -606,7 +616,7 @@ function ChartTooltip({
|
|
|
606
616
|
|
|
607
617
|
useBrowserLayoutEffect(() => {
|
|
608
618
|
if (!active || items.length === 0 || typeof window === "undefined") {
|
|
609
|
-
|
|
619
|
+
clearPortalPosition();
|
|
610
620
|
return;
|
|
611
621
|
}
|
|
612
622
|
|
|
@@ -688,7 +698,7 @@ function ChartTooltip({
|
|
|
688
698
|
window.removeEventListener("resize", schedule);
|
|
689
699
|
window.removeEventListener("scroll", schedule, true);
|
|
690
700
|
};
|
|
691
|
-
});
|
|
701
|
+
}, [active, clearPortalPosition, items.length, portalVisible]);
|
|
692
702
|
|
|
693
703
|
if (!active || items.length === 0) return null;
|
|
694
704
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { gunzipSync } from "node:zlib";
|
|
2
|
+
|
|
2
3
|
import {
|
|
3
4
|
defineEventHandler,
|
|
4
5
|
getHeader,
|
|
5
6
|
getQuery,
|
|
6
7
|
getRouterParam,
|
|
8
|
+
readRawBody,
|
|
7
9
|
setResponseHeader,
|
|
8
10
|
setResponseStatus,
|
|
9
11
|
} from "h3";
|
|
@@ -48,7 +50,7 @@ function setCors(event: any): void {
|
|
|
48
50
|
setResponseHeader(
|
|
49
51
|
event,
|
|
50
52
|
"Access-Control-Allow-Headers",
|
|
51
|
-
"content-type, x-agent-native-analytics-key",
|
|
53
|
+
"content-type, content-encoding, x-agent-native-analytics-key",
|
|
52
54
|
);
|
|
53
55
|
setResponseHeader(event, "Access-Control-Max-Age", "86400");
|
|
54
56
|
}
|
|
@@ -76,15 +78,60 @@ function injectHeaderKey(body: unknown, headerKey?: string): unknown {
|
|
|
76
78
|
return { publicKey: headerKey };
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
+
function statusError(message: string, statusCode: number): Error {
|
|
82
|
+
return Object.assign(new Error(message), { statusCode });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function decodeSessionReplayRequestBody(
|
|
86
|
+
rawBody: Buffer | Uint8Array | string | undefined,
|
|
87
|
+
contentEncoding?: string | null,
|
|
88
|
+
): { body: unknown; requestBytes: number } {
|
|
89
|
+
const requestBytes =
|
|
90
|
+
typeof rawBody === "string"
|
|
91
|
+
? Buffer.byteLength(rawBody, "utf8")
|
|
92
|
+
: (rawBody?.byteLength ?? 0);
|
|
93
|
+
const bytes = Buffer.isBuffer(rawBody)
|
|
94
|
+
? rawBody
|
|
95
|
+
: rawBody instanceof Uint8Array
|
|
96
|
+
? Buffer.from(rawBody)
|
|
97
|
+
: Buffer.from(rawBody ?? "", "utf8");
|
|
98
|
+
const encoding = (contentEncoding ?? "").split(";")[0]?.trim().toLowerCase();
|
|
99
|
+
let decoded = bytes;
|
|
100
|
+
|
|
101
|
+
if (encoding === "gzip" || encoding === "x-gzip") {
|
|
102
|
+
try {
|
|
103
|
+
decoded = gunzipSync(bytes);
|
|
104
|
+
} catch {
|
|
105
|
+
throw statusError("Invalid gzip-compressed replay body", 400);
|
|
106
|
+
}
|
|
107
|
+
} else if (encoding && encoding !== "identity") {
|
|
108
|
+
throw statusError(
|
|
109
|
+
`Unsupported replay request content-encoding: ${encoding}`,
|
|
110
|
+
415,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const text = decoded.toString("utf8");
|
|
115
|
+
if (!text.trim()) {
|
|
116
|
+
return { body: undefined, requestBytes };
|
|
117
|
+
}
|
|
81
118
|
try {
|
|
82
|
-
return
|
|
119
|
+
return { body: JSON.parse(text), requestBytes };
|
|
83
120
|
} catch {
|
|
84
|
-
return
|
|
121
|
+
return { body: text, requestBytes };
|
|
85
122
|
}
|
|
86
123
|
}
|
|
87
124
|
|
|
125
|
+
async function readSessionReplayRequestBody(
|
|
126
|
+
event: any,
|
|
127
|
+
): Promise<{ body: unknown; requestBytes: number }> {
|
|
128
|
+
const rawBody = await readRawBody(event, false);
|
|
129
|
+
return decodeSessionReplayRequestBody(
|
|
130
|
+
rawBody,
|
|
131
|
+
getHeader(event, "content-encoding"),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
88
135
|
function asString(value: unknown): string | undefined {
|
|
89
136
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
90
137
|
if (Array.isArray(value) && typeof value[0] === "string" && value[0].trim()) {
|
|
@@ -153,12 +200,13 @@ export const handleSessionReplayIngest = defineEventHandler(async (event) => {
|
|
|
153
200
|
}
|
|
154
201
|
|
|
155
202
|
const headerKey = getHeader(event, "x-agent-native-analytics-key");
|
|
156
|
-
const rawBody
|
|
203
|
+
const { body: rawBody, requestBytes } =
|
|
204
|
+
await readSessionReplayRequestBody(event);
|
|
157
205
|
const body = injectHeaderKey(rawBody, headerKey);
|
|
158
206
|
const parsed = parseSessionReplayIngestPayload(body);
|
|
159
207
|
const result = await recordSessionReplayChunks(parsed, {
|
|
160
208
|
origin: readOrigin(event),
|
|
161
|
-
requestBytes
|
|
209
|
+
requestBytes,
|
|
162
210
|
});
|
|
163
211
|
setResponseStatus(event, 202);
|
|
164
212
|
return { success: true, ...result };
|
|
@@ -585,7 +585,7 @@ function TranscriptSetupCard({
|
|
|
585
585
|
<p className="text-xs font-semibold">
|
|
586
586
|
{builderConfigured
|
|
587
587
|
? "Builder.io connected"
|
|
588
|
-
: "
|
|
588
|
+
: "Use Builder.io (free)"}
|
|
589
589
|
</p>
|
|
590
590
|
{!builderConfigured && (
|
|
591
591
|
<span className="rounded-sm bg-primary/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-primary">
|
|
@@ -13,6 +13,13 @@ import {
|
|
|
13
13
|
} from "@tabler/icons-react";
|
|
14
14
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
15
15
|
|
|
16
|
+
import {
|
|
17
|
+
Tooltip,
|
|
18
|
+
TooltipContent,
|
|
19
|
+
TooltipProvider,
|
|
20
|
+
TooltipTrigger,
|
|
21
|
+
} from "@/components/ui/tooltip";
|
|
22
|
+
|
|
16
23
|
function BuilderBMark({ className }: { className?: string }) {
|
|
17
24
|
return (
|
|
18
25
|
<svg
|
|
@@ -173,23 +180,44 @@ export function StorageSetupCard({
|
|
|
173
180
|
|
|
174
181
|
{/* S3 — secondary option */}
|
|
175
182
|
{!connected && (
|
|
176
|
-
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
183
|
+
<>
|
|
184
|
+
<div className="flex items-center gap-3 border-t border-border pt-4">
|
|
185
|
+
<IconServer className="h-4 w-4 text-muted-foreground" />
|
|
186
|
+
<div className="flex-1">
|
|
187
|
+
<span className="text-sm text-muted-foreground">
|
|
188
|
+
Or{" "}
|
|
189
|
+
<a
|
|
190
|
+
href={appPath("/settings#video-storage")}
|
|
191
|
+
className="font-medium text-foreground underline underline-offset-2 hover:text-foreground/80"
|
|
192
|
+
>
|
|
193
|
+
{t("storageSetup.configureS3")}
|
|
194
|
+
</a>
|
|
195
|
+
</span>
|
|
196
|
+
<span className="mt-0.5 block text-[11px] text-muted-foreground">
|
|
197
|
+
AWS S3, Cloudflare R2, DigitalOcean Spaces, MinIO
|
|
198
|
+
</span>
|
|
199
|
+
</div>
|
|
191
200
|
</div>
|
|
192
|
-
|
|
201
|
+
<TooltipProvider delayDuration={150}>
|
|
202
|
+
<Tooltip>
|
|
203
|
+
<TooltipTrigger asChild>
|
|
204
|
+
<button
|
|
205
|
+
type="button"
|
|
206
|
+
className="self-start text-xs text-muted-foreground underline decoration-muted-foreground/50 decoration-dotted underline-offset-4 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
207
|
+
>
|
|
208
|
+
{t("storageSetup.whyPrompt")}
|
|
209
|
+
</button>
|
|
210
|
+
</TooltipTrigger>
|
|
211
|
+
<TooltipContent
|
|
212
|
+
side="bottom"
|
|
213
|
+
align="start"
|
|
214
|
+
className="max-w-80 whitespace-normal text-xs leading-relaxed"
|
|
215
|
+
>
|
|
216
|
+
{t("storageSetup.whyDescription")}
|
|
217
|
+
</TooltipContent>
|
|
218
|
+
</Tooltip>
|
|
219
|
+
</TooltipProvider>
|
|
220
|
+
</>
|
|
193
221
|
)}
|
|
194
222
|
</div>
|
|
195
223
|
);
|
|
@@ -344,7 +344,7 @@ const messages = {
|
|
|
344
344
|
cleanupBuilderFailed:
|
|
345
345
|
"تعذر إنهاء عملية التنظيف على الرغم من اتصال Builder.io. تم الاحتفاظ بالنص الأصلي.",
|
|
346
346
|
cleanupPaused:
|
|
347
|
-
"
|
|
347
|
+
"التنظيف متوقف مؤقتًا. صِل الذكاء الاصطناعي من الإعدادات: Builder.io (أرصدة مجانية) أو مفتاح LLM الخاص بك.",
|
|
348
348
|
builderNoResponse:
|
|
349
349
|
"لم أتلق ردًا من Builder. اسمح بالنوافذ المنبثقة وحاول مرة أخرى.",
|
|
350
350
|
saveFailed: "فشل الحفظ ({{status}})",
|
|
@@ -584,7 +584,7 @@ const messages = {
|
|
|
584
584
|
"Builder.io هو مسار التخزين الأساسي لعمليات رفع Clips. يتوفر S3 عندما تحتاج إلى استخدام حاويتك الخاصة.",
|
|
585
585
|
checkingBuilder: "جار فحص Builder.io",
|
|
586
586
|
builderConnected: "Builder.io متصل",
|
|
587
|
-
connectBuilder: "
|
|
587
|
+
connectBuilder: "استخدام Builder.io (مجاني)",
|
|
588
588
|
builderConnectedFor: "يتم استخدام Builder.io لـ {{orgName}}.",
|
|
589
589
|
builderConnectedGeneric: "تستخدم المقاطع الجديدة مزود Builder.io المتصل.",
|
|
590
590
|
builderIncludes:
|
|
@@ -610,12 +610,12 @@ const messages = {
|
|
|
610
610
|
s3PublicBaseUrlLabel: "عنوان URL الأساسي العام",
|
|
611
611
|
apiSetup: "إعداد الذكاء الاصطناعي",
|
|
612
612
|
apiSetupDescription:
|
|
613
|
-
"
|
|
614
|
-
builderEasySetup: "Builder.io
|
|
613
|
+
"صِل الذكاء الاصطناعي باستخدام أرصدة Builder.io المجانية أو مفاتيح LLM الخاصة بك.",
|
|
614
|
+
builderEasySetup: "أرصدة Builder.io مجانية",
|
|
615
615
|
builderAiAvailable:
|
|
616
616
|
"تتوفر اعتمادات الذكاء الاصطناعي المضمنة والنسخ المُدار لـ Clips.",
|
|
617
617
|
builderAiDescription:
|
|
618
|
-
"
|
|
618
|
+
"استخدم Builder.io أولًا لاعتمادات الذكاء الاصطناعي المضمنة وتخزين الكائنات والرفع والنسخ المُدار.",
|
|
619
619
|
providerKeyTitle: "استخدام مفتاح مزود خاص بك",
|
|
620
620
|
providerKeyDescription:
|
|
621
621
|
"أضف مفاتيح Anthropic أو OpenAI أو Gemini أو Groq أو OpenRouter للاستخدام المفوتر من المزود.",
|
|
@@ -762,7 +762,7 @@ const messages = {
|
|
|
762
762
|
pickAtLeastTwo: "اختر تسجيلين على الأقل لدمجهما",
|
|
763
763
|
videoUrlMissing: "واحد أو أكثر من التسجيلات لا يحتوي بعد على رابط فيديو جاهز",
|
|
764
764
|
connectStorage:
|
|
765
|
-
"
|
|
765
|
+
"صِل التخزين قبل دمج التسجيلات: Builder.io (تخزين + ذكاء اصطناعي في الخطة المجانية) أو تخزين متوافق مع S3.",
|
|
766
766
|
created: "تم إنشاء التسجيل المدمج",
|
|
767
767
|
failed: "فشل دمج التسجيلات",
|
|
768
768
|
noOtherRecordings: "لا توجد تسجيلات أخرى متاحة.",
|
|
@@ -988,9 +988,12 @@ const messages = {
|
|
|
988
988
|
"لم يصل رد من Builder خلال 5 دقائق. تحقق من النافذة المنبثقة وحاول مرة أخرى.",
|
|
989
989
|
builderConnected: "Builder.io متصل",
|
|
990
990
|
waitingForBuilder: "بانتظار Builder...",
|
|
991
|
-
connectBuilder: "
|
|
991
|
+
connectBuilder: "استخدام Builder.io (مجاني)",
|
|
992
992
|
free: "مجاني",
|
|
993
993
|
configureS3: "تكوين تخزين متوافق مع S3",
|
|
994
|
+
whyPrompt: "لماذا أرى هذا؟",
|
|
995
|
+
whyDescription:
|
|
996
|
+
"Clips تطبيق مجاني ومفتوح المصدر 100%، لذلك تحتاج إلى توصيل طريقة لتخزين المقاطع. صِل التخزين عبر Builder.io لتخزين وذكاء اصطناعي ضمن الخطة المجانية، أو استخدم تخزين كائنات متوافقًا مع S3 ومفاتيح LLM الخاصة بك.",
|
|
994
997
|
},
|
|
995
998
|
captureInstall: {
|
|
996
999
|
title: "Choose your recorder (مترجم)",
|
|
@@ -1193,9 +1196,9 @@ const messages = {
|
|
|
1193
1196
|
downloadRecording: "Download recording (مترجم)",
|
|
1194
1197
|
openRecorderInTab: "Open recorder in tab (مترجم)",
|
|
1195
1198
|
connectStorageToFinish:
|
|
1196
|
-
"
|
|
1199
|
+
"صِل التخزين في الشاشة التالية: Builder.io (تخزين + ذكاء اصطناعي في الخطة المجانية) أو تخزين متوافق مع S3. سيكمل Clips الحفظ.",
|
|
1197
1200
|
connectStorageToRetryLoom:
|
|
1198
|
-
"
|
|
1201
|
+
"صِل التخزين في الشاشة التالية: Builder.io (تخزين + ذكاء اصطناعي في الخطة المجانية) أو تخزين متوافق مع S3. سيعيد Clips محاولة الاستيراد.",
|
|
1199
1202
|
},
|
|
1200
1203
|
dictateRoute: {
|
|
1201
1204
|
pageTitle: "Dictate · Clips (مترجم)",
|
|
@@ -359,7 +359,7 @@ const messages = {
|
|
|
359
359
|
cleanupBuilderFailed:
|
|
360
360
|
"Die Bereinigung konnte nicht abgeschlossen werden, obwohl Builder.io verbunden ist. Das native Transkript wurde aufbewahrt.",
|
|
361
361
|
cleanupPaused:
|
|
362
|
-
"Die Bereinigung ist pausiert.
|
|
362
|
+
"Die Bereinigung ist pausiert. Verbinde KI in den Einstellungen: Builder.io (kostenlose Credits) oder deinen eigenen LLM-Schlüssel.",
|
|
363
363
|
builderNoResponse:
|
|
364
364
|
"Ich habe keine Antwort von Builder erhalten. Lassen Sie Popups zu und versuchen Sie es erneut.",
|
|
365
365
|
saveFailed: "Speichern fehlgeschlagen ({{status}})",
|
|
@@ -607,7 +607,7 @@ Alle sichtbaren Änderungen für Clips-Nutzer werden hier dokumentiert. Du kanns
|
|
|
607
607
|
"Builder.io ist der primäre Speicherpfad für Clips-Uploads. S3 ist verfügbar, wenn du deinen eigenen Bucket verwenden musst.",
|
|
608
608
|
checkingBuilder: "Builder.io wird geprüft",
|
|
609
609
|
builderConnected: "Builder.io verbunden",
|
|
610
|
-
connectBuilder: "Builder.io
|
|
610
|
+
connectBuilder: "Builder.io nutzen (kostenlos)",
|
|
611
611
|
builderConnectedFor: "Builder.io wird für {{orgName}} verwendet.",
|
|
612
612
|
builderConnectedGeneric:
|
|
613
613
|
"Neue Clips verwenden den verbundenen Builder.io-Anbieter.",
|
|
@@ -635,12 +635,12 @@ Alle sichtbaren Änderungen für Clips-Nutzer werden hier dokumentiert. Du kanns
|
|
|
635
635
|
s3PublicBaseUrlLabel: "Öffentliche Basis-URL",
|
|
636
636
|
apiSetup: "KI-Einrichtung",
|
|
637
637
|
apiSetupDescription:
|
|
638
|
-
"
|
|
639
|
-
builderEasySetup: "Builder.io
|
|
638
|
+
"Verbinde KI mit kostenlosen Builder.io-Credits oder deinen eigenen LLM-Schlüsseln.",
|
|
639
|
+
builderEasySetup: "Kostenlose Builder.io-Credits",
|
|
640
640
|
builderAiAvailable:
|
|
641
641
|
"Enthaltene KI-Guthaben und verwaltete Transkription sind für Clips verfügbar.",
|
|
642
642
|
builderAiDescription:
|
|
643
|
-
"
|
|
643
|
+
"Nutze Builder.io zuerst für enthaltene KI-Guthaben, Objektspeicher, Uploads und verwaltete Transkription.",
|
|
644
644
|
providerKeyTitle: "Eigenen Anbieter-Schlüssel verwenden",
|
|
645
645
|
providerKeyDescription:
|
|
646
646
|
"Füge Anthropic-, OpenAI-, Gemini-, Groq- oder OpenRouter-Schlüssel für anbieterseitig abgerechnete Nutzung hinzu.",
|
|
@@ -787,7 +787,7 @@ Alle sichtbaren Änderungen für Clips-Nutzer werden hier dokumentiert. Du kanns
|
|
|
787
787
|
videoUrlMissing:
|
|
788
788
|
"Eine oder mehrere Aufzeichnungen haben noch keine fertige Video-URL",
|
|
789
789
|
connectStorage:
|
|
790
|
-
"Verbinde Builder.io oder S3-
|
|
790
|
+
"Verbinde Speicher vor dem Zusammenfügen von Aufzeichnungen: Builder.io (Speicher + KI im kostenlosen Tarif) oder S3-kompatibler Speicher.",
|
|
791
791
|
created: "Zusammengefügte Aufzeichnung erstellt",
|
|
792
792
|
failed: "Aufzeichnungen konnten nicht zusammengefügt werden",
|
|
793
793
|
noOtherRecordings: "Keine anderen Aufzeichnungen verfügbar.",
|
|
@@ -1018,9 +1018,12 @@ Alle sichtbaren Änderungen für Clips-Nutzer werden hier dokumentiert. Du kanns
|
|
|
1018
1018
|
"Nach 5 Minuten kam keine Antwort von Builder. Prüfe das Popup und versuche es erneut.",
|
|
1019
1019
|
builderConnected: "Builder.io verbunden",
|
|
1020
1020
|
waitingForBuilder: "Warten auf Builder...",
|
|
1021
|
-
connectBuilder: "Builder.io
|
|
1021
|
+
connectBuilder: "Builder.io nutzen (kostenlos)",
|
|
1022
1022
|
free: "Kostenlos",
|
|
1023
1023
|
configureS3: "S3-kompatiblen Speicher konfigurieren",
|
|
1024
|
+
whyPrompt: "Warum sehe ich das?",
|
|
1025
|
+
whyDescription:
|
|
1026
|
+
"Clips ist 100 % kostenlos und Open Source, deshalb musst du eine Möglichkeit zum Speichern deiner Clips verbinden. Verbinde Speicher mit Builder.io für Speicher und KI im kostenlosen Tarif, oder nutze S3-kompatiblen Objektspeicher und deine eigenen LLM-Schlüssel.",
|
|
1024
1027
|
},
|
|
1025
1028
|
captureInstall: {
|
|
1026
1029
|
title: "Choose your recorder (Lokalisiert)",
|
|
@@ -1223,9 +1226,9 @@ Alle sichtbaren Änderungen für Clips-Nutzer werden hier dokumentiert. Du kanns
|
|
|
1223
1226
|
downloadRecording: "Download recording (Lokalisiert)",
|
|
1224
1227
|
openRecorderInTab: "Open recorder in tab (Lokalisiert)",
|
|
1225
1228
|
connectStorageToFinish:
|
|
1226
|
-
"
|
|
1229
|
+
"Verbinde Speicher im nächsten Bildschirm: Builder.io (Speicher + KI im kostenlosen Tarif) oder S3-kompatibler Speicher. Clips schließt das Speichern ab.",
|
|
1227
1230
|
connectStorageToRetryLoom:
|
|
1228
|
-
"
|
|
1231
|
+
"Verbinde Speicher im nächsten Bildschirm: Builder.io (Speicher + KI im kostenlosen Tarif) oder S3-kompatibler Speicher. Clips versucht den Import erneut.",
|
|
1229
1232
|
},
|
|
1230
1233
|
dictateRoute: {
|
|
1231
1234
|
pageTitle: "Dictate · Clips (Lokalisiert)",
|