@kortix/ui 0.14.7 → 0.14.8
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/_vendor/kortix-shared.js +1 -1
- package/dist/_vendor/shared/runtime-versions.d.ts +4 -0
- package/dist/access/access/access-dialog-share.js +4 -7
- package/dist/access/access/access-dialog.js +2 -1
- package/dist/access/access/principal-picker.js +6 -5
- package/dist/customize/agents/agent-cover-card.d.ts +17 -0
- package/dist/customize/agents/agent-cover-card.js +13 -0
- package/dist/customize/agents/agent-page.js +6 -33
- package/dist/customize/agents/agents-page.js +43 -49
- package/dist/customize/migrate-to-v2/migration-prompt.d.ts +1 -0
- package/dist/customize/migrate-to-v2/migration-prompt.js +1 -0
- package/dist/customize/sections/view/channels-view.js +2 -6
- package/dist/customize/shared/new-entity-menu.js +4 -1
- package/dist/customize/skills/skills-page.js +4 -5
- package/dist/customize/triggers/schedule/then-fields.js +1 -5
- package/dist/customize/triggers/schedule-view.js +4 -2
- package/dist/i18n/en.d.ts +60 -12
- package/dist/i18n/en.js +62 -14
- package/dist/internal/components/agent-mark.d.ts +38 -0
- package/dist/internal/components/agent-mark.js +38 -0
- package/dist/internal/lib/pixel-mark.d.ts +33 -0
- package/dist/internal/lib/pixel-mark.js +152 -0
- package/dist/internal/lib/use-project-feature-flags.js +3 -3
- package/dist/markdown/genui/components/inline.d.ts +1 -1
- package/dist/markdown/setup-links/app-credentials-form.d.ts +12 -0
- package/dist/markdown/setup-links/app-credentials-form.js +115 -0
- package/dist/markdown/setup-links/secret-intake-form.d.ts +10 -0
- package/dist/markdown/setup-links/secret-intake-form.js +1 -1
- package/dist/primitives/entity-avatar.d.ts +4 -1
- package/dist/primitives/entity-avatar.js +4 -2
- package/dist/primitives/user-avatar.d.ts +3 -1
- package/dist/primitives/user-avatar.js +4 -2
- package/dist/providers/ui-provider.d.ts +1 -1
- package/dist/session/composer/agent-selector.d.ts +7 -1
- package/dist/session/composer/agent-selector.js +20 -10
- package/dist/session/composer/context-usage.d.ts +5 -0
- package/dist/session/composer/context-usage.js +6 -1
- package/dist/session/composer/token-progress.js +4 -4
- package/dist/session/header/export-transcript-modal.d.ts +3 -2
- package/dist/session/header/export-transcript-modal.js +6 -3
- package/dist/session/models/model-selector.d.ts +3 -1
- package/dist/session/models/model-selector.js +3 -2
- package/dist/session/panel/easy/plan-card.d.ts +2 -0
- package/dist/session/panel/easy/plan-card.js +2 -0
- package/dist/session/thread/plan-surface.d.ts +1 -0
- package/dist/session/thread/plan-surface.js +1 -0
- package/dist/session/thread/session-chat.js +2 -2
- package/dist/session/thread/session-context-metrics.d.ts +15 -0
- package/dist/session/thread/session-context-metrics.js +10 -1
- package/dist/session/thread/session-context-modal.d.ts +4 -2
- package/dist/session/thread/session-context-modal.js +16 -9
- package/dist/session/thread/transcript.d.ts +4 -0
- package/dist/session/thread/transcript.js +18 -5
- package/dist/session/thread/turn-share.d.ts +16 -0
- package/dist/session/thread/turn-share.js +162 -0
- package/dist/session/tool/tools/apply-patch-tool.d.ts +1 -0
- package/dist/session/tool/tools/apply-patch-tool.js +1 -0
- package/dist/session/tool/tools/pty-kill-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-kill-tool.js +1 -0
- package/dist/session/tool/tools/pty-read-tool.d.ts +2 -0
- package/dist/session/tool/tools/pty-read-tool.js +2 -0
- package/dist/session/tool/tools/pty-spawn-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-spawn-tool.js +1 -0
- package/dist/session/tool/tools/pty-write-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-write-tool.js +1 -0
- package/dist/session/tool/tools/todo-write-tool.d.ts +1 -0
- package/dist/session/tool/tools/todo-write-tool.js +1 -0
- package/dist/session/turn/message-author-label.js +6 -2
- package/dist/session/turn/plan-anchor.d.ts +3 -0
- package/dist/session/turn/plan-anchor.js +3 -0
- package/dist/session/turn/plan-card.d.ts +7 -0
- package/dist/session/turn/plan-card.js +7 -0
- package/dist/styles.css +1 -1
- package/dist/terminal/session-terminal-connect-bar.js +2 -1
- package/dist/workspace/sidebar/session-brief-hover-card.js +28 -1
- package/package.json +11 -3
- package/dist/customize/agents/agent-nav.prototype.d.ts +0 -35
- package/dist/customize/agents/agent-nav.prototype.js +0 -95
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from '../../primitives/button.js';
|
|
4
|
+
import { Field, FieldGroup, FieldLabel } from '../../primitives/field.js';
|
|
5
|
+
import { Input } from '../../primitives/input.js';
|
|
6
|
+
import { InputGroup, InputGroupAddon, InputGroupInput } from '../../primitives/input-group.js';
|
|
7
|
+
import Loading from '../../primitives/loading.js';
|
|
8
|
+
import { useTranslations } from '../../i18n/index.js';
|
|
9
|
+
import { getConnectorAppForm, HostBoundaryError, submitConnectorAppForm, } from '@kortix/sdk';
|
|
10
|
+
import { ArrowUpRightIcon, CheckIcon, ClockCountdownIcon, LinkBreakIcon, WarningCircleIcon, } from '@phosphor-icons/react';
|
|
11
|
+
import { useEffect, useState } from 'react';
|
|
12
|
+
import { setupLinkApiBase } from './api-base.js';
|
|
13
|
+
import { StatusNotice } from './secret-intake-form.js';
|
|
14
|
+
// Machine values, not prose: Shopify serves every store at <handle>.myshopify.com.
|
|
15
|
+
const SHOPIFY_HOST_SUFFIX = '.myshopify.com';
|
|
16
|
+
const EXAMPLE_STORE_HANDLE = 'acme';
|
|
17
|
+
const DEV_DASHBOARD_URL = 'https://dev.shopify.com/dashboard';
|
|
18
|
+
/** Long enough to read the confirmation before the window closes itself. */
|
|
19
|
+
const CLOSE_AFTER_MS = 1_500;
|
|
20
|
+
function statusOf(cause) {
|
|
21
|
+
return cause instanceof HostBoundaryError ? cause.status : null;
|
|
22
|
+
}
|
|
23
|
+
function DashboardLink({ children }) {
|
|
24
|
+
return (_jsxs("a", { href: DEV_DASHBOARD_URL, target: "_blank", rel: "noopener noreferrer", className: "group text-kortix-base focus-visible:ring-ring inline-flex items-baseline gap-0.5 rounded-sm font-medium outline-none focus-visible:ring-2", children: [_jsx("span", { className: "decoration-kortix-base/40 group-hover:decoration-kortix-base underline underline-offset-2", children: children }), _jsx(ArrowUpRightIcon, { className: "size-3 shrink-0 self-center" })] }));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The form behind an own-app connect link (`/connect/app/[token]`): the app's
|
|
28
|
+
* client credentials and the store, for a Composio toolkit that runs on the
|
|
29
|
+
* customer's own app. Shopify is the one such app today.
|
|
30
|
+
*
|
|
31
|
+
* The page opens in the connect popup. The window that opened it polls the
|
|
32
|
+
* connection, so after a success this window only confirms and closes itself
|
|
33
|
+
* (or follows the native app's return link).
|
|
34
|
+
*/
|
|
35
|
+
export function AppCredentialsForm({ token }) {
|
|
36
|
+
const t = useTranslations('connectAppForm');
|
|
37
|
+
const base = setupLinkApiBase();
|
|
38
|
+
const [phase, setPhase] = useState('loading');
|
|
39
|
+
const [info, setInfo] = useState(null);
|
|
40
|
+
const [store, setStore] = useState('');
|
|
41
|
+
const [clientId, setClientId] = useState('');
|
|
42
|
+
const [clientSecret, setClientSecret] = useState('');
|
|
43
|
+
const [connectedAs, setConnectedAs] = useState(null);
|
|
44
|
+
const [message, setMessage] = useState(null);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
let cancelled = false;
|
|
47
|
+
getConnectorAppForm(token, { backendUrl: base }).then((body) => {
|
|
48
|
+
if (cancelled)
|
|
49
|
+
return;
|
|
50
|
+
setInfo(body);
|
|
51
|
+
setConnectedAs(body.connected_as);
|
|
52
|
+
setPhase(body.connected ? 'done' : 'ready');
|
|
53
|
+
}, (cause) => {
|
|
54
|
+
if (cancelled)
|
|
55
|
+
return;
|
|
56
|
+
const status = statusOf(cause);
|
|
57
|
+
setMessage(cause instanceof Error ? cause.message : null);
|
|
58
|
+
setPhase(status === 410 ? 'expired' : status === 404 ? 'invalid' : status === 409 ? 'stale' : 'error');
|
|
59
|
+
});
|
|
60
|
+
return () => {
|
|
61
|
+
cancelled = true;
|
|
62
|
+
};
|
|
63
|
+
}, [base, token]);
|
|
64
|
+
async function submit() {
|
|
65
|
+
if (!store.trim() || !clientId.trim() || !clientSecret.trim()) {
|
|
66
|
+
setMessage(t.raw('missingFields'));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
setPhase('submitting');
|
|
70
|
+
setMessage(null);
|
|
71
|
+
try {
|
|
72
|
+
const result = await submitConnectorAppForm(token, { store: store.trim(), clientId: clientId.trim(), clientSecret: clientSecret.trim() }, { backendUrl: base });
|
|
73
|
+
setConnectedAs(result.connected_as);
|
|
74
|
+
setPhase('done');
|
|
75
|
+
window.setTimeout(() => {
|
|
76
|
+
if (result.success_redirect_uri)
|
|
77
|
+
window.location.assign(result.success_redirect_uri);
|
|
78
|
+
else
|
|
79
|
+
window.close();
|
|
80
|
+
}, CLOSE_AFTER_MS);
|
|
81
|
+
}
|
|
82
|
+
catch (cause) {
|
|
83
|
+
const status = statusOf(cause);
|
|
84
|
+
setMessage(cause instanceof Error ? cause.message : t.raw('failed'));
|
|
85
|
+
if (status === 410)
|
|
86
|
+
setPhase('expired');
|
|
87
|
+
else if (status === 409)
|
|
88
|
+
setPhase('stale');
|
|
89
|
+
else
|
|
90
|
+
setPhase('ready');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const app = info?.name ?? 'Shopify';
|
|
94
|
+
switch (phase) {
|
|
95
|
+
case 'loading':
|
|
96
|
+
return (_jsxs("div", { role: "status", className: "text-muted-foreground flex items-center justify-center gap-2 py-8 text-sm", children: [_jsx(Loading, { className: "size-4 shrink-0" }), t.raw('loading')] }));
|
|
97
|
+
case 'expired':
|
|
98
|
+
return (_jsx(StatusNotice, { icon: ClockCountdownIcon, tone: "warning", title: t.raw('expiredTitle'), description: t.raw('startAgain') }));
|
|
99
|
+
case 'invalid':
|
|
100
|
+
return (_jsx(StatusNotice, { icon: LinkBreakIcon, tone: "destructive", title: t.raw('invalidTitle'), description: t.raw('startAgain') }));
|
|
101
|
+
case 'stale':
|
|
102
|
+
return (_jsx(StatusNotice, { icon: LinkBreakIcon, tone: "warning", title: t.raw('staleTitle'), description: message ?? t.raw('startAgain') }));
|
|
103
|
+
case 'error':
|
|
104
|
+
return _jsx(StatusNotice, { icon: WarningCircleIcon, tone: "destructive", title: message ?? t.raw('failed') });
|
|
105
|
+
case 'done':
|
|
106
|
+
return (_jsx(StatusNotice, { icon: CheckIcon, tone: "success", title: t('connectedTitle', { app }), description: connectedAs ? t('connectedDescription', { store: connectedAs }) : t.raw('closeWindow') }));
|
|
107
|
+
}
|
|
108
|
+
const submitting = phase === 'submitting';
|
|
109
|
+
return (_jsxs("div", { className: "space-y-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: t.raw('stepsTitle') }), _jsxs("ol", { className: "text-muted-foreground list-decimal space-y-1 pl-4 text-xs text-pretty", children: [_jsx("li", { children: t.rich('step1', { link: (chunks) => _jsx(DashboardLink, { children: chunks }) }) }), _jsx("li", { children: t.raw('step2') }), _jsx("li", { children: t.raw('step3') }), _jsx("li", { children: t.raw('step4') })] }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: t.raw('sameOrganization') })] }), _jsxs(FieldGroup, { className: "gap-5", children: [_jsxs(Field, { className: "space-y-2", children: [_jsx(FieldLabel, { htmlFor: "app-form-store", className: "text-foreground", children: t.raw('storeLabel') }), _jsxs(InputGroup, { children: [_jsx(InputGroupInput, { id: "app-form-store", name: "store", autoComplete: "off", spellCheck: false,
|
|
110
|
+
// 16px below `sm` so iOS Safari does not zoom the page on focus.
|
|
111
|
+
className: "font-mono max-sm:text-base", placeholder: EXAMPLE_STORE_HANDLE, value: store, disabled: submitting, onChange: (e) => setStore(e.target.value) }), _jsx(InputGroupAddon, { align: "inline-end", children: SHOPIFY_HOST_SUFFIX })] })] }), _jsxs(Field, { className: "space-y-2", children: [_jsx(FieldLabel, { htmlFor: "app-form-client-id", className: "text-foreground", children: t.raw('clientIdLabel') }), _jsx(Input, { id: "app-form-client-id", name: "client_id", autoComplete: "off", spellCheck: false, className: "font-mono max-sm:text-base", value: clientId, disabled: submitting, onChange: (e) => setClientId(e.target.value) })] }), _jsxs(Field, { className: "space-y-2", children: [_jsx(FieldLabel, { htmlFor: "app-form-client-secret", className: "text-foreground", children: t.raw('clientSecretLabel') }), _jsx(Input, { id: "app-form-client-secret", name: "client_secret", type: "password", autoComplete: "off", spellCheck: false, className: "font-mono max-sm:text-base", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", value: clientSecret, disabled: submitting, onChange: (e) => setClientSecret(e.target.value), onKeyDown: (e) => {
|
|
112
|
+
if (e.key === 'Enter')
|
|
113
|
+
void submit();
|
|
114
|
+
} })] })] }), _jsxs("div", { className: "space-y-3", children: [message ? (_jsx("p", { role: "alert", className: "text-destructive text-xs text-pretty", children: message })) : null, _jsxs(Button, { type: "button", className: "w-full", onClick: submit, disabled: submitting, children: [submitting ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, submitting ? t.raw('connecting') : t('submit', { app })] }), _jsx("p", { className: "text-muted-foreground text-center text-xs text-pretty", children: t.raw('secretNote') })] })] }));
|
|
115
|
+
}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import type { OutcomeTone } from '../../internal/outcomes/outcome-types.js';
|
|
2
|
+
import { type Icon } from '@phosphor-icons/react';
|
|
3
|
+
import { type ReactElement } from 'react';
|
|
4
|
+
/** Every phase that is not the form: tinted tile, title, one muted line. */
|
|
5
|
+
export declare function StatusNotice({ icon: Glyph, tone, title, description, }: {
|
|
6
|
+
icon: Icon;
|
|
7
|
+
tone: OutcomeTone;
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}): ReactElement;
|
|
1
11
|
/**
|
|
2
12
|
* Renders the fields an agent-minted secret link asks for, and submits the
|
|
3
13
|
* values the human types. Shared by the public /secret-intake/[token] page and
|
|
@@ -14,7 +14,7 @@ import { Fragment, useEffect, useState } from 'react';
|
|
|
14
14
|
import { classifySetupLinkError, describeLinkExpiry, describeWithheldSecrets, splitTextLinks, } from './util.js';
|
|
15
15
|
import { setupLinkApiBase } from './api-base.js';
|
|
16
16
|
/** Every phase that is not the form: tinted tile, title, one muted line. */
|
|
17
|
-
function StatusNotice({ icon: Glyph, tone, title, description, }) {
|
|
17
|
+
export function StatusNotice({ icon: Glyph, tone, title, description, }) {
|
|
18
18
|
const tint = outcomeTint(tone);
|
|
19
19
|
return (_jsxs("div", { role: "status", className: "flex flex-col items-center gap-3 py-8 text-center", children: [_jsx("span", { className: cn('flex size-9 items-center justify-center rounded-sm ring-1', tint.ring, tint.bg), children: _jsx(Glyph, { weight: "fill", className: cn('size-5', tint.fg) }) }), _jsxs("div", { className: "max-w-xs space-y-1", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: title }), description ? (_jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: description })) : null] })] }));
|
|
20
20
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
1
2
|
import type { Icon } from './kortix-icons.js';
|
|
2
3
|
import { type Icon as IconType } from '@phosphor-icons/react';
|
|
3
4
|
/**
|
|
@@ -79,6 +80,8 @@ export interface EntityAvatarProps {
|
|
|
79
80
|
icon?: Icon | IconType;
|
|
80
81
|
size?: EntityAvatarSize;
|
|
81
82
|
className?: string;
|
|
83
|
+
/** Shows on hover, through `Hint`: who or what this avatar stands for. */
|
|
84
|
+
hint?: ReactNode;
|
|
82
85
|
}
|
|
83
|
-
export declare function EntityAvatar({ label, glyph, emoji, icon: IconComponent, size, className, }: EntityAvatarProps): import("react").JSX.Element;
|
|
86
|
+
export declare function EntityAvatar({ hint, label, glyph, emoji, icon: IconComponent, size, className, }: EntityAvatarProps): import("react").JSX.Element;
|
|
84
87
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Hint } from './hint.js';
|
|
3
4
|
import { emojiTint } from './emoji-tint.js';
|
|
4
5
|
import { glyphFace } from './glyph-face.js';
|
|
5
6
|
import { glyphForeground, glyphTint } from './glyph-tint.js';
|
|
@@ -48,12 +49,12 @@ function resolveGlyph(glyph) {
|
|
|
48
49
|
const GlyphComponent = glyphFace(glyph.name);
|
|
49
50
|
return GlyphComponent ? { GlyphComponent, color: glyph.color } : null;
|
|
50
51
|
}
|
|
51
|
-
export function EntityAvatar({ label, glyph, emoji, icon: IconComponent, size = 'md', className, }) {
|
|
52
|
+
export function EntityAvatar({ hint, label, glyph, emoji, icon: IconComponent, size = 'md', className, }) {
|
|
52
53
|
const sizes = SIZE_MAP[size];
|
|
53
54
|
const initial = (label?.trim()?.charAt(0) || '?').toUpperCase();
|
|
54
55
|
const chalk = chalkColors(`${label?.trim()}` || initial);
|
|
55
56
|
const resolvedGlyph = resolveGlyph(glyph);
|
|
56
|
-
|
|
57
|
+
const tile = (_jsx("span", { "data-slot": "entity-avatar",
|
|
57
58
|
// chalkColors() is an inline style, so it beats any class a caller
|
|
58
59
|
// passes. A glyph or an emoji is already the colour — sitting either on
|
|
59
60
|
// a saturated hash-derived pastel reads as noise, and in dark mode that
|
|
@@ -103,4 +104,5 @@ export function EntityAvatar({ label, glyph, emoji, icon: IconComponent, size =
|
|
|
103
104
|
// before the label that says the same thing. Same treatment as the
|
|
104
105
|
// picker trigger in packages/ui/src/workspace/projects/modal/project-icon-field.tsx.
|
|
105
106
|
_jsx("span", { "aria-hidden": true, className: "leading-none", children: emoji })) : IconComponent ? (_jsx(IconComponent, { className: sizes.icon })) : (initial) }));
|
|
107
|
+
return hint ? (_jsx(Hint, { side: "top", sideOffset: 6, alignOffset: 0, label: hint, children: tile })) : (tile);
|
|
106
108
|
}
|
|
@@ -42,6 +42,8 @@ export interface UserAvatarProps {
|
|
|
42
42
|
className?: string;
|
|
43
43
|
ring?: boolean;
|
|
44
44
|
variant?: 'default' | 'primary';
|
|
45
|
+
/** Shows on hover, through `Hint`: usually the person's name or email. */
|
|
46
|
+
hint?: React.ReactNode;
|
|
45
47
|
}
|
|
46
|
-
export declare function UserAvatar({ email, name, avatarUrl, size, variant, className, ring, }: UserAvatarProps): React.JSX.Element;
|
|
48
|
+
export declare function UserAvatar({ hint, email, name, avatarUrl, size, variant, className, ring, }: UserAvatarProps): React.JSX.Element;
|
|
47
49
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Hint } from './hint.js';
|
|
3
4
|
import { Avatar, AvatarFallback, AvatarImage } from './avatar.js';
|
|
4
5
|
import { cn } from '../lib/utils.js';
|
|
5
6
|
import { AVATAR_INK, avatarFill } from '../_vendor/kortix-shared.js';
|
|
@@ -34,13 +35,14 @@ const SIZE_MAP = {
|
|
|
34
35
|
lg: { box: 'size-10', text: 'text-sm', initials: 2 },
|
|
35
36
|
xl: { box: 'size-14', text: 'text-base', initials: 2 },
|
|
36
37
|
};
|
|
37
|
-
export function UserAvatar({ email, name, avatarUrl, size = 'md', variant = 'default', className, ring = false, }) {
|
|
38
|
+
export function UserAvatar({ hint, email, name, avatarUrl, size = 'md', variant = 'default', className, ring = false, }) {
|
|
38
39
|
const sizes = SIZE_MAP[size] ?? SIZE_MAP.md;
|
|
39
40
|
const initials = React.useMemo(() => initialsFromIdentity(name ?? undefined, email || '').slice(0, sizes.initials), [name, email, sizes.initials]);
|
|
40
41
|
// Avatar v2: a solid fill keyed on the email, so a rename keeps the colour.
|
|
41
42
|
const fill = avatarFill(email || name || '');
|
|
42
|
-
|
|
43
|
+
const avatar = (_jsxs(Avatar, { className: cn(sizes.box, 'shrink-0 overflow-hidden rounded-sm p-0 font-medium', ring && 'ring-background ring-2',
|
|
43
44
|
// A photo keeps an edge on a light page: a 1px inset hairline over it.
|
|
44
45
|
avatarUrl &&
|
|
45
46
|
'after:ring-border after:pointer-events-none after:absolute after:inset-0 after:rounded-sm after:ring-1 after:ring-inset', variant === 'primary' && 'bg-primary text-primary-foreground', className), children: [avatarUrl ? _jsx(AvatarImage, { src: avatarUrl, alt: name || email }) : null, _jsx(AvatarFallback, { className: cn('rounded-sm font-medium', sizes.text), style: { backgroundColor: fill, color: AVATAR_INK }, children: initials || '?' })] }));
|
|
47
|
+
return hint ? (_jsx(Hint, { side: "top", sideOffset: 6, alignOffset: 0, label: hint, children: avatar })) : (avatar);
|
|
46
48
|
}
|
|
@@ -145,7 +145,7 @@ export interface UiHostConfig {
|
|
|
145
145
|
terminalEnabled: boolean;
|
|
146
146
|
/**
|
|
147
147
|
* The Terminal panel offers "Connect from your machine": the deployment's
|
|
148
|
-
* CLI installer (`<origin>/install`) and `kortix sessions
|
|
148
|
+
* CLI installer (`<origin>/install`) and `kortix sessions shell`. A host
|
|
149
149
|
* that serves no installer and ships no Kortix CLI sets false, and the bar
|
|
150
150
|
* is hidden. Default: true.
|
|
151
151
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Agent } from '@kortix/sdk/react';
|
|
2
2
|
/** The label `AgentSelector` shows for a selection (null = nothing picked). Other surfaces that name the agent reuse it. */
|
|
3
3
|
export declare function agentDisplayLabel(agents: Agent[], selectedAgent: string | null): string;
|
|
4
|
-
export declare function AgentSelector({ agents, selectedAgent, onSelect, disabled, triggerLabelClassName, unavailableHint, open: controlledOpen, onOpenChange, }: {
|
|
4
|
+
export declare function AgentSelector({ agents, selectedAgent, onSelect, disabled, triggerLabelClassName, unavailableHint, open: controlledOpen, onOpenChange, appearance, }: {
|
|
5
5
|
agents: Agent[];
|
|
6
6
|
selectedAgent: string | null;
|
|
7
7
|
onSelect: (agentName: string | null) => void;
|
|
@@ -17,4 +17,10 @@ export declare function AgentSelector({ agents, selectedAgent, onSelect, disable
|
|
|
17
17
|
unavailableHint?: string | null;
|
|
18
18
|
open?: boolean;
|
|
19
19
|
onOpenChange?: (open: boolean) => void;
|
|
20
|
+
/**
|
|
21
|
+
* `chip` is the composer's borderless toolbar chip. `field` is a form field:
|
|
22
|
+
* the full-width `h-9` box every `SelectTrigger` in a form draws, so the
|
|
23
|
+
* picker reads as one more field instead of a chip inside a frame.
|
|
24
|
+
*/
|
|
25
|
+
appearance?: 'chip' | 'field';
|
|
20
26
|
}): import("react").JSX.Element;
|
|
@@ -7,8 +7,10 @@ import Hint from '../../primitives/hint.js';
|
|
|
7
7
|
import { useTranslations } from '../../i18n/index.js';
|
|
8
8
|
import { useUiHostConfig } from '../../providers/ui-provider.js';
|
|
9
9
|
import { cn } from '../../lib/utils.js';
|
|
10
|
+
import { TRIGGER_CARET_CLASS, triggerVariants } from '../../primitives/trigger-variants.js';
|
|
10
11
|
import { capitalizeWords, isMetaAgentName, META_AGENT_DISPLAY_NAME } from '../../_vendor/kortix-shared.js';
|
|
11
|
-
import { CaretDownIcon, CheckIcon
|
|
12
|
+
import { CaretDownIcon, CheckIcon } from '@phosphor-icons/react';
|
|
13
|
+
import { AgentMark } from '../../internal/components/agent-mark.js';
|
|
12
14
|
import { useEffect, useMemo, useState } from 'react';
|
|
13
15
|
import { composerSelectableAgents } from './composer-agent-access.js';
|
|
14
16
|
/**
|
|
@@ -36,7 +38,7 @@ export function agentDisplayLabel(agents, selectedAgent) {
|
|
|
36
38
|
return 'Agent';
|
|
37
39
|
return isMetaAgentName(name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(name);
|
|
38
40
|
}
|
|
39
|
-
export function AgentSelector({ agents, selectedAgent, onSelect, disabled = false, triggerLabelClassName, unavailableHint, open: controlledOpen, onOpenChange, }) {
|
|
41
|
+
export function AgentSelector({ agents, selectedAgent, onSelect, disabled = false, triggerLabelClassName, unavailableHint, open: controlledOpen, onOpenChange, appearance = 'chip', }) {
|
|
40
42
|
const tHardcodedUi = useTranslations('hardcodedUi');
|
|
41
43
|
const t = useTranslations('threads');
|
|
42
44
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
@@ -84,7 +86,13 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
|
|
|
84
86
|
// matching the resolver's own first-accessible pre-selection.
|
|
85
87
|
const displayedName = resolveDisplayedAgentName(primaryAgents, selectedAgent);
|
|
86
88
|
const displayName = displayedName ? label(displayedName) : agentDisplayLabel(offered, selectedAgent);
|
|
87
|
-
|
|
89
|
+
// The trigger leads with the agent's pixel mark, the same face it has on the
|
|
90
|
+
// Agents page and in every row below. Only for a real agent: a placeholder
|
|
91
|
+
// name such as a trigger's `default` would get a mark of its own, and that
|
|
92
|
+
// face would belong to no agent.
|
|
93
|
+
const triggerMark = displayedName && primaryAgents.some((agent) => agent.name === displayedName) ? (_jsx(AgentMark, { slug: displayedName })) : null;
|
|
94
|
+
const field = appearance === 'field';
|
|
95
|
+
const fieldTrigger = (caret, props) => (_jsxs("button", { type: "button", className: cn(triggerVariants(), 'h-9 w-full'), ...props, children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [triggerMark, _jsx("span", { className: "truncate", children: displayName })] }), caret ? _jsx(CaretDownIcon, { className: cn(TRIGGER_CARET_CLASS, 'size-4') }) : null] }));
|
|
88
96
|
/**
|
|
89
97
|
* One agent: name, its type, its description, and a check when it is the
|
|
90
98
|
* one in use.
|
|
@@ -111,10 +119,7 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
|
|
|
111
119
|
className: cn('items-start gap-2 py-2', isSelected && 'bg-primary/[0.06]'), onSelect: () => {
|
|
112
120
|
onSelect(agent.name);
|
|
113
121
|
setOpen(false);
|
|
114
|
-
}, children: [meta && (
|
|
115
|
-
// Meta is the platform agent that runs the others, so its row leads
|
|
116
|
-
// with its own mark instead of reading as one more peer in the list.
|
|
117
|
-
_jsx("span", { className: "bg-muted flex size-8 shrink-0 items-center justify-center rounded-md", children: _jsx(SparkleIcon, { weight: "fill", className: "text-foreground size-4" }) })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx("span", { className: "text-foreground truncate text-sm font-medium", children: label(agent.name) }), meta && (_jsx(Badge, { variant: "outline", size: "xs", className: "shrink-0 font-normal", children: tHardcodedUi.raw('i18nComplete.textc78ffe195710') }))] }), agent.description && (_jsx("p", { className: "text-muted-foreground mt-0.5 truncate text-xs", children: agent.description }))] }), isSelected && (
|
|
122
|
+
}, children: [agent.description ? (_jsx(AgentMark, { slug: agent.name, size: 28, tile: 36 })) : (_jsx(AgentMark, { slug: agent.name, size: 14 })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx("span", { className: "text-foreground truncate text-sm font-medium", children: label(agent.name) }), meta && (_jsx(Badge, { variant: "outline", size: "xs", className: "shrink-0 font-normal", children: tHardcodedUi.raw('i18nComplete.textc78ffe195710') }))] }), agent.description && (_jsx("p", { className: "text-muted-foreground truncate text-xs", children: agent.description }))] }), isSelected && (
|
|
118
123
|
// `mt-0.5` optically centres the 16px check against the first line
|
|
119
124
|
// rather than the top edge of the text box.
|
|
120
125
|
_jsx(CheckIcon, { className: "text-foreground mt-0.5 size-4 shrink-0" }))] }, agent.name));
|
|
@@ -135,7 +140,7 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
|
|
|
135
140
|
* open — in exactly the state where its words matter.
|
|
136
141
|
*/
|
|
137
142
|
if (primaryAgents.length === 0) {
|
|
138
|
-
const trigger = (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", disabled: true, "aria-label": unavailableHint ?? undefined, className: "text-muted-foreground rounded-lg hit-area-y-3", children: [_jsx("span", { className: cn('max-w-[100px] truncate', triggerLabelClassName), children: displayName }), _jsx(CaretDownIcon, { className: "size-3" })] }));
|
|
143
|
+
const trigger = field ? (fieldTrigger(false, { disabled: true, 'aria-label': unavailableHint ?? undefined })) : (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", disabled: true, "aria-label": unavailableHint ?? undefined, className: "text-muted-foreground rounded-lg hit-area-y-3", children: [_jsx("span", { className: cn('max-w-[100px] truncate', triggerLabelClassName), children: displayName }), _jsx(CaretDownIcon, { className: "size-3" })] }));
|
|
139
144
|
if (!unavailableHint)
|
|
140
145
|
return trigger;
|
|
141
146
|
return (_jsx(Hint, { side: "top", label: unavailableHint, children: _jsx("span", { className: "inline-flex", children: trigger }) }));
|
|
@@ -148,7 +153,12 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
|
|
|
148
153
|
* text every inert control on this rail wears, and the disabled attribute.
|
|
149
154
|
*/
|
|
150
155
|
if (disabled) {
|
|
151
|
-
|
|
156
|
+
if (field)
|
|
157
|
+
return fieldTrigger(false, { disabled: true });
|
|
158
|
+
return (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", disabled: true, className: "text-muted-foreground rounded-lg hit-area-y-3", children: [triggerMark, _jsx("span", { className: cn('max-w-[100px] truncate', triggerLabelClassName), children: displayName })] }));
|
|
152
159
|
}
|
|
153
|
-
return (_jsxs(CommandPopover, { open: open, onOpenChange: setOpen, children: [_jsx(CommandPopoverTrigger, { children: _jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": t('selectAgent'), className: "text-foreground/70 rounded-lg hit-area-y-3", children: [
|
|
160
|
+
return (_jsxs(CommandPopover, { open: open, onOpenChange: setOpen, children: [_jsx(CommandPopoverTrigger, { children: field ? (fieldTrigger(true, { 'aria-label': t('selectAgent') })) : (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": t('selectAgent'), className: "text-foreground/70 rounded-lg hit-area-y-3", children: [triggerMark, _jsx("span", { className: cn('max-w-[100px] truncate', triggerLabelClassName), children: displayName }), _jsx(CaretDownIcon, { className: cn('size-3 transition-transform duration-200 ease-out', open && 'rotate-180') })] })) }), _jsxs(CommandPopoverContent, { side: "top", align: "start", sideOffset: 8, className: "w-[min(300px,calc(100vw-1.5rem))]", children: [_jsx("div", { className: showSearch ? undefined : 'sr-only', children: _jsx(CommandInput, { compact: true, placeholder: tHardcodedUi.raw('componentsSessionSessionChatInput.line231JsxAttrPlaceholderSearchAgents'), value: search, onValueChange: setSearch }) }), _jsxs(CommandList, { className: "max-h-[320px]", children: [filteredPrimary.length > 0 && (
|
|
161
|
+
// `gap-px` between rows: a selected row and a hovered row next to it
|
|
162
|
+
// otherwise draw one merged grey block.
|
|
163
|
+
_jsxs(CommandGroup, { forceMount: true, className: "[&_[cmdk-group-items]]:flex [&_[cmdk-group-items]]:flex-col [&_[cmdk-group-items]]:gap-px", children: [filteredMeta.map((agent) => renderAgentItem(agent, true)), filteredMeta.length > 0 && filteredProject.length > 0 && (_jsx(CommandSeparator, { className: "my-1" })), filteredProject.map((agent) => renderAgentItem(agent, false))] })), filteredPrimary.length === 0 && search.trim() && (_jsxs("div", { className: "text-muted-foreground/50 py-8 text-center text-xs", children: [tHardcodedUi.raw('componentsSessionSessionChatInput.line273JsxTextNoAgentsMatchLdquo'), search.trim(), tHardcodedUi.raw('componentsSessionSessionChatInput.line273JsxTextRdquo')] }))] })] })] }));
|
|
154
164
|
}
|
|
@@ -13,6 +13,11 @@ export interface ContextBreakdown {
|
|
|
13
13
|
* compaction, whose usage is its summary reply's) with a positive token total.
|
|
14
14
|
* `sessionLogThreadMetrics(...).context` picks the same message, but prices the
|
|
15
15
|
* whole thread to do it.
|
|
16
|
+
*
|
|
17
|
+
* `reasoning` is a SUBSET of `output` (pi-ai `Usage`, and the same convention in
|
|
18
|
+
* OpenCode and the Kortix transcript): adding it again reads a session at 107%
|
|
19
|
+
* of a window the model has not filled, and hides the compaction that should
|
|
20
|
+
* have run. It stays in the breakdown for display, outside `total`.
|
|
16
21
|
*/
|
|
17
22
|
export declare function getLastAssistantTokenBreakdown(messages: SessionLogMessage[] | undefined): ContextBreakdown;
|
|
18
23
|
export declare function getLastAssistantTokenTotal(messages: SessionLogMessage[] | undefined): number;
|
|
@@ -10,6 +10,11 @@ const EMPTY_BREAKDOWN = {
|
|
|
10
10
|
* compaction, whose usage is its summary reply's) with a positive token total.
|
|
11
11
|
* `sessionLogThreadMetrics(...).context` picks the same message, but prices the
|
|
12
12
|
* whole thread to do it.
|
|
13
|
+
*
|
|
14
|
+
* `reasoning` is a SUBSET of `output` (pi-ai `Usage`, and the same convention in
|
|
15
|
+
* OpenCode and the Kortix transcript): adding it again reads a session at 107%
|
|
16
|
+
* of a window the model has not filled, and hides the compaction that should
|
|
17
|
+
* have run. It stays in the breakdown for display, outside `total`.
|
|
13
18
|
*/
|
|
14
19
|
export function getLastAssistantTokenBreakdown(messages) {
|
|
15
20
|
if (!messages)
|
|
@@ -25,7 +30,7 @@ export function getLastAssistantTokenBreakdown(messages) {
|
|
|
25
30
|
const output = t.output;
|
|
26
31
|
const reasoning = t.reasoning ?? 0;
|
|
27
32
|
const cache = t.cache_read + t.cache_write;
|
|
28
|
-
const total = input + output +
|
|
33
|
+
const total = input + output + cache;
|
|
29
34
|
if (total > 0)
|
|
30
35
|
return { input, output, reasoning, cache, total };
|
|
31
36
|
}
|
|
@@ -40,15 +40,15 @@ const HEADLINE_TIP_KEY = {
|
|
|
40
40
|
};
|
|
41
41
|
/**
|
|
42
42
|
* Breakdown rows, in a fixed order chosen by size rather than by the order the
|
|
43
|
-
* API happens to emit them: input dominates, cache is next, output
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* API happens to emit them: input dominates, cache is next, output is the tail.
|
|
44
|
+
* A fixed order matters more than a sorted one here — rows must not reshuffle
|
|
45
|
+
* under the cursor as tokens stream in. `reasoning` is not a row: it is a subset
|
|
46
|
+
* of `output`, so counting it here would read context the model never sends.
|
|
46
47
|
*/
|
|
47
48
|
const BREAKDOWN_ROWS = [
|
|
48
49
|
{ key: 'input', labelKey: 'labelInput' },
|
|
49
50
|
{ key: 'cache', labelKey: 'labelCached' },
|
|
50
51
|
{ key: 'output', labelKey: 'labelOutput' },
|
|
51
|
-
{ key: 'reasoning', labelKey: 'labelReasoning' },
|
|
52
52
|
];
|
|
53
53
|
/**
|
|
54
54
|
* Exported for the `/` palette: the "Show context" row's detail pane renders
|
|
@@ -9,8 +9,9 @@ interface TranscriptSessionHeader {
|
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* The transcript's header: the runtime's session while the computer runs, else
|
|
12
|
-
* the session row the page shows (asleep, stopped, failed).
|
|
13
|
-
*
|
|
12
|
+
* the session row the page shows (asleep, stopped, failed). The start is the row's
|
|
13
|
+
* (`sessionStartedAt`): a pi box stamps its runtime session at boot. Null: nothing
|
|
14
|
+
* to name the export after yet.
|
|
14
15
|
*/
|
|
15
16
|
export declare function transcriptSessionHeader(runtime: {
|
|
16
17
|
id: string;
|
|
@@ -14,14 +14,17 @@ import { loadSessionExportMessages, useProjectSession, useRuntimeSession, useSes
|
|
|
14
14
|
import { CheckIcon as Check, CopyIcon as Copy, } from '@phosphor-icons/react';
|
|
15
15
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
16
16
|
import { Download } from '../../internal/icons/download.js';
|
|
17
|
+
import { sessionStartedAt } from '../thread/session-context-metrics.js';
|
|
17
18
|
/**
|
|
18
19
|
* The transcript's header: the runtime's session while the computer runs, else
|
|
19
|
-
* the session row the page shows (asleep, stopped, failed).
|
|
20
|
-
*
|
|
20
|
+
* the session row the page shows (asleep, stopped, failed). The start is the row's
|
|
21
|
+
* (`sessionStartedAt`): a pi box stamps its runtime session at boot. Null: nothing
|
|
22
|
+
* to name the export after yet.
|
|
21
23
|
*/
|
|
22
24
|
export function transcriptSessionHeader(runtime, row, sessionId) {
|
|
23
25
|
if (runtime) {
|
|
24
|
-
|
|
26
|
+
const created = sessionStartedAt(runtime, row) ?? runtime.time.created;
|
|
27
|
+
return { id: runtime.id, title: runtime.title || runtime.slug || 'Untitled', time: { ...runtime.time, created } };
|
|
25
28
|
}
|
|
26
29
|
if (!row)
|
|
27
30
|
return null;
|
|
@@ -56,6 +56,8 @@ export interface ModelSelectorProps {
|
|
|
56
56
|
triggerLabelClassName?: string;
|
|
57
57
|
/** `secondary` for a page-level control; the composer keeps `ghost`. */
|
|
58
58
|
triggerVariant?: 'ghost' | 'secondary';
|
|
59
|
+
/** `field`: the full-width `h-9` form-field box, the same as `AgentSelector`'s. */
|
|
60
|
+
appearance?: 'chip' | 'field';
|
|
59
61
|
projectId?: string;
|
|
60
62
|
/**
|
|
61
63
|
* Offer the member's own ChatGPT subscription (pooled provider secrets) at
|
|
@@ -83,4 +85,4 @@ export interface ModelSelectorProps {
|
|
|
83
85
|
open?: boolean;
|
|
84
86
|
onOpenChange?: (open: boolean) => void;
|
|
85
87
|
}
|
|
86
|
-
export declare function ModelSelector({ models, selectedModel, onSelect, defaultControls, unsetLabel, disabled, modelsLoading, triggerLabelClassName, triggerVariant, offerProviderAccounts, open: openProp, onOpenChange, }: ModelSelectorProps): import("react").JSX.Element;
|
|
88
|
+
export declare function ModelSelector({ models, selectedModel, onSelect, defaultControls, unsetLabel, disabled, modelsLoading, triggerLabelClassName, triggerVariant, appearance, offerProviderAccounts, open: openProp, onOpenChange, }: ModelSelectorProps): import("react").JSX.Element;
|
|
@@ -5,6 +5,7 @@ import { CommandGroup, CommandInput, CommandItem, CommandList, CommandPopover, C
|
|
|
5
5
|
import Loading from '../../primitives/loading.js';
|
|
6
6
|
import { MODEL_SELECTOR_PROVIDER_IDS, ProviderLogo } from '../../internal/lib/provider-branding.js';
|
|
7
7
|
import { cn } from '../../lib/utils.js';
|
|
8
|
+
import { TRIGGER_CARET_CLASS, triggerVariants } from '../../primitives/trigger-variants.js';
|
|
8
9
|
import { useProviderModalStore } from '../../internal/stores/provider-modal-store.js';
|
|
9
10
|
import { useFeatureFlag, useModelAccess, useModelStore, } from '@kortix/sdk/react';
|
|
10
11
|
import { CheckIcon as Check, CaretDownIcon as ChevronDown, CaretRightIcon as CaretRight, CreditCardIcon as CreditCard, KeyIcon as KeyRound, PlusIcon as Plus, SlidersHorizontalIcon as SlidersHorizontal, StarIcon as Star, } from '@phosphor-icons/react';
|
|
@@ -136,7 +137,7 @@ function ModelRow({ model, groupProviderID, groupProviderName, isSelected, isAcc
|
|
|
136
137
|
function GroupHeading({ children }) {
|
|
137
138
|
return _jsx("span", { className: "text-muted-foreground font-medium tracking-wide", children: children });
|
|
138
139
|
}
|
|
139
|
-
export function ModelSelector({ models, selectedModel, onSelect, defaultControls, unsetLabel, disabled = false, modelsLoading = false, triggerLabelClassName, triggerVariant = 'ghost', offerProviderAccounts = false, open: openProp, onOpenChange, }) {
|
|
140
|
+
export function ModelSelector({ models, selectedModel, onSelect, defaultControls, unsetLabel, disabled = false, modelsLoading = false, triggerLabelClassName, triggerVariant = 'ghost', appearance = 'chip', offerProviderAccounts = false, open: openProp, onOpenChange, }) {
|
|
140
141
|
const tModel = useTranslations('modelSelector');
|
|
141
142
|
const t = useTranslations('threads');
|
|
142
143
|
// Controlled when `open` is supplied, uncontrolled otherwise — Radix's own
|
|
@@ -303,7 +304,7 @@ export function ModelSelector({ models, selectedModel, onSelect, defaultControls
|
|
|
303
304
|
setOpen(false);
|
|
304
305
|
openUpgrade();
|
|
305
306
|
}, [openUpgrade, setOpen]);
|
|
306
|
-
return (_jsxs(_Fragment, { children: [connectionModal, projectId && chatGptPossible && ProviderAccountsModal && (_jsx(ProviderAccountsModal, { projectId: projectId, providerId: "codex", open: chatGptOpen, onOpenChange: setChatGptOpen })), _jsxs(CommandPopover, { open: disabled ? false : open, onOpenChange: (next) => !disabled && setOpen(next), children: [_jsx(CommandPopoverTrigger, { children: _jsxs(Button, { type: "button", variant: triggerVariant, size: "sm", className: cn('rounded-lg hit-area-y-3', triggerVariant === 'ghost' && 'text-muted-foreground'), children: [_jsx("span", { className: cn('max-w-30 truncate', triggerLabelClassName), children: displayName }), _jsx(ChevronDown, { className: cn('duration-moderate size-3 transition-transform ease-out', open && 'rotate-180') })] }) }), _jsx(CommandPopoverContent, { side: "top", align: "start", sideOffset: 8, className: "w-[min(300px,calc(100vw-1.5rem))]", children: _jsxs(_Fragment, { children: [_jsx(CommandInput, { compact: true, placeholder: tModel('search'), value: search, onValueChange: setSearch, rightElement: canManageModels && canConnectProvider ? (_jsxs("div", { className: "-mr-0.5 flex shrink-0 items-center gap-0.5", children: [_jsx(Hint, { label: tModel('connectProvider'), side: "top", className: "z-50", children: _jsx("button", { type: "button", "aria-label": tModel('addProvider'), onClick: () => handleOpenProviderModal('providers'), className: "text-muted-foreground hover:text-foreground hover:bg-muted flex size-8 cursor-pointer items-center justify-center rounded-md transition-colors", children: _jsx(Plus, { className: "size-4" }) }) }), _jsx(Hint, { label: tModel('manageModels'), side: "top", className: "z-50", children: _jsx("button", { type: "button", "aria-label": tModel('manageModels'), onClick: () => handleOpenProviderModal('models'), className: "text-muted-foreground hover:text-foreground hover:bg-muted flex size-8 cursor-pointer items-center justify-center rounded-md transition-colors", children: _jsx(SlidersHorizontal, { className: "size-4" }) }) })] })) : undefined }), grouped.length > 1 && _jsx(CommandSeparator, { className: "bg-border/60" }), _jsx(CommandList, { className: "max-h-[380px]", children: modelsLoading || entitlementsPending ? (_jsx("div", { className: "flex min-h-32 items-center justify-center", role: "status", "aria-label": tModel('loading'), children: _jsx(Loading, { className: "text-muted-foreground size-4 shrink-0" }) })) : grouped.length > 0 ? (_jsxs(_Fragment, { children: [pinnedDefault && (_jsxs(_Fragment, { children: [_jsx(CommandGroup, { heading: _jsx(GroupHeading, { children: tModel('yourDefault') }), forceMount: true, children: _jsx(ModelRow, { model: pinnedDefault.model, groupProviderID: pinnedDefault.providerID, groupProviderName: pinnedDefault.providerName, isSelected: availableSelectedModel?.providerID === pinnedDefault.model.providerID &&
|
|
307
|
+
return (_jsxs(_Fragment, { children: [connectionModal, projectId && chatGptPossible && ProviderAccountsModal && (_jsx(ProviderAccountsModal, { projectId: projectId, providerId: "codex", open: chatGptOpen, onOpenChange: setChatGptOpen })), _jsxs(CommandPopover, { open: disabled ? false : open, onOpenChange: (next) => !disabled && setOpen(next), children: [_jsx(CommandPopoverTrigger, { children: appearance === 'field' ? (_jsxs("button", { type: "button", disabled: disabled, className: cn(triggerVariants(), 'h-9 w-full'), children: [_jsx("span", { className: "truncate", children: displayName }), disabled ? null : _jsx(ChevronDown, { className: cn(TRIGGER_CARET_CLASS, 'size-4') })] })) : (_jsxs(Button, { type: "button", variant: triggerVariant, size: "sm", className: cn('rounded-lg hit-area-y-3', triggerVariant === 'ghost' && 'text-muted-foreground'), children: [_jsx("span", { className: cn('max-w-30 truncate', triggerLabelClassName), children: displayName }), _jsx(ChevronDown, { className: cn('duration-moderate size-3 transition-transform ease-out', open && 'rotate-180') })] })) }), _jsx(CommandPopoverContent, { side: "top", align: "start", sideOffset: 8, className: "w-[min(300px,calc(100vw-1.5rem))]", children: _jsxs(_Fragment, { children: [_jsx(CommandInput, { compact: true, placeholder: tModel('search'), value: search, onValueChange: setSearch, rightElement: canManageModels && canConnectProvider ? (_jsxs("div", { className: "-mr-0.5 flex shrink-0 items-center gap-0.5", children: [_jsx(Hint, { label: tModel('connectProvider'), side: "top", className: "z-50", children: _jsx("button", { type: "button", "aria-label": tModel('addProvider'), onClick: () => handleOpenProviderModal('providers'), className: "text-muted-foreground hover:text-foreground hover:bg-muted flex size-8 cursor-pointer items-center justify-center rounded-md transition-colors", children: _jsx(Plus, { className: "size-4" }) }) }), _jsx(Hint, { label: tModel('manageModels'), side: "top", className: "z-50", children: _jsx("button", { type: "button", "aria-label": tModel('manageModels'), onClick: () => handleOpenProviderModal('models'), className: "text-muted-foreground hover:text-foreground hover:bg-muted flex size-8 cursor-pointer items-center justify-center rounded-md transition-colors", children: _jsx(SlidersHorizontal, { className: "size-4" }) }) })] })) : undefined }), grouped.length > 1 && _jsx(CommandSeparator, { className: "bg-border/60" }), _jsx(CommandList, { className: "max-h-[380px]", children: modelsLoading || entitlementsPending ? (_jsx("div", { className: "flex min-h-32 items-center justify-center", role: "status", "aria-label": tModel('loading'), children: _jsx(Loading, { className: "text-muted-foreground size-4 shrink-0" }) })) : grouped.length > 0 ? (_jsxs(_Fragment, { children: [pinnedDefault && (_jsxs(_Fragment, { children: [_jsx(CommandGroup, { heading: _jsx(GroupHeading, { children: tModel('yourDefault') }), forceMount: true, children: _jsx(ModelRow, { model: pinnedDefault.model, groupProviderID: pinnedDefault.providerID, groupProviderName: pinnedDefault.providerName, isSelected: availableSelectedModel?.providerID === pinnedDefault.model.providerID &&
|
|
307
308
|
availableSelectedModel?.modelID === pinnedDefault.model.modelID, isAccountDefault: true, defaultControls: rowDefaultControls, onSelect: handleSelect, connectLabel: tPooled('connect'), scope: "pinned" }) }), _jsx(CommandSeparator, {})] })), sections.map((section, sectionIndex) => {
|
|
308
309
|
const selectedInSection = section.models.find((m) => availableSelectedModel?.providerID === m.providerID &&
|
|
309
310
|
availableSelectedModel?.modelID === m.modelID);
|
|
@@ -10,11 +10,13 @@ import { type TodoItem } from '../../tool/shared/todo-helpers.js';
|
|
|
10
10
|
* single row.
|
|
11
11
|
*
|
|
12
12
|
* Pure, so both branches are testable without mounting anything.
|
|
13
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
13
14
|
*/
|
|
14
15
|
export declare function planPanelRows(todos: ReadonlyArray<TodoItem>): Array<{
|
|
15
16
|
todo: TodoItem;
|
|
16
17
|
key: string;
|
|
17
18
|
}>;
|
|
19
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
18
20
|
export declare function PlanPanelCard({ sessionId }: {
|
|
19
21
|
sessionId: string;
|
|
20
22
|
}): import("react").JSX.Element | null;
|
|
@@ -64,6 +64,7 @@ function PlanProgress({ done, total, running }) {
|
|
|
64
64
|
* single row.
|
|
65
65
|
*
|
|
66
66
|
* Pure, so both branches are testable without mounting anything.
|
|
67
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
67
68
|
*/
|
|
68
69
|
export function planPanelRows(todos) {
|
|
69
70
|
// Keyed first, filtered second — see `planListTodos`: keying first is what
|
|
@@ -72,6 +73,7 @@ export function planPanelRows(todos) {
|
|
|
72
73
|
const listed = planListTodos(keyed);
|
|
73
74
|
return listed.length > 0 ? listed : keyed;
|
|
74
75
|
}
|
|
76
|
+
/** @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode. */
|
|
75
77
|
export function PlanPanelCard({ sessionId }) {
|
|
76
78
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
77
79
|
const { data } = useRuntimeSessionTodo(sessionId);
|
|
@@ -5,5 +5,6 @@
|
|
|
5
5
|
* `isActionPanelOpen` / `isSidePanelOpen` / `panelMode`: those made the plan
|
|
6
6
|
* hop surfaces on every panel toggle, and they also re-rendered the whole
|
|
7
7
|
* transcript each time one of them changed.
|
|
8
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
8
9
|
*/
|
|
9
10
|
export declare function usePlanInChat(): boolean;
|
|
@@ -23,6 +23,7 @@ import { planBelongsToChat } from '../turn/plan-anchor.js';
|
|
|
23
23
|
* `isActionPanelOpen` / `isSidePanelOpen` / `panelMode`: those made the plan
|
|
24
24
|
* hop surfaces on every panel toggle, and they also re-rendered the whole
|
|
25
25
|
* transcript each time one of them changed.
|
|
26
|
+
* @deprecated Renders an OpenCode-only tool or todo list in an OpenCode session (pi has none). OpenCode is sunset (2026-10-23): no new code; delete with OpenCode.
|
|
26
27
|
*/
|
|
27
28
|
export function usePlanInChat() {
|
|
28
29
|
return planBelongsToChat({ isMobile: useIsMobile() });
|
|
@@ -3435,7 +3435,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
3435
3435
|
: welcomeWallpaper, !hideHeader && (_jsx(SessionSiteHeader, { sessionId: sessionId, sessionTitle: session?.title || 'Untitled', leadingAction: headerLeadingAction, parent: parentCrumb })), _jsx(SessionContextModal, { open: contextModalOpen, onOpenChange: setContextModalOpen,
|
|
3436
3436
|
// A chat with no `useSession` source: the raw explorer lists the
|
|
3437
3437
|
// rows `useSessionSync` read for it.
|
|
3438
|
-
detachedRows: sessionState ? undefined : hookMessages, source: sessionState ?? DETACHED_SESSION_MESSAGES, session: session, providers: providers, allSessions: allSessions, servedModel: sessionServedModel, billedCost: sessionBilledCost(modelUsage) }), _jsx(CompactModal, { sessionId: sessionId, open: compactModalOpen, onOpenChange: setCompactModalOpen }), _jsx(ChangeRequestDetailDialog, { crId: openCrId, onClose: () => setOpenCrId(null), onOpenChangeRequest: setOpenCrId }), _jsxs(SessionBodyRow, { actionPanel: !hideHeader && !readOnly, children: [isNotFound ? (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 text-center", children: [_jsx("div", { className: "text-muted-foreground text-sm", children: tHardcodedUi.raw('componentsSessionSessionChat.line5821JsxTextThisSessionIsNotAccessibleRightNow') }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { href: "/", prefetch: true, onClick: () => {
|
|
3438
|
+
detachedRows: sessionState ? undefined : hookMessages, source: sessionState ?? DETACHED_SESSION_MESSAGES, session: session, sessionRow: projectSessionRow, providers: providers, allSessions: allSessions, servedModel: sessionServedModel, billedCost: sessionBilledCost(modelUsage) }), _jsx(CompactModal, { sessionId: sessionId, open: compactModalOpen, onOpenChange: setCompactModalOpen }), _jsx(ChangeRequestDetailDialog, { crId: openCrId, onClose: () => setOpenCrId(null), onOpenChangeRequest: setOpenCrId }), _jsxs(SessionBodyRow, { actionPanel: !hideHeader && !readOnly, children: [isNotFound ? (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 text-center", children: [_jsx("div", { className: "text-muted-foreground text-sm", children: tHardcodedUi.raw('componentsSessionSessionChat.line5821JsxTextThisSessionIsNotAccessibleRightNow') }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { href: "/", prefetch: true, onClick: () => {
|
|
3439
3439
|
try {
|
|
3440
3440
|
if (sessionId)
|
|
3441
3441
|
useTabStore.getState().closeTab?.(sessionId);
|
|
@@ -3546,7 +3546,7 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
3546
3546
|
pendingTurnIds.has(turn.id) &&
|
|
3547
3547
|
pendingTurnIds.has(turns[turnIndex - 1].id)
|
|
3548
3548
|
? 'mt-3'
|
|
3549
|
-
: 'mt-12', turn: turn, author: turnAuthor, showAuthor: showAuthorName(turnAuthor, groupChat, viewerId), stoppedBy: stoppedByName(messageAuthors, turn.id, viewerId), servedModel: servedModelOfTurn(modelUsage, turn.id, turns[turnIndex - 1]?.id), turnOutcome: turnOutcome, isLast: turn.id === lastUserMessageId, ownsPlan: turn.id === planAnchorId, sessionId: sessionId, sessionStatus: sessionStatus, permissions: pendingPermissions, questions: pendingQuestions, agentNames: agentNames, isFirstTurn: turnIndex === 0,
|
|
3549
|
+
: 'mt-12', turn: turn, author: turnAuthor, showAuthor: showAuthorName(turnAuthor, groupChat, viewerId), stoppedBy: stoppedByName(messageAuthors, turn.id, viewerId), servedModel: servedModelOfTurn(modelUsage, turn.id, turns[turnIndex - 1]?.id), turnOutcome: turnOutcome, isLast: turn.id === lastUserMessageId, ownsPlan: turn.id === planAnchorId, sessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId, sessionStatus: sessionStatus, permissions: pendingPermissions, questions: pendingQuestions, agentNames: agentNames, isFirstTurn: turnIndex === 0,
|
|
3550
3550
|
// Handed over only once the stand-in has stepped
|
|
3551
3551
|
// aside — while it is up it draws these itself.
|
|
3552
3552
|
pendingText: turnIndex === 0 ? firstTurnHandover?.text : undefined, pendingAttachments: sentAttachmentsForTurn({
|
|
@@ -21,4 +21,19 @@ interface Metrics {
|
|
|
21
21
|
}
|
|
22
22
|
/** Pure token math for the modal — exported for characterization tests. */
|
|
23
23
|
export declare function getSessionContextMetrics(thread: SessionLogThread | null, providers: ProviderListResponse | undefined, pricingLookup: ModelPricingLookup): Metrics;
|
|
24
|
+
/**
|
|
25
|
+
* When the session started. The session row's creation, when the runtime is the row's root (or
|
|
26
|
+
* not up): a pi box stamps its runtime session at boot, so a woken session's runtime time is after
|
|
27
|
+
* its last reply. A sub-agent's runtime (not the row's root) keeps its own time.
|
|
28
|
+
*/
|
|
29
|
+
export declare function sessionStartedAt(runtime: {
|
|
30
|
+
id: string;
|
|
31
|
+
time: {
|
|
32
|
+
created: number;
|
|
33
|
+
};
|
|
34
|
+
} | undefined, row: {
|
|
35
|
+
created_at: string;
|
|
36
|
+
runtime_session_id?: string | null;
|
|
37
|
+
opencode_session_id?: string | null;
|
|
38
|
+
} | null | undefined): number | undefined;
|
|
24
39
|
export {};
|
|
@@ -16,7 +16,7 @@ export function getSessionContextMetrics(thread, providers, pricingLookup) {
|
|
|
16
16
|
? model.provider
|
|
17
17
|
: undefined;
|
|
18
18
|
const limit = model?.limit?.context;
|
|
19
|
-
const total = u.input + u.output +
|
|
19
|
+
const total = u.input + u.output + u.cache_read + u.cache_write; // reasoning is a subset of output
|
|
20
20
|
// The gateway registers every model under the single synthetic `kortix`
|
|
21
21
|
// opencode provider, so `provider.name` is always "Kortix" — even for a
|
|
22
22
|
// BYOK Anthropic/Bedrock/OpenAI model. The gateway separately serves the
|
|
@@ -42,3 +42,12 @@ export function getSessionContextMetrics(thread, providers, pricingLookup) {
|
|
|
42
42
|
},
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* When the session started. The session row's creation, when the runtime is the row's root (or
|
|
47
|
+
* not up): a pi box stamps its runtime session at boot, so a woken session's runtime time is after
|
|
48
|
+
* its last reply. A sub-agent's runtime (not the row's root) keeps its own time.
|
|
49
|
+
*/
|
|
50
|
+
export function sessionStartedAt(runtime, row) {
|
|
51
|
+
const own = row && (!runtime || (row.runtime_session_id ?? row.opencode_session_id) === runtime.id);
|
|
52
|
+
return own ? Date.parse(row.created_at) : runtime?.time.created;
|
|
53
|
+
}
|