@kortix/ui 0.14.14 → 0.14.15
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/dist/customize/agents/agent-tabs.d.ts +1 -0
- package/dist/customize/agents/agent-tabs.js +1 -0
- package/dist/customize/shared/capability-library.js +7 -2
- package/dist/files/drives/drive-storage.d.ts +47 -0
- package/dist/files/drives/drive-storage.js +144 -0
- package/dist/files/drives/session-drives-chip.js +20 -2
- package/dist/files/index.d.ts +1 -0
- package/dist/files/index.js +1 -0
- package/dist/files/project/components/drive-explorer.js +13 -5
- package/dist/files/project/components/drive-upload-tray.js +1 -1
- package/dist/files/project/upload-batch.d.ts +3 -1
- package/dist/files/project/upload-batch.js +5 -2
- package/dist/i18n/en.d.ts +32 -12
- package/dist/i18n/en.js +33 -13
- package/dist/internal/chrome/use-chrome-offered.d.ts +14 -5
- package/dist/internal/chrome/use-chrome-offered.js +15 -6
- package/dist/session/composer/context-usage.d.ts +2 -0
- package/dist/session/composer/context-usage.js +7 -0
- package/dist/session/header/compact-modal.js +5 -2
- package/dist/session/header/session-site-header.js +5 -2
- package/dist/session/thread/transcript.js +2 -2
- package/dist/session/tool/tools/kortix-tool-card.js +1 -1
- package/dist/session/turn/burst-step-body.js +2 -2
- package/dist/session/turn/compaction-card.d.ts +23 -1
- package/dist/session/turn/compaction-card.js +20 -8
- package/dist/styles.css +1 -1
- package/dist/workspace/sidebar/project-nav-items.d.ts +0 -11
- package/dist/workspace/sidebar/project-nav-items.js +10 -77
- package/dist/workspace/sidebar/project-sidebar.js +4 -3
- package/package.json +3 -7
- package/skills/kortix-ui/references/modules.md +1 -1
- package/dist/internal/stores/sidebar-nav-store.d.ts +0 -78
- package/dist/internal/stores/sidebar-nav-store.js +0 -125
- package/dist/workspace/sidebar/customize-sidebar-modal.d.ts +0 -9
- package/dist/workspace/sidebar/customize-sidebar-modal.js +0 -198
- package/dist/workspace/sidebar/nav-row-meta.d.ts +0 -14
- package/dist/workspace/sidebar/nav-row-meta.js +0 -94
|
@@ -101,8 +101,9 @@ function catalogRows(view, catalog, t, agentMode) {
|
|
|
101
101
|
}
|
|
102
102
|
const item = (ext) => ({
|
|
103
103
|
id: ext.id,
|
|
104
|
-
// "Follows the agent tool switch" only where that switch is on screen.
|
|
105
|
-
|
|
104
|
+
// "Follows the agent tool switch" only where that switch is on screen. The context record's
|
|
105
|
+
// switch is its own row (the agent's `context` setting), so it follows nothing.
|
|
106
|
+
description: ext.controlled_by && ext.controlled_by !== 'context' && agentMode ? `${ext.description ?? ''} ${t('followsTool', { tool: ext.controlled_by })}` : ext.description,
|
|
106
107
|
status: ext.required ? _jsx("span", { className: "text-muted-foreground text-xs", children: t('alwaysOn') }) : undefined,
|
|
107
108
|
});
|
|
108
109
|
// A required platform extension (permission rules on script calls, the show
|
|
@@ -140,6 +141,10 @@ function withSwitches(view, rows, catalog, editor) {
|
|
|
140
141
|
const ext = catalog.extensions.find((x) => x.id === row.id);
|
|
141
142
|
if (!ext || ext.required)
|
|
142
143
|
return row;
|
|
144
|
+
// The context record is the agent's `context` field: on is the default (`record`), off is `summary`.
|
|
145
|
+
if (ext.group === 'platform' && ext.controlled_by === 'context') {
|
|
146
|
+
return { ...row, checked: editor.draft.context !== 'summary', onCheckedChange: (on) => editor.set('context', on ? 'record' : 'summary') };
|
|
147
|
+
}
|
|
143
148
|
if (ext.group === 'platform') {
|
|
144
149
|
const tool = catalog.tools.find((x) => x.id === ext.controlled_by);
|
|
145
150
|
return tool ? { ...row, ...toolSwitch(tool.id, tool.aliases) } : row;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Files storage, shown where people put files: how much a project's Files
|
|
3
|
+
* hold against the plan's limit, the folders close to the largest folder a
|
|
4
|
+
* session mounts (before a session fails to mount one), and a refused upload
|
|
5
|
+
* in words with its numbers. Every figure comes from the API
|
|
6
|
+
* (`GET /drives/:driveId/usage`, `driveLimitRefusal`); nothing is computed
|
|
7
|
+
* from a guess here.
|
|
8
|
+
*/
|
|
9
|
+
import { type DriveFolderUsage } from '@kortix/sdk';
|
|
10
|
+
import { useDriveUsage } from '@kortix/sdk/react';
|
|
11
|
+
export { useDriveUsage };
|
|
12
|
+
/**
|
|
13
|
+
* `5 GB`, `512 MB`, `3.4 KB` in the reader's locale. Binary steps (1 GB =
|
|
14
|
+
* 1024 MB), the same figures the API's refusals print; one decimal under 10.
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatStorageBytes(bytes: number, locale?: string): string;
|
|
17
|
+
/** `near` from 90 % of the limit, `full` at it. A storage limit is a policy a person can act on: orange, never red. */
|
|
18
|
+
export type StorageTone = 'ok' | 'near' | 'full';
|
|
19
|
+
export declare function storageTone(used: number, limit: number | null): StorageTone;
|
|
20
|
+
/** One headline figure against its limit: the label, `1.2 GB of 5 GB`, and one bar. */
|
|
21
|
+
export declare function StorageMeter({ label, usedBytes, limitBytes, className, }: {
|
|
22
|
+
label: string;
|
|
23
|
+
usedBytes: number;
|
|
24
|
+
limitBytes: number | null;
|
|
25
|
+
className?: string;
|
|
26
|
+
}): import("react").JSX.Element;
|
|
27
|
+
/** A folder at or past 80 % of the folder-mount cap, as one sentence. Null for a folder well under it. */
|
|
28
|
+
export declare function useFolderMountNotice(): (folder: DriveFolderUsage | undefined, limitBytes: number) => string | null;
|
|
29
|
+
/**
|
|
30
|
+
* The storage block of the Files page: the project's Files against the plan's
|
|
31
|
+
* limit (and the account's, when it is the closer one), what to do when it is
|
|
32
|
+
* nearly full, and each folder close to the folder-mount cap.
|
|
33
|
+
*/
|
|
34
|
+
export declare function DriveStorageSummary({ driveId, className }: {
|
|
35
|
+
driveId: string;
|
|
36
|
+
className?: string;
|
|
37
|
+
}): import("react").JSX.Element | null;
|
|
38
|
+
/** A storage state a person can act on: the orange glyph and tint, the words in ink. */
|
|
39
|
+
export declare function StorageNotice({ children }: {
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
}): import("react").JSX.Element | null;
|
|
42
|
+
/**
|
|
43
|
+
* The sentence for a refused upload, in the reader's language, from the
|
|
44
|
+
* refusal's code and numbers. Null for an error that is not a limit refusal:
|
|
45
|
+
* the caller keeps the server's message then.
|
|
46
|
+
*/
|
|
47
|
+
export declare function useDriveLimitMessage(): (error: unknown, fileName: string, fileSize?: number) => string | null;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Files storage, shown where people put files: how much a project's Files
|
|
5
|
+
* hold against the plan's limit, the folders close to the largest folder a
|
|
6
|
+
* session mounts (before a session fails to mount one), and a refused upload
|
|
7
|
+
* in words with its numbers. Every figure comes from the API
|
|
8
|
+
* (`GET /drives/:driveId/usage`, `driveLimitRefusal`); nothing is computed
|
|
9
|
+
* from a guess here.
|
|
10
|
+
*/
|
|
11
|
+
import { driveLimitRefusal } from '@kortix/sdk';
|
|
12
|
+
import { useDriveUsage } from '@kortix/sdk/react';
|
|
13
|
+
import { WarningIcon } from '@phosphor-icons/react';
|
|
14
|
+
import { useCallback } from 'react';
|
|
15
|
+
import { useLocale, useTranslations } from '../../i18n/index.js';
|
|
16
|
+
import { cn } from '../../lib/utils.js';
|
|
17
|
+
import { Progress } from '../../primitives/progress.js';
|
|
18
|
+
export { useDriveUsage };
|
|
19
|
+
const UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte'];
|
|
20
|
+
/**
|
|
21
|
+
* `5 GB`, `512 MB`, `3.4 KB` in the reader's locale. Binary steps (1 GB =
|
|
22
|
+
* 1024 MB), the same figures the API's refusals print; one decimal under 10.
|
|
23
|
+
*/
|
|
24
|
+
export function formatStorageBytes(bytes, locale) {
|
|
25
|
+
let value = Math.max(0, bytes);
|
|
26
|
+
let unit = 0;
|
|
27
|
+
while (value >= 1024 && unit < UNITS.length - 1) {
|
|
28
|
+
value /= 1024;
|
|
29
|
+
unit++;
|
|
30
|
+
}
|
|
31
|
+
return new Intl.NumberFormat(locale, {
|
|
32
|
+
style: 'unit',
|
|
33
|
+
unit: UNITS[unit],
|
|
34
|
+
unitDisplay: 'short',
|
|
35
|
+
maximumFractionDigits: unit === 0 || value >= 10 ? 0 : 1,
|
|
36
|
+
}).format(value);
|
|
37
|
+
}
|
|
38
|
+
export function storageTone(used, limit) {
|
|
39
|
+
if (!limit || limit <= 0)
|
|
40
|
+
return 'ok';
|
|
41
|
+
if (used >= limit)
|
|
42
|
+
return 'full';
|
|
43
|
+
return used >= limit * 0.9 ? 'near' : 'ok';
|
|
44
|
+
}
|
|
45
|
+
/** One headline figure against its limit: the label, `1.2 GB of 5 GB`, and one bar. */
|
|
46
|
+
export function StorageMeter({ label, usedBytes, limitBytes, className, }) {
|
|
47
|
+
const t = useTranslations('drives');
|
|
48
|
+
const locale = useLocale();
|
|
49
|
+
const tone = storageTone(usedBytes, limitBytes);
|
|
50
|
+
const used = formatStorageBytes(usedBytes, locale);
|
|
51
|
+
const percent = limitBytes ? Math.min(100, (usedBytes / limitBytes) * 100) : 0;
|
|
52
|
+
return (_jsxs("div", { className: cn('flex flex-col gap-1.5', className), children: [_jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [_jsx("span", { className: "text-foreground min-w-0 truncate text-xs font-medium", children: label }), _jsx("span", { className: "text-muted-foreground shrink-0 text-xs tabular-nums", children: limitBytes ? t('storageOfLimit', { used, limit: formatStorageBytes(limitBytes, locale) }) : t('storageUsed', { used }) })] }), limitBytes ? (_jsx(Progress, { value: percent, "aria-label": label, className: "h-1.5", indicatorClassName: tone === 'ok' ? 'bg-primary' : 'bg-kortix-orange' })) : null] }));
|
|
53
|
+
}
|
|
54
|
+
/** A folder at or past 80 % of the folder-mount cap, as one sentence. Null for a folder well under it. */
|
|
55
|
+
export function useFolderMountNotice() {
|
|
56
|
+
const t = useTranslations('drives');
|
|
57
|
+
const locale = useLocale();
|
|
58
|
+
return useCallback((folder, limitBytes) => {
|
|
59
|
+
if (!folder || folder.state === 'ok')
|
|
60
|
+
return null;
|
|
61
|
+
const params = {
|
|
62
|
+
folder: folder.path.split('/').filter(Boolean).pop() ?? folder.path,
|
|
63
|
+
size: formatStorageBytes(folder.bytes, locale),
|
|
64
|
+
limit: formatStorageBytes(limitBytes, locale),
|
|
65
|
+
};
|
|
66
|
+
return folder.state === 'over_limit' ? t('folderOverMountLimit', params) : t('folderNearMountLimit', params);
|
|
67
|
+
}, [t, locale]);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The storage block of the Files page: the project's Files against the plan's
|
|
71
|
+
* limit (and the account's, when it is the closer one), what to do when it is
|
|
72
|
+
* nearly full, and each folder close to the folder-mount cap.
|
|
73
|
+
*/
|
|
74
|
+
export function DriveStorageSummary({ driveId, className }) {
|
|
75
|
+
const t = useTranslations('drives');
|
|
76
|
+
const usage = useDriveUsage(driveId);
|
|
77
|
+
const folderNotice = useFolderMountNotice();
|
|
78
|
+
const data = usage.data;
|
|
79
|
+
if (!data || data.usedBytes === null)
|
|
80
|
+
return null;
|
|
81
|
+
const project = { used: data.usedBytes, limit: data.limits.projectBytes };
|
|
82
|
+
const account = data.account;
|
|
83
|
+
// The account figure shows only when it is the one closer to full.
|
|
84
|
+
const accountLeft = account ? account.limitBytes - account.usedBytes : Number.POSITIVE_INFINITY;
|
|
85
|
+
const projectLeft = project.limit === null ? Number.POSITIVE_INFINITY : project.limit - project.used;
|
|
86
|
+
const tightest = account && accountLeft < projectLeft ? storageTone(account.usedBytes, account.limitBytes) : storageTone(project.used, project.limit);
|
|
87
|
+
const folders = data.folders.filter((f) => f.state !== 'ok');
|
|
88
|
+
return (_jsxs("div", { className: cn('flex flex-col gap-2', className), children: [_jsx(StorageMeter, { label: t('storageLabel'), usedBytes: project.used, limitBytes: project.limit }), account && accountLeft < projectLeft ? (_jsx(AccountLine, { usedBytes: account.usedBytes, limitBytes: account.limitBytes })) : null, tightest !== 'ok' ? (_jsx(StorageNotice, { children: tightest === 'full' ? t('storageFull') : t('storageNearFull') })) : null, folders.map((f) => (_jsx(StorageNotice, { children: folderNotice(f, data.limits.folderMountBytes) }, f.path)))] }));
|
|
89
|
+
}
|
|
90
|
+
function AccountLine({ usedBytes, limitBytes }) {
|
|
91
|
+
const t = useTranslations('drives');
|
|
92
|
+
const locale = useLocale();
|
|
93
|
+
return (_jsx("p", { className: "text-muted-foreground text-xs tabular-nums", children: t('storageAccountLine', { used: formatStorageBytes(usedBytes, locale), limit: formatStorageBytes(limitBytes, locale) }) }));
|
|
94
|
+
}
|
|
95
|
+
/** A storage state a person can act on: the orange glyph and tint, the words in ink. */
|
|
96
|
+
export function StorageNotice({ children }) {
|
|
97
|
+
if (!children)
|
|
98
|
+
return null;
|
|
99
|
+
return (_jsxs("p", { role: "status", className: "bg-kortix-orange/15 text-foreground flex items-start gap-2 rounded-md px-2.5 py-2 text-xs text-pretty", children: [_jsx(WarningIcon, { className: "text-kortix-orange mt-px size-3.5 shrink-0", weight: "fill" }), _jsx("span", { className: "min-w-0", children: children })] }));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The sentence for a refused upload, in the reader's language, from the
|
|
103
|
+
* refusal's code and numbers. Null for an error that is not a limit refusal:
|
|
104
|
+
* the caller keeps the server's message then.
|
|
105
|
+
*/
|
|
106
|
+
export function useDriveLimitMessage() {
|
|
107
|
+
const t = useTranslations('drives');
|
|
108
|
+
const locale = useLocale();
|
|
109
|
+
return useCallback((error, fileName, fileSize) => {
|
|
110
|
+
const refusal = driveLimitRefusal(error);
|
|
111
|
+
if (!refusal)
|
|
112
|
+
return null;
|
|
113
|
+
const bytes = (n) => (n === undefined ? '' : formatStorageBytes(n, locale));
|
|
114
|
+
const name = fileName;
|
|
115
|
+
switch (refusal.code) {
|
|
116
|
+
case 'storage_limit_exceeded':
|
|
117
|
+
if (refusal.max === undefined || refusal.used === undefined || (refusal.requested ?? fileSize) === undefined)
|
|
118
|
+
return refusal.message || null;
|
|
119
|
+
return t(refusal.limit === 'account_bytes' ? 'uploadRefusedAccountFull' : 'uploadRefusedProjectFull', {
|
|
120
|
+
name,
|
|
121
|
+
needed: bytes(refusal.requested ?? fileSize),
|
|
122
|
+
used: bytes(refusal.used),
|
|
123
|
+
limit: bytes(refusal.max),
|
|
124
|
+
});
|
|
125
|
+
case 'file_too_large':
|
|
126
|
+
case 'payload_too_large':
|
|
127
|
+
if (refusal.max === undefined)
|
|
128
|
+
return refusal.message || null;
|
|
129
|
+
return t('uploadRefusedFileTooLarge', { name, size: bytes(refusal.requested ?? fileSize), limit: bytes(refusal.max) });
|
|
130
|
+
case 'sub_path_too_large':
|
|
131
|
+
return t('uploadRefusedFolderMount', {
|
|
132
|
+
name,
|
|
133
|
+
folder: refusal.path?.split('/').filter(Boolean).pop() ?? refusal.path ?? '',
|
|
134
|
+
limit: bytes(refusal.max ?? 10 * 1024 ** 3),
|
|
135
|
+
});
|
|
136
|
+
case 'transfer_limit_exceeded':
|
|
137
|
+
return t('uploadRefusedBusy', { name });
|
|
138
|
+
case 'quota_exceeded':
|
|
139
|
+
return t('uploadRefusedStorageFull', { name });
|
|
140
|
+
default:
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}, [t, locale]);
|
|
144
|
+
}
|
|
@@ -5,10 +5,20 @@ import { Button } from '../../primitives/button.js';
|
|
|
5
5
|
import { warningToast } from '../../primitives/toast.js';
|
|
6
6
|
import { Home } from '../../internal/icons/home.js';
|
|
7
7
|
import { useSessionDrives } from './use-drives.js';
|
|
8
|
-
import { useTranslations } from '../../i18n/index.js';
|
|
8
|
+
import { useLocale, useTranslations } from '../../i18n/index.js';
|
|
9
|
+
import { formatStorageBytes } from './drive-storage.js';
|
|
9
10
|
import { ArrowUpRightIcon, FolderIcon, WarningIcon } from '@phosphor-icons/react';
|
|
10
11
|
import { useUiLink } from '../../providers/ui-provider.js';
|
|
11
12
|
import { useEffect, useRef } from 'react';
|
|
13
|
+
/**
|
|
14
|
+
* One notice per cause of a folder the box does not mount: storage refused it
|
|
15
|
+
* at boot (it mounts later), it is too large to mount, or it did not fit.
|
|
16
|
+
*/
|
|
17
|
+
const SKIPPED_NOTICES = [
|
|
18
|
+
{ key: 'sessionSkippedUnavailable', matches: (reason) => reason === 'unavailable' },
|
|
19
|
+
{ key: 'sessionSkippedTooLarge', matches: (reason) => reason === 'too_large' },
|
|
20
|
+
{ key: 'sessionSkippedDrives', matches: (reason) => reason !== 'unavailable' && reason !== 'too_large' },
|
|
21
|
+
];
|
|
12
22
|
/** Where the person's own folder also appears in the box: a path, not prose. */
|
|
13
23
|
const DESKTOP_PATH = '~/Desktop';
|
|
14
24
|
/**
|
|
@@ -22,12 +32,20 @@ export function SessionDrivesList({ projectId, sessionId }) {
|
|
|
22
32
|
const t = useTranslations('drives');
|
|
23
33
|
const Link = useUiLink();
|
|
24
34
|
const driveHref = uiRoutes().drive(projectId);
|
|
35
|
+
const locale = useLocale();
|
|
25
36
|
const session = useSessionDrives(projectId, sessionId);
|
|
26
37
|
const rows = session.data?.drives ?? [];
|
|
27
38
|
const skipped = session.data?.skipped ?? [];
|
|
39
|
+
// A folder too large to mount names its size: the number to get it under.
|
|
40
|
+
const named = (d) => d.reason === 'too_large' && typeof d.bytes === 'number' ? `${d.name} (${formatStorageBytes(d.bytes, locale)})` : d.name;
|
|
28
41
|
return (_jsxs("div", { className: "flex flex-col gap-3", children: [driveHref ? (_jsx(Button, { asChild: true, variant: "ghost", size: "xs", className: "text-muted-foreground hover:text-foreground -ml-1.5 gap-1 self-start", children: _jsxs(Link, { href: driveHref, children: [t('openDrive'), _jsx(ArrowUpRightIcon, { className: "size-3.5" })] }) })) : null, rows.length === 0 ? (_jsx("p", { className: "text-muted-foreground py-2 text-xs", children: session.isLoading ? t('sessionCanUse') : t('sessionNoFolders') })) : (
|
|
29
42
|
// Up to 8 mount (the sandbox's volume limit), so the list stays short.
|
|
30
|
-
_jsx("ul", { className: "border-border divide-border divide-y rounded-lg border", children: rows.map((row) => (_jsx("li", { children: _jsx(SessionFolderRow, { driveHref: driveHref, row: row }) }, row.mountPath))) })),
|
|
43
|
+
_jsx("ul", { className: "border-border divide-border divide-y rounded-lg border", children: rows.map((row) => (_jsx("li", { children: _jsx(SessionFolderRow, { driveHref: driveHref, row: row }) }, row.mountPath))) })), SKIPPED_NOTICES.map(({ key, matches }) => {
|
|
44
|
+
const these = skipped.filter((d) => matches(d.reason));
|
|
45
|
+
if (!these.length)
|
|
46
|
+
return null;
|
|
47
|
+
return (_jsxs("p", { role: "status", className: "bg-kortix-orange/15 text-foreground flex items-start gap-2 rounded-md px-2.5 py-2 text-xs text-pretty", children: [_jsx(WarningIcon, { className: "text-kortix-orange mt-px size-3.5 shrink-0", weight: "fill" }), t(key, { count: these.length, names: these.map(named).join(', ') })] }, key));
|
|
48
|
+
})] }));
|
|
31
49
|
}
|
|
32
50
|
/**
|
|
33
51
|
* Raises the session's conflict notice: one toast when a mounted folder gets a
|
package/dist/files/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* session's workspace (`SandboxFileExplorer`, needs a mounted `useSession`).
|
|
5
5
|
*/
|
|
6
6
|
export { saveDriveFile, useDismissDriveConflict, useDriveAvailability, useDriveConflicts, useDriveFolder, useFolderAccess, useFolderPrincipals, useInvalidateDrives, useProjectDrive, useShareFolder, useUnshareFolder, } from './drives/use-drives.js';
|
|
7
|
+
export { DriveStorageSummary, formatStorageBytes, StorageMeter, StorageNotice, storageTone, type StorageTone, useDriveLimitMessage, useDriveUsage, useFolderMountNotice, } from './drives/drive-storage.js';
|
|
7
8
|
export { FilePreviewHost } from './file-preview-host.js';
|
|
8
9
|
export { FilesStoreProvider, getFileIcon } from './project/index.js';
|
|
9
10
|
export { ChangeRequestDetailDialog } from './project/components/change-request-detail-dialog.js';
|
package/dist/files/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* session's workspace (`SandboxFileExplorer`, needs a mounted `useSession`).
|
|
5
5
|
*/
|
|
6
6
|
export { saveDriveFile, useDismissDriveConflict, useDriveAvailability, useDriveConflicts, useDriveFolder, useFolderAccess, useFolderPrincipals, useInvalidateDrives, useProjectDrive, useShareFolder, useUnshareFolder, } from './drives/use-drives.js';
|
|
7
|
+
export { DriveStorageSummary, formatStorageBytes, StorageMeter, StorageNotice, storageTone, useDriveLimitMessage, useDriveUsage, useFolderMountNotice, } from './drives/drive-storage.js';
|
|
7
8
|
export { FilePreviewHost } from './file-preview-host.js';
|
|
8
9
|
export { FilesStoreProvider, getFileIcon } from './project/index.js';
|
|
9
10
|
export { ChangeRequestDetailDialog } from './project/components/change-request-detail-dialog.js';
|
|
@@ -15,7 +15,8 @@ import { ErrorState } from '../../../primitives/error-state.js';
|
|
|
15
15
|
import { isSandboxNotReadyError } from '@kortix/sdk';
|
|
16
16
|
import { ClipboardIcon as Clipboard, FolderPlusIcon as FolderPlus, ClipboardTextIcon, MoonIcon, XIcon, UploadSimpleIcon, UploadIcon as Upload, ArrowsLeftRightIcon, CheckSquareIcon, TrashIcon, } from '@phosphor-icons/react';
|
|
17
17
|
import { Download } from '../../../internal/icons/download.js';
|
|
18
|
-
import { useTranslations } from '../../../i18n/index.js';
|
|
18
|
+
import { useLocale, useTranslations } from '../../../i18n/index.js';
|
|
19
|
+
import { formatStorageBytes } from '../../drives/drive-storage.js';
|
|
19
20
|
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
|
|
20
21
|
import { useFileExplorerSource } from '../explorer-source.js';
|
|
21
22
|
import { buildGitStatusMap } from '../hooks/index.js';
|
|
@@ -72,6 +73,13 @@ export function DriveExplorer({ embedded = false, shareContext, leading, trailin
|
|
|
72
73
|
: source.capabilities;
|
|
73
74
|
const canWrite = capabilities.write;
|
|
74
75
|
const uploadLimit = capabilities.maxUploadBytes === undefined ? MAX_UPLOAD_BYTES : capabilities.maxUploadBytes;
|
|
76
|
+
const locale = useLocale();
|
|
77
|
+
// A file over the limit is refused before any byte moves, in the reader's language.
|
|
78
|
+
const describeRejection = useCallback((file, limit) => tDrives('uploadRefusedFileTooLarge', {
|
|
79
|
+
name: file.name,
|
|
80
|
+
size: formatStorageBytes(file.size, locale),
|
|
81
|
+
limit: formatStorageBytes(limit, locale),
|
|
82
|
+
}), [tDrives, locale]);
|
|
75
83
|
const currentPath = useFilesStore((s) => s.currentPath);
|
|
76
84
|
const navigateToPath = useFilesStore((s) => s.navigateToPath);
|
|
77
85
|
const viewMode = useFilesStore((s) => s.viewMode);
|
|
@@ -462,7 +470,7 @@ export function DriveExplorer({ embedded = false, shareContext, leading, trailin
|
|
|
462
470
|
* One failed file never stops the rest; its tray row offers Retry.
|
|
463
471
|
*/
|
|
464
472
|
const handleUploadFiles = useCallback(async (fileList, targetDirPath) => {
|
|
465
|
-
const { accepted, rejected } = partitionUploadBatch(Array.from(fileList), uploadLimit);
|
|
473
|
+
const { accepted, rejected } = partitionUploadBatch(Array.from(fileList), uploadLimit, describeRejection);
|
|
466
474
|
for (const { reason } of rejected)
|
|
467
475
|
errorToast(reason);
|
|
468
476
|
if (accepted.length === 0)
|
|
@@ -481,14 +489,14 @@ export function DriveExplorer({ embedded = false, shareContext, leading, trailin
|
|
|
481
489
|
]);
|
|
482
490
|
}
|
|
483
491
|
await startBatch(accepted.map((file) => ({ file, targetPath })), shownIn, targetDirPath ?? currentPath);
|
|
484
|
-
}, [currentPath, isRootPath, pendingNode, startBatch, uploadLimit]);
|
|
492
|
+
}, [currentPath, isRootPath, pendingNode, startBatch, uploadLimit, describeRejection]);
|
|
485
493
|
/**
|
|
486
494
|
* Upload a tree (a picked or dropped folder) into `base`: its folders are
|
|
487
495
|
* made first, one depth at a time (a level in parallel), then its files go
|
|
488
496
|
* up into them. Top folders landing in the folder in view show at once.
|
|
489
497
|
*/
|
|
490
498
|
const uploadTree = useCallback(async (tree, base) => {
|
|
491
|
-
const { accepted, rejected } = partitionUploadBatch(tree.map((t) => t.file), uploadLimit);
|
|
499
|
+
const { accepted, rejected } = partitionUploadBatch(tree.map((t) => t.file), uploadLimit, describeRejection);
|
|
492
500
|
for (const { reason } of rejected)
|
|
493
501
|
errorToast(reason);
|
|
494
502
|
const acceptedSet = new Set(accepted);
|
|
@@ -530,7 +538,7 @@ export function DriveExplorer({ embedded = false, shareContext, leading, trailin
|
|
|
530
538
|
await startBatch(items.map(({ file, dir: rel }) => ({ file, targetPath: at(rel) || undefined })),
|
|
531
539
|
// Loose files dropped beside the folders land in the view.
|
|
532
540
|
inView ? currentPath : null, tops.length === 1 ? at(tops[0]) : root || '/');
|
|
533
|
-
}, [currentPath, normalizedCurrentPath, pendingNode, settlePending, mkdirMutation, startBatch, tHardcodedUi, uploadLimit]);
|
|
541
|
+
}, [currentPath, normalizedCurrentPath, pendingNode, settlePending, mkdirMutation, startBatch, tHardcodedUi, uploadLimit, describeRejection]);
|
|
534
542
|
/** The folder picker (`webkitRelativePath` is `Picked/sub/file.png`). The browser already asked. */
|
|
535
543
|
const handleUploadFolder = useCallback((fileList) => uploadTree(Array.from(fileList).map((file) => ({
|
|
536
544
|
file,
|
|
@@ -57,7 +57,7 @@ export function DriveUploadTray() {
|
|
|
57
57
|
: failed > 0
|
|
58
58
|
? t('uploadsFailedCount', { count: failed })
|
|
59
59
|
: t('uploadsComplete', { count: done });
|
|
60
|
-
return (_jsxs("section", { "aria-label": title, className: "bg-popover border-border animate-in fade-in-0 slide-in-from-bottom-2 fixed right-4 bottom-4 z-50 w-80 max-w-[calc(100vw-2rem)] overflow-hidden rounded-md border shadow-lg duration-(--duration-moderate) motion-reduce:animate-none", children: [_jsxs("div", { className: "flex h-10 items-center gap-2 pr-1.5 pl-3", children: [active > 0 ? (_jsx(UploadGauge, { value: fraction, className: "size-5" })) : failed > 0 ? (_jsx(WarningCircleIcon, { className: "text-kortix-red size-5 shrink-0" })) : (_jsx(SolidCheckIcon, { className: "text-kortix-green size-5" })), _jsx("p", { "aria-live": "polite", className: "min-w-0 flex-1 truncate text-sm font-medium tabular-nums", children: title }), active > 0 ? (_jsxs("span", { className: "text-muted-foreground shrink-0 text-xs tabular-nums", children: [Math.min(99, Math.round(fraction * 100)), "%"] })) : null, _jsx(Button, { type: "button", variant: "ghost", size: "icon-xs", "aria-label": t('uploadsToggle'), "aria-expanded": !collapsed, onClick: () => setCollapsed((c) => !c), className: "text-muted-foreground hover:text-foreground", children: _jsx(CaretDownIcon, { className: cn('size-3.5 transition-transform duration-(--duration-moderate) motion-reduce:transition-none', collapsed && 'rotate-180') }) }), active === 0 ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon-xs", "aria-label": t('uploadsDismiss'), onClick: clear, className: "text-muted-foreground hover:text-foreground", children: _jsx(XIcon, { className: "size-3.5" }) })) : null] }), !collapsed ? (_jsx("ul", { className: "border-border/60 max-h-64 overflow-y-auto border-t py-1", children: uploads.map((u) => (_jsxs("li", { className: "flex h-9 items-center gap-2.5 px-3", children: [getFileIcon(u.file.name, { className: 'text-muted-foreground size-4 shrink-0' }),
|
|
60
|
+
return (_jsxs("section", { "aria-label": title, className: "bg-popover border-border animate-in fade-in-0 slide-in-from-bottom-2 fixed right-4 bottom-4 z-50 w-80 max-w-[calc(100vw-2rem)] overflow-hidden rounded-md border shadow-lg duration-(--duration-moderate) motion-reduce:animate-none", children: [_jsxs("div", { className: "flex h-10 items-center gap-2 pr-1.5 pl-3", children: [active > 0 ? (_jsx(UploadGauge, { value: fraction, className: "size-5" })) : failed > 0 ? (_jsx(WarningCircleIcon, { className: "text-kortix-red size-5 shrink-0" })) : (_jsx(SolidCheckIcon, { className: "text-kortix-green size-5" })), _jsx("p", { "aria-live": "polite", className: "min-w-0 flex-1 truncate text-sm font-medium tabular-nums", children: title }), active > 0 ? (_jsxs("span", { className: "text-muted-foreground shrink-0 text-xs tabular-nums", children: [Math.min(99, Math.round(fraction * 100)), "%"] })) : null, _jsx(Button, { type: "button", variant: "ghost", size: "icon-xs", "aria-label": t('uploadsToggle'), "aria-expanded": !collapsed, onClick: () => setCollapsed((c) => !c), className: "text-muted-foreground hover:text-foreground", children: _jsx(CaretDownIcon, { className: cn('size-3.5 transition-transform duration-(--duration-moderate) motion-reduce:transition-none', collapsed && 'rotate-180') }) }), active === 0 ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon-xs", "aria-label": t('uploadsDismiss'), onClick: clear, className: "text-muted-foreground hover:text-foreground", children: _jsx(XIcon, { className: "size-3.5" }) })) : null] }), !collapsed ? (_jsx("ul", { className: "border-border/60 max-h-64 overflow-y-auto border-t py-1", children: uploads.map((u) => (_jsxs("li", { className: "flex min-h-9 items-center gap-2.5 px-3 py-1.5", children: [getFileIcon(u.file.name, { className: 'text-muted-foreground size-4 shrink-0' }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: cn('truncate text-sm', u.status === 'done' ? 'text-foreground' : 'text-muted-foreground'), title: u.file.name, children: u.file.name }), u.status === 'error' && u.error ? (_jsx("span", { className: "text-foreground text-xs text-pretty", children: u.error })) : null] }), _jsx(UploadStatus, { upload: u, onRetry: () => u.retry?.() })] }, u.id))) })) : null] }));
|
|
61
61
|
}
|
|
62
62
|
function UploadStatus({ upload, onRetry }) {
|
|
63
63
|
const t = useTranslations('drives');
|
|
@@ -40,7 +40,9 @@ export interface UploadBatchPartition<T extends UploadCandidate> {
|
|
|
40
40
|
*/
|
|
41
41
|
export declare function uploadRejectionReason(file: UploadCandidate, limit?: number | null): string | null;
|
|
42
42
|
/** Split a batch into what will be sent and what is refused up front. `limit` null takes any size. */
|
|
43
|
-
export declare function partitionUploadBatch<T extends UploadCandidate>(files: readonly T[], limit?: number | null
|
|
43
|
+
export declare function partitionUploadBatch<T extends UploadCandidate>(files: readonly T[], limit?: number | null,
|
|
44
|
+
/** The sentence for a refused file, in the reader's language; the English default otherwise. */
|
|
45
|
+
describe?: (file: T, limit: number) => string): UploadBatchPartition<T>;
|
|
44
46
|
/**
|
|
45
47
|
* Share of the run sent, 0-1, by bytes: a finished file counts whole, a failed
|
|
46
48
|
* one drops out. An all-empty batch falls back to counting files.
|
|
@@ -32,11 +32,14 @@ export function uploadRejectionReason(file, limit = MAX_UPLOAD_BYTES) {
|
|
|
32
32
|
return `${file.name} is ${formatFileSize(file.size)} — the upload limit is ${label}`;
|
|
33
33
|
}
|
|
34
34
|
/** Split a batch into what will be sent and what is refused up front. `limit` null takes any size. */
|
|
35
|
-
export function partitionUploadBatch(files, limit = MAX_UPLOAD_BYTES
|
|
35
|
+
export function partitionUploadBatch(files, limit = MAX_UPLOAD_BYTES,
|
|
36
|
+
/** The sentence for a refused file, in the reader's language; the English default otherwise. */
|
|
37
|
+
describe) {
|
|
36
38
|
const accepted = [];
|
|
37
39
|
const rejected = [];
|
|
38
40
|
for (const file of files) {
|
|
39
|
-
const
|
|
41
|
+
const english = uploadRejectionReason(file, limit);
|
|
42
|
+
const reason = english && describe && limit !== null ? describe(file, limit) : english;
|
|
40
43
|
if (reason)
|
|
41
44
|
rejected.push({ file, reason });
|
|
42
45
|
else
|
package/dist/i18n/en.d.ts
CHANGED
|
@@ -906,6 +906,7 @@ declare const catalog: {
|
|
|
906
906
|
addCredits: string;
|
|
907
907
|
spentThisPeriod: string;
|
|
908
908
|
compute: string;
|
|
909
|
+
storage: string;
|
|
909
910
|
total: string;
|
|
910
911
|
history: string;
|
|
911
912
|
historyDescription: string;
|
|
@@ -1337,20 +1338,8 @@ declare const catalog: {
|
|
|
1337
1338
|
};
|
|
1338
1339
|
drive: string;
|
|
1339
1340
|
repo: string;
|
|
1340
|
-
customizeSidebar: string;
|
|
1341
1341
|
searchApps: string;
|
|
1342
1342
|
appsNoMatches: string;
|
|
1343
|
-
customizeSidebarDescription: string;
|
|
1344
|
-
resetSidebar: string;
|
|
1345
|
-
reorderItem: string;
|
|
1346
|
-
reorderHint: string;
|
|
1347
|
-
reorderAnnouncement: string;
|
|
1348
|
-
more: string;
|
|
1349
|
-
inSidebar: string;
|
|
1350
|
-
underMore: string;
|
|
1351
|
-
sidebarEmpty: string;
|
|
1352
|
-
moreEmpty: string;
|
|
1353
|
-
moreCaption: string;
|
|
1354
1343
|
spaces: {
|
|
1355
1344
|
channels: string;
|
|
1356
1345
|
directMessages: string;
|
|
@@ -7448,6 +7437,8 @@ declare const catalog: {
|
|
|
7448
7437
|
agentFullWriteDescription: string;
|
|
7449
7438
|
agentCardDrive: string;
|
|
7450
7439
|
sessionSkippedDrives: string;
|
|
7440
|
+
sessionSkippedUnavailable: string;
|
|
7441
|
+
sessionSkippedTooLarge: string;
|
|
7451
7442
|
shareFolder: string;
|
|
7452
7443
|
shareTitle: string;
|
|
7453
7444
|
shareDescription: string;
|
|
@@ -7515,6 +7506,24 @@ declare const catalog: {
|
|
|
7515
7506
|
readWrite: string;
|
|
7516
7507
|
folderHint: string;
|
|
7517
7508
|
driveLabel: string;
|
|
7509
|
+
storageLabel: string;
|
|
7510
|
+
storageOfLimit: string;
|
|
7511
|
+
storageUsed: string;
|
|
7512
|
+
storageNearFull: string;
|
|
7513
|
+
storageFull: string;
|
|
7514
|
+
storageAccountLine: string;
|
|
7515
|
+
folderNearMountLimit: string;
|
|
7516
|
+
folderOverMountLimit: string;
|
|
7517
|
+
uploadRefusedProjectFull: string;
|
|
7518
|
+
uploadRefusedAccountFull: string;
|
|
7519
|
+
uploadRefusedFileTooLarge: string;
|
|
7520
|
+
uploadRefusedFolderMount: string;
|
|
7521
|
+
uploadRefusedBusy: string;
|
|
7522
|
+
uploadRefusedStorageFull: string;
|
|
7523
|
+
accountStorageTitle: string;
|
|
7524
|
+
accountStorageDescription: string;
|
|
7525
|
+
accountStorageEmpty: string;
|
|
7526
|
+
accountStorageIncomplete: string;
|
|
7518
7527
|
};
|
|
7519
7528
|
genui: {
|
|
7520
7529
|
showData: string;
|
|
@@ -9571,5 +9580,16 @@ declare const catalog: {
|
|
|
9571
9580
|
connectClaude: string;
|
|
9572
9581
|
reconnectClaude: string;
|
|
9573
9582
|
};
|
|
9583
|
+
contextReset: {
|
|
9584
|
+
pill: string;
|
|
9585
|
+
pillRunning: string;
|
|
9586
|
+
detail: string;
|
|
9587
|
+
detailManual: string;
|
|
9588
|
+
showDetail: string;
|
|
9589
|
+
hideDetail: string;
|
|
9590
|
+
dialogTitle: string;
|
|
9591
|
+
dialogDescription: string;
|
|
9592
|
+
dialogAction: string;
|
|
9593
|
+
};
|
|
9574
9594
|
};
|
|
9575
9595
|
export default catalog;
|
package/dist/i18n/en.js
CHANGED
|
@@ -907,6 +907,7 @@ const catalog = {
|
|
|
907
907
|
"addCredits": "Add credits",
|
|
908
908
|
"spentThisPeriod": "Spent this period",
|
|
909
909
|
"compute": "Compute",
|
|
910
|
+
"storage": "Storage",
|
|
910
911
|
"total": "Total",
|
|
911
912
|
"history": "History",
|
|
912
913
|
"historyDescription": "What every session cost, and every credit added or spent.",
|
|
@@ -1338,20 +1339,8 @@ const catalog = {
|
|
|
1338
1339
|
},
|
|
1339
1340
|
"drive": "Drive",
|
|
1340
1341
|
"repo": "Repo",
|
|
1341
|
-
"customizeSidebar": "Customize sidebar",
|
|
1342
1342
|
"searchApps": "Search apps",
|
|
1343
1343
|
"appsNoMatches": "No matches",
|
|
1344
|
-
"customizeSidebarDescription": "Drag rows between the two groups.",
|
|
1345
|
-
"resetSidebar": "Reset to default",
|
|
1346
|
-
"reorderItem": "Reorder {name}",
|
|
1347
|
-
"reorderHint": "Drag to reorder. Or press the up and down arrow keys; moving past More hides the row from the sidebar.",
|
|
1348
|
-
"reorderAnnouncement": "{name}: {group}, position {position} of {total}",
|
|
1349
|
-
"more": "More",
|
|
1350
|
-
"inSidebar": "Sidebar",
|
|
1351
|
-
"underMore": "More",
|
|
1352
|
-
"sidebarEmpty": "Drag a row here to show it in the sidebar.",
|
|
1353
|
-
"moreEmpty": "Drag a row here to move it under More.",
|
|
1354
|
-
"moreCaption": "Rows under More open from the More menu, below the nav.",
|
|
1355
1344
|
"spaces": {
|
|
1356
1345
|
"channels": "Channels",
|
|
1357
1346
|
"directMessages": "Direct messages",
|
|
@@ -7449,6 +7438,8 @@ const catalog = {
|
|
|
7449
7438
|
"agentFullWriteDescription": "In the sessions you start, this agent can change any file in your drive, not only its From agents folder.",
|
|
7450
7439
|
"agentCardDrive": "{size} drive",
|
|
7451
7440
|
"sessionSkippedDrives": "{count, plural, one {# drive did not fit in this session} other {# drives did not fit in this session}}: {names}. Take a drive out to make room, then restart the session.",
|
|
7441
|
+
"sessionSkippedUnavailable": "{count, plural, one {# folder is not mounted yet} other {# folders are not mounted yet}}: {names}. The session runs without {count, plural, one {it} other {them}} and mounts {count, plural, one {it} other {them}} as soon as storage can.",
|
|
7442
|
+
"sessionSkippedTooLarge": "{count, plural, one {# folder is too large to mount in a session} other {# folders are too large to mount in a session}}: {names}. The session runs without {count, plural, one {it} other {them}}. Move large files out, or share a smaller folder.",
|
|
7452
7443
|
"shareFolder": "Share",
|
|
7453
7444
|
"shareTitle": "Share {name}",
|
|
7454
7445
|
"shareDescription": "People, teams and agents you add get this folder and everything in it. A session sees a folder when its agent, its project or the person who started it has access.",
|
|
@@ -7515,7 +7506,25 @@ const catalog = {
|
|
|
7515
7506
|
"sessionFilesDescription": "The Drive folders this session mounts, and where the agent sees them. Share a folder in Drive to add it; stop sharing to take it away.",
|
|
7516
7507
|
"readWrite": "Read & write",
|
|
7517
7508
|
"folderHint": "A folder inside {name}",
|
|
7518
|
-
"driveLabel": "Drive"
|
|
7509
|
+
"driveLabel": "Drive",
|
|
7510
|
+
"storageLabel": "Storage",
|
|
7511
|
+
"storageOfLimit": "{used} of {limit}",
|
|
7512
|
+
"storageUsed": "{used} used",
|
|
7513
|
+
"storageNearFull": "Almost full. Delete files you no longer need to keep uploading.",
|
|
7514
|
+
"storageFull": "Full. New uploads are refused until files are deleted.",
|
|
7515
|
+
"storageAccountLine": "All projects: {used} of {limit}",
|
|
7516
|
+
"folderNearMountLimit": "{folder} holds {size}. A session cannot mount a folder larger than {limit}.",
|
|
7517
|
+
"folderOverMountLimit": "{folder} holds {size}, more than the {limit} a session can mount. Sessions run without it.",
|
|
7518
|
+
"uploadRefusedProjectFull": "{name} needs {needed}. This project's Files use {used} of {limit}.",
|
|
7519
|
+
"uploadRefusedAccountFull": "{name} needs {needed}. Your account's Files use {used} of {limit} across all projects.",
|
|
7520
|
+
"uploadRefusedFileTooLarge": "{name} is {size}. One file can be at most {limit}.",
|
|
7521
|
+
"uploadRefusedFolderMount": "{name} would take {folder} past {limit}, the largest folder a session can mount.",
|
|
7522
|
+
"uploadRefusedBusy": "File storage is busy. Retry {name} in a moment.",
|
|
7523
|
+
"uploadRefusedStorageFull": "File storage is full right now. Retry {name} later.",
|
|
7524
|
+
"accountStorageTitle": "Files storage",
|
|
7525
|
+
"accountStorageDescription": "What each project's Files hold. Your plan allows {project} per project and {account} across all projects.",
|
|
7526
|
+
"accountStorageEmpty": "No project holds files yet.",
|
|
7527
|
+
"accountStorageIncomplete": "Some projects could not be measured right now."
|
|
7519
7528
|
},
|
|
7520
7529
|
"genui": {
|
|
7521
7530
|
"showData": "Show data",
|
|
@@ -9571,6 +9580,17 @@ const catalog = {
|
|
|
9571
9580
|
"codeError": "Could not connect Claude with that code. Sign in again and paste the new code.",
|
|
9572
9581
|
"connectClaude": "Connect Claude",
|
|
9573
9582
|
"reconnectClaude": "Reconnect Claude"
|
|
9583
|
+
},
|
|
9584
|
+
"contextReset": {
|
|
9585
|
+
"pill": "Fresh context · full history kept",
|
|
9586
|
+
"pillRunning": "Freeing up context…",
|
|
9587
|
+
"detail": "The context reached {tokens} tokens, so the agent continued in a fresh one. Nothing was summarized: it searches the full history ({entries} entries) when it needs a detail.",
|
|
9588
|
+
"detailManual": "You started a fresh context at {tokens} tokens. Nothing was summarized: the agent searches the full history ({entries} entries) when it needs a detail.",
|
|
9589
|
+
"showDetail": "Show details",
|
|
9590
|
+
"hideDetail": "Hide details",
|
|
9591
|
+
"dialogTitle": "Start a fresh context?",
|
|
9592
|
+
"dialogDescription": "The agent continues in a fresh context and searches the full history when it needs a detail. Nothing is summarized, and the conversation stays here as it is.",
|
|
9593
|
+
"dialogAction": "Start a fresh context"
|
|
9574
9594
|
}
|
|
9575
9595
|
};
|
|
9576
9596
|
export default catalog;
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
/** @internal Kortix apps only. No semver: it may change in any release. */
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* The web app's master switch for Kortix in Chrome. Off, no surface renders
|
|
4
|
+
* and nothing calls /v1/chrome/*: no Settings page, no command-palette entry,
|
|
5
|
+
* no sidebar row and no card in Apps and extensions. Off on every deployment
|
|
6
|
+
* until launch: the probe below logged a 404 in the console on staging and
|
|
7
|
+
* prod, where the API's KORTIX_CHROME_ENABLED is off. Set true to bring the
|
|
8
|
+
* surfaces back where the API serves the routes.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CHROME_UI_ENABLED = false;
|
|
11
|
+
/**
|
|
12
|
+
* Whether this platform offers Kortix in Chrome at all. The API answers 404
|
|
13
|
+
* on /v1/chrome/* where KORTIX_CHROME_ENABLED is off (staging, prod), so the
|
|
14
|
+
* account-level surfaces — Settings → Kortix in Chrome and its command-palette
|
|
15
|
+
* entry — show only once the browser list answers. Project surfaces read the
|
|
16
|
+
* project's `chrome` flag and `CHROME_UI_ENABLED` instead.
|
|
8
17
|
*
|
|
9
18
|
* `enabled`: ask only while the surface that needs the answer is open.
|
|
10
19
|
*/
|
|
@@ -3,11 +3,20 @@ import { listChromeConnections } from '@kortix/sdk';
|
|
|
3
3
|
import { chromeKeys } from '@kortix/sdk/react';
|
|
4
4
|
import { useQuery } from '@tanstack/react-query';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* The web app's master switch for Kortix in Chrome. Off, no surface renders
|
|
7
|
+
* and nothing calls /v1/chrome/*: no Settings page, no command-palette entry,
|
|
8
|
+
* no sidebar row and no card in Apps and extensions. Off on every deployment
|
|
9
|
+
* until launch: the probe below logged a 404 in the console on staging and
|
|
10
|
+
* prod, where the API's KORTIX_CHROME_ENABLED is off. Set true to bring the
|
|
11
|
+
* surfaces back where the API serves the routes.
|
|
12
|
+
*/
|
|
13
|
+
export const CHROME_UI_ENABLED = false;
|
|
14
|
+
/**
|
|
15
|
+
* Whether this platform offers Kortix in Chrome at all. The API answers 404
|
|
16
|
+
* on /v1/chrome/* where KORTIX_CHROME_ENABLED is off (staging, prod), so the
|
|
17
|
+
* account-level surfaces — Settings → Kortix in Chrome and its command-palette
|
|
18
|
+
* entry — show only once the browser list answers. Project surfaces read the
|
|
19
|
+
* project's `chrome` flag and `CHROME_UI_ENABLED` instead.
|
|
11
20
|
*
|
|
12
21
|
* `enabled`: ask only while the surface that needs the answer is open.
|
|
13
22
|
*/
|
|
@@ -15,7 +24,7 @@ export function useChromeOffered(enabled = true) {
|
|
|
15
24
|
return useQuery({
|
|
16
25
|
queryKey: chromeKeys.list(),
|
|
17
26
|
queryFn: listChromeConnections,
|
|
18
|
-
enabled,
|
|
27
|
+
enabled: CHROME_UI_ENABLED && enabled,
|
|
19
28
|
staleTime: 60_000,
|
|
20
29
|
retry: false,
|
|
21
30
|
}).isSuccess;
|