@kortix/ui 0.14.4 → 0.14.6
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/README.md +8 -4
- package/dist/access/access/access-dialog.js +8 -5
- package/dist/access/access/copy-row.js +2 -2
- package/dist/access/sharing-picker.js +2 -1
- package/dist/browser/app-in-browser.js +5 -3
- package/dist/browser/browser-panel.js +6 -6
- package/dist/browser/sandbox-browser-chrome.d.ts +4 -1
- package/dist/browser/sandbox-browser-chrome.js +4 -3
- package/dist/customize/agents/agent-access.d.ts +20 -0
- package/dist/customize/agents/agent-access.js +17 -0
- package/dist/customize/agents/agent-grant-pages.js +5 -6
- package/dist/customize/agents/agent-page.js +2 -1
- package/dist/customize/agents/agents-page.js +18 -1
- package/dist/customize/connectors/add-mcp/add-mcp-server.js +4 -2
- package/dist/customize/connectors/connector-routes.d.ts +3 -1
- package/dist/customize/connectors/connector-routes.js +18 -0
- package/dist/customize/connectors/connectors-page.js +23 -4
- package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
- package/dist/customize/providers/provider-accordion.js +2 -1
- package/dist/customize/providers/provider-connect.js +2 -1
- package/dist/customize/sections/component/telegram-channel-row.js +1 -1
- package/dist/customize/sections/connector-connection-modal.js +11 -9
- package/dist/customize/sections/connectors-view.js +11 -13
- package/dist/customize/sections/llm-provider/constants.d.ts +0 -1
- package/dist/customize/sections/llm-provider/constants.js +0 -2
- package/dist/customize/sections/llm-provider/models-tab-group.d.ts +1 -1
- package/dist/customize/sections/llm-provider/models-tab-group.js +7 -4
- package/dist/customize/sections/llm-provider/models-tab-row.d.ts +2 -18
- package/dist/customize/sections/llm-provider/models-tab-row.js +6 -30
- package/dist/customize/sections/llm-provider/models-tab.d.ts +0 -1
- package/dist/customize/sections/llm-provider/models-tab.js +1 -10
- package/dist/customize/sections/llm-provider/use-connected-providers.js +2 -2
- package/dist/customize/sections/llm-provider/utils.d.ts +1 -1
- package/dist/customize/sections/llm-provider/utils.js +7 -14
- package/dist/customize/sections/view/gateway/_shared.js +1 -2
- package/dist/customize/sections/view/gateway/gateway-routing.js +18 -20
- package/dist/customize/sections/view/sandbox-provider-coverage.d.ts +3 -18
- package/dist/customize/sections/view/sandbox-provider-coverage.js +7 -27
- package/dist/customize/settings/rail-copy-context.js +5 -3
- package/dist/customize/settings/tabs/sandbox-tab.d.ts +2 -27
- package/dist/customize/settings/tabs/sandbox-tab.js +13 -112
- package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
- package/dist/customize/shared/capability-tabs.js +2 -1
- package/dist/customize/shared/entity/entity-modal.d.ts +5 -1
- package/dist/customize/shared/entity/entity-modal.js +7 -4
- package/dist/customize/skills/remove-skill-button.d.ts +16 -0
- package/dist/customize/skills/remove-skill-button.js +42 -0
- package/dist/customize/skills/skills-page.js +4 -6
- package/dist/customize/triggers/schedule/event-trigger-copy.js +3 -2
- package/dist/customize/triggers/schedule/schedule-builder.js +7 -4
- package/dist/customize/triggers/schedule/trigger-composer.js +5 -2
- package/dist/customize/triggers/schedule/trigger-list.d.ts +0 -2
- package/dist/customize/triggers/schedule/trigger-list.js +1 -18
- package/dist/customize/triggers/schedule/trigger-tile.d.ts +1 -32
- package/dist/customize/triggers/schedule/trigger-tile.js +1 -16
- package/dist/customize/triggers/schedule/when-event.js +1 -1
- package/dist/diff/diff-view.js +9 -10
- package/dist/files/editor/code-editor.js +2 -3
- package/dist/files/project/components/change-request-detail-dialog.d.ts +3 -1
- package/dist/files/project/components/change-request-detail-dialog.js +3 -5
- package/dist/files/project/components/change-request-status-badge.d.ts +12 -0
- package/dist/files/project/components/change-request-status-badge.js +27 -0
- package/dist/files/project/components/change-requests-panel.js +1 -1
- package/dist/files/project/components/drive-explorer.js +2 -1
- package/dist/files/project/components/drive-grid-view.js +11 -7
- package/dist/files/project/components/drive-list-view.js +4 -4
- package/dist/files/project/components/file-breadcrumbs.js +1 -1
- package/dist/files/project/components/file-explorer-page.js +1 -1
- package/dist/files/project/components/file-history-popover.js +1 -6
- package/dist/files/project/components/file-search.js +6 -1
- package/dist/files/project/components/open-change-request-dialog.js +2 -1
- package/dist/files/project/components/version-selector-logic.d.ts +11 -0
- package/dist/files/project/components/version-selector-logic.js +26 -0
- package/dist/files/project/components/version-selector.js +11 -20
- package/dist/files/project/hooks/use-change-requests.d.ts +14 -1
- package/dist/files/project/hooks/use-change-requests.js +44 -25
- package/dist/files/project/hooks/use-server-health.js +17 -15
- package/dist/files/renderers/docx/docx-viewer.js +4 -6
- package/dist/files/renderers/image-renderer.js +6 -24
- package/dist/files/renderers/pdf/pdf-renderer.d.ts +3 -1
- package/dist/files/renderers/pdf/pdf-renderer.js +2 -2
- package/dist/files/renderers/pdf/pdf-viewer.d.ts +3 -0
- package/dist/files/renderers/pdf/pdf-viewer.js +16 -7
- package/dist/files/renderers/presentation/FullScreenPresentationViewer.js +2 -11
- package/dist/files/renderers/show-content-renderer.js +1 -1
- package/dist/files/renderers/sqlite-renderer.js +8 -3
- package/dist/files/renderers/xlsx/xlsx-viewer.js +15 -16
- package/dist/files/tree/components/file-tree-item.js +1 -1
- package/dist/files/viewer/file-content-renderer.d.ts +4 -1
- package/dist/files/viewer/file-content-renderer.js +2 -2
- package/dist/files/viewer/file-preview-modal.js +1 -1
- package/dist/files/viewer/use-file-content-state.js +1 -1
- package/dist/i18n/en.d.ts +36 -35
- package/dist/i18n/en.js +45 -44
- package/dist/index.d.ts +1 -1
- package/dist/internal/desktop/desktop-back-button.js +5 -3
- package/dist/internal/hooks/use-auto-scroll.js +5 -3
- package/dist/internal/hooks/use-notification-center.js +21 -15
- package/dist/internal/hooks/use-review-session-summary.d.ts +6 -0
- package/dist/internal/hooks/use-review-session-summary.js +11 -0
- package/dist/internal/lib/browser-error-noise.d.ts +1 -1
- package/dist/internal/lib/browser-error-noise.js +1 -1
- package/dist/internal/lib/browser-noise/rules/rejections.d.ts +10 -0
- package/dist/internal/lib/browser-noise/rules/rejections.js +27 -1
- package/dist/internal/lib/provider-branding.js +14 -3
- package/dist/internal/lib/trigger-clock.d.ts +42 -0
- package/dist/internal/lib/trigger-clock.js +34 -0
- package/dist/internal/lib/web-notifications.d.ts +6 -0
- package/dist/internal/lib/web-notifications.js +57 -7
- package/dist/internal/notifications/notification-bell.d.ts +13 -0
- package/dist/internal/notifications/notification-bell.js +1 -1
- package/dist/internal/notifications/notification-rows.js +2 -1
- package/dist/internal/outcomes/outcome-types.d.ts +5 -0
- package/dist/internal/reminders/reminder-filter.d.ts +12 -0
- package/dist/internal/reminders/reminder-filter.js +34 -0
- package/dist/internal/reminders/reminder-format.d.ts +6 -0
- package/dist/internal/reminders/reminder-format.js +20 -2
- package/dist/internal/stores/kortix-computer-store.d.ts +1 -1
- package/dist/internal/stores/session-hover-store.d.ts +20 -5
- package/dist/internal/stores/session-hover-store.js +31 -13
- package/dist/internal/stores/session-panel-memory-store.js +2 -0
- package/dist/markdown/code/inline-code.js +8 -0
- package/dist/markdown/code/output-paths-context.d.ts +18 -0
- package/dist/markdown/code/output-paths-context.js +35 -0
- package/dist/markdown/genui/components/charts.js +6 -5
- package/dist/primitives/count-badge.d.ts +3 -1
- package/dist/primitives/count-badge.js +3 -3
- package/dist/primitives/glyph-paths-source.js +1 -1
- package/dist/primitives/glyph-paths.generated.js +1 -1
- package/dist/primitives/map.js +2 -1
- package/dist/primitives/preview-image.js +4 -2
- package/dist/primitives/textarea.js +1 -1
- package/dist/primitives/typed-title.js +12 -14
- package/dist/primitives/typing-animation.js +4 -2
- package/dist/providers/ui-provider.d.ts +57 -0
- package/dist/providers/ui-provider.js +4 -0
- package/dist/session/composer/ComposerCard.d.ts +2 -0
- package/dist/session/composer/ComposerCard.js +5 -1
- package/dist/session/composer/agent-selector.js +17 -5
- package/dist/session/composer/composer-chat-input.js +5 -3
- package/dist/session/composer/composer-logic.js +8 -7
- package/dist/session/composer/composer-toolbar.js +4 -2
- package/dist/session/composer/composer-underbar.js +3 -1
- package/dist/session/composer/composer.js +34 -5
- package/dist/session/composer/draft/composer-draft.js +4 -4
- package/dist/session/composer/menus/slash-actions.d.ts +22 -2
- package/dist/session/composer/menus/slash-actions.js +20 -0
- package/dist/session/composer/menus/slash-items.js +19 -3
- package/dist/session/composer/pasted-text.js +3 -2
- package/dist/session/composer/stores/queued-draft-store.js +3 -2
- package/dist/session/header/reminder-parts.d.ts +294 -0
- package/dist/session/header/reminder-parts.js +120 -0
- package/dist/session/header/session-reminders-indicator.d.ts +13 -0
- package/dist/session/header/session-reminders-indicator.js +142 -35
- package/dist/session/media/sandbox-url-detector.js +0 -7
- package/dist/session/models/model-availability.d.ts +23 -0
- package/dist/session/models/model-availability.js +20 -0
- package/dist/session/models/model-grouping.js +1 -4
- package/dist/session/models/model-selector.js +23 -14
- package/dist/session/models/use-model-connection-gate.js +4 -1
- package/dist/session/outcomes/change-request-outcomes.js +24 -3
- package/dist/session/outcomes/session-outcomes-provider.d.ts +3 -1
- package/dist/session/outcomes/session-outcomes-provider.js +31 -3
- package/dist/session/overrides/provider-secret-pool-editor.js +2 -1
- package/dist/session/overrides/session-overrides-composer.d.ts +1 -1
- package/dist/session/overrides/session-overrides-composer.js +5 -1
- package/dist/session/panel/advanced/advanced-panel.js +3 -7
- package/dist/session/panel/easy/app-preview.js +12 -2
- package/dist/session/panel/easy/easy-panel-logic.d.ts +1 -1
- package/dist/session/panel/easy/easy-panel-logic.js +1 -1
- package/dist/session/panel/easy/outputs-card.js +2 -1
- package/dist/session/panel/easy/panel-card.js +5 -3
- package/dist/session/panel/easy/preview-load.d.ts +14 -0
- package/dist/session/panel/easy/preview-load.js +15 -0
- package/dist/session/panel/easy/step-detail-body.js +2 -7
- package/dist/session/panel/mobile-tool-drawer.js +4 -2
- package/dist/session/panel/open-session-quick-view.js +2 -1
- package/dist/session/panel/panel-fallback.js +8 -4
- package/dist/session/panel/session-panel-provider.d.ts +1 -0
- package/dist/session/panel/session-panel-provider.js +19 -3
- package/dist/session/panel/session-reminders-panel.d.ts +4 -0
- package/dist/session/panel/session-reminders-panel.js +109 -0
- package/dist/session/panel/shared/use-panel-derivations.js +6 -4
- package/dist/session/panel/shared/use-panel-handoffs.d.ts +2 -2
- package/dist/session/panel/shared/use-panel-handoffs.js +5 -1
- package/dist/session/panel/use-session-panel-layout.js +7 -5
- package/dist/session/print/session-print-header.d.ts +0 -10
- package/dist/session/print/session-print-header.js +25 -9
- package/dist/session/status/session-surface.js +1 -1
- package/dist/session/thread/optimistic-turn.d.ts +5 -1
- package/dist/session/thread/optimistic-turn.js +8 -6
- package/dist/session/thread/session-chat.d.ts +6 -1
- package/dist/session/thread/session-chat.js +34 -12
- package/dist/session/thread/session-history.js +3 -1
- package/dist/session/thread/session-thread.d.ts +10 -1
- package/dist/session/thread/session-thread.js +2 -2
- package/dist/session/thread/transcript.js +13 -7
- package/dist/session/tool/shared/file-list.js +19 -2
- package/dist/session/tool/shared/infrastructure-contexts.d.ts +7 -0
- package/dist/session/tool/shared/infrastructure-contexts.js +7 -0
- package/dist/session/tool/shared/infrastructure-output.js +9 -5
- package/dist/session/tool/shared/infrastructure-row-header.js +9 -1
- package/dist/session/tool/shared/infrastructure-rows.js +7 -5
- package/dist/session/tool/tools/bash-tool.js +10 -6
- package/dist/session/tool/tools/web-search-tool.js +25 -3
- package/dist/session/turn/burst-step-body.js +4 -0
- package/dist/session/turn/recovered-failures.d.ts +8 -0
- package/dist/session/turn/recovered-failures.js +32 -0
- package/dist/session/turn/streaming-cadence.js +5 -8
- package/dist/styles.css +1 -1
- package/dist/terminal/session-terminal-panel.js +3 -5
- package/dist/theme/theme.js +2 -1
- package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
- package/dist/workspace/review/review-center.js +13 -10
- package/dist/workspace/session-workspace.js +5 -9
- package/dist/workspace/sessions/deleted-session-row.js +5 -2
- package/dist/workspace/sidebar/footer/project-computer-nav.js +6 -4
- package/dist/workspace/sidebar/footer/project-mcp-nav.js +6 -2
- package/dist/workspace/sidebar/modal/rename-session-modal.js +8 -3
- package/dist/workspace/sidebar/project-session-list.js +52 -55
- package/dist/workspace/sidebar/session-brief-hover-card.d.ts +35 -18
- package/dist/workspace/sidebar/session-brief-hover-card.js +114 -37
- package/dist/workspace/sidebar/session-status-mark.d.ts +15 -1
- package/dist/workspace/sidebar/session-status-mark.js +63 -17
- package/package.json +7 -3
- package/skills/kortix-ui/SKILL.md +1 -1
- package/dist/customize/sections/view/sandbox-provider-result.d.ts +0 -47
- package/dist/customize/sections/view/sandbox-provider-result.js +0 -79
package/dist/primitives/map.js
CHANGED
|
@@ -163,7 +163,8 @@ function MapMarker({ longitude, latitude, label, children, ...markerOptions }) {
|
|
|
163
163
|
marker.setLngLat([longitude, latitude]);
|
|
164
164
|
}
|
|
165
165
|
}, [marker, longitude, latitude]);
|
|
166
|
-
|
|
166
|
+
const markerContextValue = useMemo(() => ({ marker, map }), [marker, map]);
|
|
167
|
+
return _jsx(MarkerContext.Provider, { value: markerContextValue, children: children });
|
|
167
168
|
}
|
|
168
169
|
function MarkerContent({ children, className }) {
|
|
169
170
|
const { marker } = useMarkerContext();
|
|
@@ -22,10 +22,12 @@ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
|
22
22
|
const PreviewImageContent = React.forwardRef(({ className, fileContent, fileName, ...props }, ref) => {
|
|
23
23
|
const [zoomLevel, setZoomLevel] = React.useState(DEFAULT_ZOOM);
|
|
24
24
|
const [transformOrigin, setTransformOrigin] = React.useState(DEFAULT_ORIGIN);
|
|
25
|
-
React.
|
|
25
|
+
const [zoomedContent, setZoomedContent] = React.useState(fileContent);
|
|
26
|
+
if (fileContent !== zoomedContent) {
|
|
27
|
+
setZoomedContent(fileContent);
|
|
26
28
|
setZoomLevel(DEFAULT_ZOOM);
|
|
27
29
|
setTransformOrigin(DEFAULT_ORIGIN);
|
|
28
|
-
}
|
|
30
|
+
}
|
|
29
31
|
const getTransformOrigin = (event) => {
|
|
30
32
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
31
33
|
if (!rect.width || !rect.height) {
|
|
@@ -59,7 +59,7 @@ export const useAutosizeTextArea = ({ textAreaRef, triggerAutoSize, maxHeight =
|
|
|
59
59
|
clearTimeout(resizeTimeoutRef.current);
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
|
-
}, [textAreaRef
|
|
62
|
+
}, [textAreaRef, triggerAutoSize, minHeight, maxHeight, currentHeight, init]);
|
|
63
63
|
};
|
|
64
64
|
export const Textarea = React.forwardRef(({ maxHeight = Number.MAX_SAFE_INTEGER, minHeight = 52, className, onChange, value, variant: _variant, ...props }, ref) => {
|
|
65
65
|
const textAreaRef = React.useRef(null);
|
|
@@ -19,25 +19,23 @@ import { shouldTypeOnChange, TYPE_SPEED_MS } from './typed-title-logic.js';
|
|
|
19
19
|
import { TypingAnimation } from './typing-animation.js';
|
|
20
20
|
import { cn } from '../lib/utils.js';
|
|
21
21
|
import { useReducedMotion } from 'motion/react';
|
|
22
|
-
import { useCallback,
|
|
22
|
+
import { useCallback, useState } from 'react';
|
|
23
23
|
export { TYPE_SPEED_MS };
|
|
24
24
|
export function useTypeOnChange(text) {
|
|
25
|
-
// The last value this row DISPLAYED
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
const previousRef = useRef(null);
|
|
25
|
+
// The last value this row DISPLAYED. It updates in the same render pass that
|
|
26
|
+
// decides to animate, or a second change arriving mid-type compares against
|
|
27
|
+
// a stale value and is missed.
|
|
28
|
+
const [previous, setPrevious] = useState(null);
|
|
30
29
|
const [isTyping, setIsTyping] = useState(false);
|
|
31
30
|
const [runId, setRunId] = useState(0);
|
|
32
31
|
const reduceMotion = !!useReducedMotion();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}, [text, reduceMotion]);
|
|
32
|
+
if (previous !== text) {
|
|
33
|
+
setPrevious(text);
|
|
34
|
+
if (shouldTypeOnChange({ previous, next: text, reduceMotion })) {
|
|
35
|
+
setIsTyping(true);
|
|
36
|
+
setRunId((id) => id + 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
41
39
|
const stop = useCallback(() => setIsTyping(false), []);
|
|
42
40
|
return { isTyping, runId, stop };
|
|
43
41
|
}
|
|
@@ -34,12 +34,14 @@ export function TypingAnimation({ children, words, className, duration = 100, ty
|
|
|
34
34
|
const deletingSpeed = deleteSpeed ?? typingSpeed / 2;
|
|
35
35
|
const shouldStart = startOnView ? isInView : true;
|
|
36
36
|
const animationSourceKey = useMemo(() => (words ? words.join('\u0000') : (children ?? '')), [words, children]);
|
|
37
|
-
|
|
37
|
+
const [seenSourceKey, setSeenSourceKey] = useState(animationSourceKey);
|
|
38
|
+
if (animationSourceKey !== seenSourceKey) {
|
|
39
|
+
setSeenSourceKey(animationSourceKey);
|
|
38
40
|
setDisplayedText('');
|
|
39
41
|
setCurrentWordIndex(0);
|
|
40
42
|
setCurrentCharIndex(0);
|
|
41
43
|
setPhase('typing');
|
|
42
|
-
}
|
|
44
|
+
}
|
|
43
45
|
useEffect(() => {
|
|
44
46
|
let timeout = null;
|
|
45
47
|
if (shouldStart && wordsToAnimate.length > 0) {
|
|
@@ -177,6 +177,63 @@ export interface UiHostConfig {
|
|
|
177
177
|
* still enforces each route. Default: true.
|
|
178
178
|
*/
|
|
179
179
|
accountApiEnabled: boolean;
|
|
180
|
+
/**
|
|
181
|
+
* The composer shows the model picker and the reasoning-effort picker. A
|
|
182
|
+
* host that runs one model for its users sets false: the session runs its
|
|
183
|
+
* resolved default model. Default: true.
|
|
184
|
+
*/
|
|
185
|
+
modelPickerEnabled: boolean;
|
|
186
|
+
/** The composer shows the context-usage ring (and its route to the context modal). Default: true. */
|
|
187
|
+
contextMeterEnabled: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* The composer shows the session-overrides gear (sandbox, secrets,
|
|
190
|
+
* connectors, provider keys, folders). A host whose users never see the
|
|
191
|
+
* infrastructure behind a session sets false. Default: true.
|
|
192
|
+
*/
|
|
193
|
+
sessionOverridesEnabled: boolean;
|
|
194
|
+
/**
|
|
195
|
+
* The agents the composer's agent picker offers, by name. Unset: every
|
|
196
|
+
* selectable agent. A host that installs its own agents beside the
|
|
197
|
+
* project's defaults lists its own here.
|
|
198
|
+
*/
|
|
199
|
+
offeredAgents?: readonly string[];
|
|
200
|
+
/** Display names by agent name, for the agent picker. Unset or missing: the title-cased name. */
|
|
201
|
+
agentLabels?: Readonly<Record<string, string>>;
|
|
202
|
+
/**
|
|
203
|
+
* A host that keeps files in its own store takes the composer's files here:
|
|
204
|
+
* the attach button, a drop and a pasted file. The composer then uploads
|
|
205
|
+
* none of them to the session's attachment store and shows no tray for
|
|
206
|
+
* them. Unset: they upload as prompt attachments.
|
|
207
|
+
*/
|
|
208
|
+
takeFiles?: (files: File[]) => void;
|
|
209
|
+
/** The attach picker's `accept` list. Unset: images, PDF, text and code files. */
|
|
210
|
+
attachAccept?: string;
|
|
211
|
+
/**
|
|
212
|
+
* The composer's `/` menu lists the session runtime's skills and commands,
|
|
213
|
+
* and the Compact session action. A host that hides the harness from its
|
|
214
|
+
* users sets false. Default: true.
|
|
215
|
+
*/
|
|
216
|
+
runtimeSlashEnabled: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* Rows the host adds to the composer's `/` menu, under their own headings.
|
|
219
|
+
* A hook, so it may read the host's own data. Unset: none.
|
|
220
|
+
*/
|
|
221
|
+
useSlashActions?: () => readonly UiSlashAction[];
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* A row the host adds to the composer's `/` menu. Selecting it deletes the
|
|
225
|
+
* `/` text and calls `onSelect`. When `onSelect` returns `{ insertText }`,
|
|
226
|
+
* the composer inserts that text at the caret.
|
|
227
|
+
*/
|
|
228
|
+
export interface UiSlashAction {
|
|
229
|
+
id: string;
|
|
230
|
+
label: string;
|
|
231
|
+
description: string;
|
|
232
|
+
/** The heading the row sits under, for example "Workflows". */
|
|
233
|
+
group: string;
|
|
234
|
+
onSelect: () => void | {
|
|
235
|
+
insertText: string;
|
|
236
|
+
};
|
|
180
237
|
}
|
|
181
238
|
/** What the session browser frames: a port and path inside the session's sandbox, or a URL. */
|
|
182
239
|
export interface UiPreviewRequest {
|
|
@@ -59,6 +59,10 @@ export const defaultUiHostConfig = {
|
|
|
59
59
|
commandPaletteShortcut: false,
|
|
60
60
|
settingsShortcut: false,
|
|
61
61
|
accountApiEnabled: true,
|
|
62
|
+
modelPickerEnabled: true,
|
|
63
|
+
contextMeterEnabled: true,
|
|
64
|
+
sessionOverridesEnabled: true,
|
|
65
|
+
runtimeSlashEnabled: true,
|
|
62
66
|
};
|
|
63
67
|
const useNoViewer = () => undefined;
|
|
64
68
|
/** A viewer hook built from an id hook: `{ id }`, stable per id. */
|
|
@@ -16,6 +16,8 @@ import type { AttachedFile } from './types.js';
|
|
|
16
16
|
* phase 1).
|
|
17
17
|
*/
|
|
18
18
|
export interface ComposerCardDerived {
|
|
19
|
+
/** The commands the `/` menu lists: the `commands` prop, or none when the host hides the runtime's (`runtimeSlashEnabled`). */
|
|
20
|
+
commands: NonNullable<SessionChatInputProps['commands']>;
|
|
19
21
|
tHardcodedUi: ReturnType<typeof useTranslations>;
|
|
20
22
|
isDragOver: boolean;
|
|
21
23
|
/** Unset inside a `ComposerDropZone`: the zone owns every file drag. */
|
|
@@ -10,6 +10,9 @@ import { EMPTY_AGENTS, EMPTY_COMMANDS, EMPTY_MODELS, EMPTY_SLASH_FILES, EMPTY_VA
|
|
|
10
10
|
import { shouldFocusEditorFromPadding } from './composer-logic.js';
|
|
11
11
|
import { ComposerToolbar } from './composer-toolbar.js';
|
|
12
12
|
import { ComposerUnderbar } from './composer-underbar.js';
|
|
13
|
+
import { useUiHostConfig } from '../../providers/ui-provider.js';
|
|
14
|
+
/** What the attach picker offers when the host names no list. */
|
|
15
|
+
const DEFAULT_ATTACH_ACCEPT = 'image/*,.pdf,.txt,.md,.json,.csv,.xml,.yaml,.yml,.toml,.js,.ts,.jsx,.tsx,.py,.rb,.go,.rs,.java,.c,.cpp,.h,.css,.html,.vue,.svelte,.log,.sql,.zip,.tar,.gz,.rar';
|
|
13
16
|
const ComposerEditorLazy = lazy(() => import('./editor/composer-editor.js').then((mod) => ({ default: mod.ComposerEditor })));
|
|
14
17
|
function ComposerEditorFallback() {
|
|
15
18
|
return _jsx("div", { className: "min-h-[1.5em]", "aria-hidden": true });
|
|
@@ -20,6 +23,7 @@ export function ComposerCard({ tHardcodedUi, isDragOver, handleDragEnter, handle
|
|
|
20
23
|
// values, not the component's defaulted locals.
|
|
21
24
|
agents = EMPTY_AGENTS, commands = EMPTY_COMMANDS, slashFiles = EMPTY_SLASH_FILES, models = EMPTY_MODELS, notice = null, disabled = false, lockForQuestion = false, lockForApproval = false, questionButtonLabel = null, questionActive = false, submitLabel = null, questionCanAct = true, escCount = 0, isBusy = false, isSending = false, onStop, stopDisabled = false, canStop = true, modelsLoading = false, modelRequired = false, onModelChange, modelDefaultControls, providers, variants = EMPTY_VARIANTS, selectedVariant = null, onVariantChange, projectId, toolbarSlot, rewind, selectedAgent = null, onAgentChange, noAccessibleAgents = false, onContextClick, messages, underbarPlacement = 'below', slashMenuPlacement = 'above', cardClassName, onArrowUpAtStart, }) {
|
|
22
25
|
const cardRef = useRef(null);
|
|
26
|
+
const { attachAccept } = useUiHostConfig();
|
|
23
27
|
const inlineUnderbar = underbarPlacement === 'inline';
|
|
24
28
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: cardRef, onDragEnter: handleDragEnter, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDropFiles, className: cn(
|
|
25
29
|
// One shadow, from the ladder. `shadow-card` is defined nowhere in
|
|
@@ -47,7 +51,7 @@ agents = EMPTY_AGENTS, commands = EMPTY_COMMANDS, slashFiles = EMPTY_SLASH_FILES
|
|
|
47
51
|
// the div and immediately fights the caret we are placing.
|
|
48
52
|
e.preventDefault();
|
|
49
53
|
editorRef.current?.focus();
|
|
50
|
-
}, children: [_jsx(AnimatedComposerPlaceholder, { placeholder: editorPlaceholder, active: animatePlaceholder }), _jsx(Suspense, { fallback: _jsx(ComposerEditorFallback, {}), children: _jsx(ComposerEditorLazy, { ref: setEditorRef, placeholder: animatePlaceholder ? '' : editorPlaceholder, disabled: editorDisabled, onSubmit: handleSubmit, onArrowUpAtStart: onArrowUpAtStart ? handleArrowUpAtStart : undefined, onEmptyChange: setIsEmpty, onDocChange: handleDocChange, agents: agents, sessions: allSessions ?? [], currentSessionId: sessionId, commands: commands, actions: slashActions, files: slashFiles, onSelectAction: handleSelectAction, slashDockSelector: `#${dockId}`, onMenuOpenChange: setMenuOpen }) })] }), _jsx("input", { ref: fileInputRef, type: "file", accept:
|
|
54
|
+
}, children: [_jsx(AnimatedComposerPlaceholder, { placeholder: editorPlaceholder, active: animatePlaceholder }), _jsx(Suspense, { fallback: _jsx(ComposerEditorFallback, {}), children: _jsx(ComposerEditorLazy, { ref: setEditorRef, placeholder: animatePlaceholder ? '' : editorPlaceholder, disabled: editorDisabled, onSubmit: handleSubmit, onArrowUpAtStart: onArrowUpAtStart ? handleArrowUpAtStart : undefined, onEmptyChange: setIsEmpty, onDocChange: handleDocChange, agents: agents, sessions: allSessions ?? [], currentSessionId: sessionId, commands: commands, actions: slashActions, files: slashFiles, onSelectAction: handleSelectAction, slashDockSelector: `#${dockId}`, onMenuOpenChange: setMenuOpen }) })] }), _jsx("input", { ref: fileInputRef, type: "file", accept: attachAccept ?? DEFAULT_ATTACH_ACCEPT, multiple: true, className: "hidden", onChange: handleFileSelect }), _jsx(ComposerToolbar, { leading: inlineUnderbar ? (_jsx(ComposerUnderbar, { variant: "inline", onAttachClick: handleAttachClick, agents: primaryAgents, selectedAgent: selectedAgent, onAgentChange: onAgentChange, agentMenuOpen: agentMenuOpen, onAgentMenuOpenChange: setAgentMenuOpen, noAccessibleAgents: noAccessibleAgents, messages: messages, models: models, selectedModel: availableSelectedModel, onContextClick: onContextClick })) : null, modelsLoading: modelsLoading, models: models, selectedModel: availableSelectedModel, onModelChange: onModelChange, modelDefaultControls: modelDefaultControls, providers: providers, modelRequired: modelRequired, modelMenuOpen: modelMenuOpen, onModelMenuOpenChange: setModelMenuOpen, reasoningMenuOpen: reasoningMenuOpen, onReasoningMenuOpenChange: setReasoningMenuOpen, variants: variants, selectedVariant: selectedVariant, onVariantChange: onVariantChange, projectId: projectId,
|
|
51
55
|
// Inline placement has no under-row, so the slot (the session
|
|
52
56
|
// overrides gear, meta indicator) rides the toolbar itself. With
|
|
53
57
|
// the 'below' placement the ComposerUnderbar further down renders
|
|
@@ -5,6 +5,7 @@ import { Button } from '../../primitives/button.js';
|
|
|
5
5
|
import { CommandGroup, CommandInput, CommandItem, CommandList, CommandPopover, CommandPopoverContent, CommandPopoverTrigger, CommandSeparator, } from '../../primitives/command.js';
|
|
6
6
|
import Hint from '../../primitives/hint.js';
|
|
7
7
|
import { useTranslations } from '../../i18n/index.js';
|
|
8
|
+
import { useUiHostConfig } from '../../providers/ui-provider.js';
|
|
8
9
|
import { cn } from '../../lib/utils.js';
|
|
9
10
|
import { capitalizeWords, isMetaAgentName, META_AGENT_DISPLAY_NAME } from '../../_vendor/kortix-shared.js';
|
|
10
11
|
import { CaretDownIcon, CheckIcon, SparkleIcon } from '@phosphor-icons/react';
|
|
@@ -42,7 +43,16 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
|
|
|
42
43
|
const open = controlledOpen ?? internalOpen;
|
|
43
44
|
const setOpen = onOpenChange ?? setInternalOpen;
|
|
44
45
|
const [search, setSearch] = useState('');
|
|
45
|
-
const
|
|
46
|
+
const { offeredAgents, agentLabels } = useUiHostConfig();
|
|
47
|
+
// A host that lists its agents (`offeredAgents`) offers only those; it names them with `agentLabels`.
|
|
48
|
+
const offered = useMemo(() => {
|
|
49
|
+
if (!offeredAgents)
|
|
50
|
+
return agents;
|
|
51
|
+
const offeredNames = new Set(offeredAgents);
|
|
52
|
+
return agents.filter((a) => offeredNames.has(a.name));
|
|
53
|
+
}, [agents, offeredAgents]);
|
|
54
|
+
const primaryAgents = useMemo(() => composerSelectableAgents(offered), [offered]);
|
|
55
|
+
const label = (name) => agentLabels?.[name] ?? (isMetaAgentName(name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(name));
|
|
46
56
|
// A `flash` state, a `prevAgentRef` feeding it, two effects and a 400ms
|
|
47
57
|
// timer used to live here. Nothing ever READ `flash` — it was written on
|
|
48
58
|
// every agent change and rendered nowhere, so the whole apparatus bought a
|
|
@@ -55,8 +65,10 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
|
|
|
55
65
|
const q = search.toLowerCase().trim();
|
|
56
66
|
if (!q)
|
|
57
67
|
return primaryAgents;
|
|
58
|
-
return primaryAgents.filter((a) => a.name.toLowerCase().includes(q) ||
|
|
59
|
-
|
|
68
|
+
return primaryAgents.filter((a) => a.name.toLowerCase().includes(q) ||
|
|
69
|
+
(agentLabels?.[a.name] ?? '').toLowerCase().includes(q) ||
|
|
70
|
+
(a.description || '').toLowerCase().includes(q));
|
|
71
|
+
}, [primaryAgents, search, agentLabels]);
|
|
60
72
|
const filteredMeta = useMemo(() => filteredPrimary.filter((a) => isMetaAgentName(a.name)), [filteredPrimary]);
|
|
61
73
|
const filteredProject = useMemo(() => filteredPrimary.filter((a) => !isMetaAgentName(a.name)), [filteredPrimary]);
|
|
62
74
|
// Keyed off the FULL list, never the filtered one: deriving it from
|
|
@@ -71,7 +83,7 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
|
|
|
71
83
|
// fallback stays only for a truly unresolved selection (no name at all),
|
|
72
84
|
// matching the resolver's own first-accessible pre-selection.
|
|
73
85
|
const displayedName = resolveDisplayedAgentName(primaryAgents, selectedAgent);
|
|
74
|
-
const displayName = agentDisplayLabel(
|
|
86
|
+
const displayName = displayedName ? label(displayedName) : agentDisplayLabel(offered, selectedAgent);
|
|
75
87
|
const metaSelected = isMetaAgentName(displayedName);
|
|
76
88
|
/**
|
|
77
89
|
* One agent: name, its type, its description, and a check when it is the
|
|
@@ -102,7 +114,7 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
|
|
|
102
114
|
}, children: [meta && (
|
|
103
115
|
// Meta is the platform agent that runs the others, so its row leads
|
|
104
116
|
// with its own mark instead of reading as one more peer in the list.
|
|
105
|
-
_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:
|
|
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 && (
|
|
106
118
|
// `mt-0.5` optically centres the 16px check against the first line
|
|
107
119
|
// rather than the top edge of the text box.
|
|
108
120
|
_jsx(CheckIcon, { className: "text-foreground mt-0.5 size-4 shrink-0" }))] }, agent.name));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { SessionOverridesComposer } from '../overrides/session-overrides-composer.js';
|
|
5
5
|
import { SessionChatInput, } from './session-chat-input.js';
|
|
6
6
|
import { useProjectConfig, useRuntimeAgents, useRuntimeCommands, useRuntimeConfig, useRuntimeProviders, useSessionModelSelection } from '@kortix/sdk/react';
|
|
@@ -103,10 +103,11 @@ export function ComposerChatInput({ onSend, onCommand, sessionId, projectId, isB
|
|
|
103
103
|
useEffect(() => {
|
|
104
104
|
onAgentSelectionChange?.(selectedAgentName);
|
|
105
105
|
}, [onAgentSelectionChange, selectedAgentName]);
|
|
106
|
-
|
|
106
|
+
// Read only by `options()` at send time, so a ref: a commit needs no render.
|
|
107
|
+
const newSessionScopeRef = useRef(null);
|
|
107
108
|
const [newProviderSecretPools, setNewProviderSecretPools] = useState({});
|
|
108
109
|
const handleCommittedScope = useCallback((commit) => {
|
|
109
|
-
|
|
110
|
+
newSessionScopeRef.current = commit ? { agentName: selectedAgentName, commit } : null;
|
|
110
111
|
}, [selectedAgentName]);
|
|
111
112
|
// The axes a session can override that have no control of their own —
|
|
112
113
|
// secrets, connectors, sandbox. Agent, model and effort are NOT passed: this
|
|
@@ -126,6 +127,7 @@ export function ComposerChatInput({ onSend, onCommand, sessionId, projectId, isB
|
|
|
126
127
|
o.model = local.model.currentKey;
|
|
127
128
|
if (local.model.variant.current)
|
|
128
129
|
o.variant = local.model.variant.current;
|
|
130
|
+
const newSessionScope = newSessionScopeRef.current;
|
|
129
131
|
if (!sessionId && newSessionScope && newSessionScope.agentName === selectedAgentName) {
|
|
130
132
|
o.scope = newSessionScope.commit;
|
|
131
133
|
}
|
|
@@ -51,11 +51,7 @@ export function extractReplyQuotes(text) {
|
|
|
51
51
|
const { quotes } = parseReplyContexts(text);
|
|
52
52
|
if (quotes.length === 0)
|
|
53
53
|
return { quotes: [], text };
|
|
54
|
-
const unique = [];
|
|
55
|
-
for (const quote of quotes) {
|
|
56
|
-
if (quote && !unique.includes(quote))
|
|
57
|
-
unique.push(quote);
|
|
58
|
-
}
|
|
54
|
+
const unique = [...new Set(quotes.filter(Boolean))];
|
|
59
55
|
return { quotes: unique, text: stripReplyContexts(text) };
|
|
60
56
|
}
|
|
61
57
|
/**
|
|
@@ -96,12 +92,16 @@ export function mergeComposerQuotes(restored, current) {
|
|
|
96
92
|
* returns `current` itself.
|
|
97
93
|
*/
|
|
98
94
|
export function restoreComposerQuotes(current, texts, makeId) {
|
|
95
|
+
const currentByText = new Map();
|
|
96
|
+
for (const quote of current)
|
|
97
|
+
if (!currentByText.has(quote.text))
|
|
98
|
+
currentByText.set(quote.text, quote);
|
|
99
99
|
const restored = [];
|
|
100
100
|
for (const raw of texts) {
|
|
101
101
|
const text = raw.trim();
|
|
102
102
|
if (!text || restored.some((quote) => quote.text === text))
|
|
103
103
|
continue;
|
|
104
|
-
restored.push(
|
|
104
|
+
restored.push(currentByText.get(text) ?? { id: makeId(), text });
|
|
105
105
|
}
|
|
106
106
|
if (restored.length === 0)
|
|
107
107
|
return current;
|
|
@@ -249,7 +249,8 @@ export function planQuoteRequests({ requests, appliedIds, }) {
|
|
|
249
249
|
* itself when nothing matches, so a no-op ack does not re-render.
|
|
250
250
|
*/
|
|
251
251
|
export function acknowledgeQuoteRequests(requests, ids) {
|
|
252
|
-
const
|
|
252
|
+
const acked = new Set(ids);
|
|
253
|
+
const remaining = requests.filter((request) => !acked.has(request.id));
|
|
253
254
|
return remaining.length === requests.length ? requests : remaining;
|
|
254
255
|
}
|
|
255
256
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useTranslations } from '../../i18n/index.js';
|
|
4
|
+
import { useUiHostConfig } from '../../providers/ui-provider.js';
|
|
4
5
|
import { ArrowCounterClockwiseIcon } from '@phosphor-icons/react';
|
|
5
6
|
import { Button } from '../../primitives/button.js';
|
|
6
7
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '../../primitives/hover-card.js';
|
|
@@ -10,8 +11,9 @@ import { ReasoningEffortSelector } from './reasoning-effort-selector.js';
|
|
|
10
11
|
import { SendStopControl } from './send-stop-control.js';
|
|
11
12
|
export function ComposerToolbar({ models, modelsLoading, selectedModel, onModelChange, modelDefaultControls, providers, modelRequired, modelMenuOpen, onModelMenuOpenChange, reasoningMenuOpen, onReasoningMenuOpenChange, variants, selectedVariant, onVariantChange, projectId, toolbarSlot, rewind, leading, isSending, isBusy, onStop, stopDisabled, canStop, escCount, lockForQuestion, questionButtonLabel, submitLabel, questionCanAct, hasText, canSubmit, submitDisabled, disabled, modelUnavailable, agentUnavailable = false, attachmentFailed = false, attachmentUnsupported = null, onSubmit, }) {
|
|
12
13
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
13
|
-
const
|
|
14
|
-
|
|
14
|
+
const { modelPickerEnabled } = useUiHostConfig();
|
|
15
|
+
const showModel = modelPickerEnabled && (models.length > 0 || modelRequired) && !!onModelChange;
|
|
16
|
+
return (_jsxs("div", { className: "kortix-composer-toolbar flex items-center justify-between gap-1 overflow-visible", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1 overflow-visible", children: [leading, showModel && (_jsx(ModelSelector, { models: models, modelsLoading: modelsLoading, selectedModel: selectedModel, onSelect: onModelChange, providers: providers, defaultControls: modelDefaultControls, triggerLabelClassName: "max-w-[7rem]", projectId: projectId, offerProviderAccounts: true, open: modelMenuOpen, onOpenChange: onModelMenuOpenChange })), modelPickerEnabled && _jsx(ReasoningEffortSelector, { variants: variants, selectedVariant: selectedVariant, onVariantChange: onVariantChange, open: reasoningMenuOpen, onOpenChange: onReasoningMenuOpenChange })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [rewind && (_jsxs(HoverCard, { openDelay: 0, closeDelay: 0, children: [_jsx(HoverCardTrigger, { asChild: true, children: _jsx("span", { className: "inline-flex", children: _jsxs(Button, { type: "button", variant: "ghost", size: "sm", disabled: rewind.pending || rewind.disabled, onClick: rewind.onRestore, className: "text-muted-foreground hover:text-foreground gap-1.5", children: [rewind.pending ? (_jsx(Loading, { className: "size-3.5 shrink-0" })) : (_jsx(ArrowCounterClockwiseIcon, { className: "size-3.5 shrink-0" })), tI18nComplete.raw('texta76e13b98392')] }) }) }), _jsx(HoverCardContent, { className: "px-3 py-2 text-sm text-balance", children: rewind.disabled && !rewind.pending
|
|
15
17
|
? tI18nComplete.raw('text98433e61649a')
|
|
16
18
|
: tI18nComplete.raw('text4c25090df9ea') })] })), toolbarSlot, _jsx(SendStopControl, { isSending: isSending, isBusy: isBusy, onStop: onStop, stopDisabled: stopDisabled, canStop: canStop, escCount: escCount, lockForQuestion: lockForQuestion, questionButtonLabel: questionButtonLabel, submitLabel: submitLabel, questionCanAct: questionCanAct, hasText: hasText, canSubmit: canSubmit, submitDisabled: submitDisabled, disabled: disabled, modelUnavailable: modelUnavailable, agentUnavailable: agentUnavailable, attachmentFailed: attachmentFailed, attachmentUnsupported: attachmentUnsupported, onSubmit: onSubmit })] })] }));
|
|
17
19
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useTranslations } from '../../i18n/index.js';
|
|
4
|
+
import { useUiHostConfig } from '../../providers/ui-provider.js';
|
|
4
5
|
import { PaperclipIcon as Paperclip } from '@phosphor-icons/react';
|
|
5
6
|
import { Button } from '../../primitives/button.js';
|
|
6
7
|
import Hint from '../../primitives/hint.js';
|
|
@@ -15,6 +16,7 @@ const NO_AGENT_SELECT = () => { };
|
|
|
15
16
|
const EMPTY_AGENTS = [];
|
|
16
17
|
export function ComposerUnderbar({ onAttachClick, agents, selectedAgent, agentMenuOpen, onAgentMenuOpenChange, onAgentChange, noAccessibleAgents = false, messages, models, selectedModel, onContextClick, variant = 'row', toolbarSlot, }) {
|
|
17
18
|
const t = useTranslations('threads');
|
|
19
|
+
const { contextMeterEnabled } = useUiHostConfig();
|
|
18
20
|
/*
|
|
19
21
|
The agent picker is UNCONDITIONAL. It used to earn its slot
|
|
20
22
|
(`agents.length > 0 && onAgentChange`), and that condition is exactly how
|
|
@@ -52,5 +54,5 @@ export function ComposerUnderbar({ onAttachClick, agents, selectedAgent, agentMe
|
|
|
52
54
|
: 'kortix-composer-underbar flex items-center justify-between gap-2 px-px pt-1.5 pb-2', children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1", children: [_jsx(Hint, { side: "top", label: t('attachFiles'), children: _jsx(Button, { type: "button", variant: "ghost", size: "icon-base", onClick: onAttachClick, "aria-label": t('attachFiles'),
|
|
53
55
|
// `hit-area-3` — the ≈29px ghost chip clears the 44px mobile tap
|
|
54
56
|
// floor only with the shared pseudo-element extension.
|
|
55
|
-
className: "text-muted-foreground hit-area-3 rounded-lg", children: _jsx(Paperclip, { className: "size-4 shrink-0" }) }) }), _jsx(AgentSelector, { agents: noAccessibleAgents ? EMPTY_AGENTS : agents, selectedAgent: selectedAgent, open: agentMenuOpen, onOpenChange: onAgentMenuOpenChange, onSelect: onAgentChange ?? NO_AGENT_SELECT, triggerLabelClassName: "max-w-[7rem]", unavailableHint: noAccessibleAgents ? NO_AGENT_ACCESS_MESSAGE : undefined })] }), _jsxs("div", { className: "flex items-center gap-1", children: [inline && !messages ? null : (_jsx(TokenProgress, { messages: messages, models: models, selectedModel: selectedModel, onContextClick: onContextClick })), toolbarSlot] })] }));
|
|
57
|
+
className: "text-muted-foreground hit-area-3 rounded-lg", children: _jsx(Paperclip, { className: "size-4 shrink-0" }) }) }), _jsx(AgentSelector, { agents: noAccessibleAgents ? EMPTY_AGENTS : agents, selectedAgent: selectedAgent, open: agentMenuOpen, onOpenChange: onAgentMenuOpenChange, onSelect: onAgentChange ?? NO_AGENT_SELECT, triggerLabelClassName: "max-w-[7rem]", unavailableHint: noAccessibleAgents ? NO_AGENT_ACCESS_MESSAGE : undefined })] }), _jsxs("div", { className: "flex items-center gap-1", children: [(inline && !messages) || !contextMeterEnabled ? null : (_jsx(TokenProgress, { messages: messages, models: models, selectedModel: selectedModel, onContextClick: onContextClick })), toolbarSlot] })] }));
|
|
56
58
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { errorToast } from '../../primitives/toast.js';
|
|
4
|
+
import { useUiHostConfig } from '../../providers/ui-provider.js';
|
|
4
5
|
import { useTranslations } from '../../i18n/index.js';
|
|
5
6
|
import { cn } from '../../lib/utils.js';
|
|
6
7
|
import { isImageFile } from '../../internal/lib/utils/file-utils.js';
|
|
@@ -28,7 +29,7 @@ import { appendComposerQuote, classifyPaste, isUndoKey, popPasteUndo, extractRep
|
|
|
28
29
|
import { getContextUsage } from './context-ring.js';
|
|
29
30
|
import { useComposerFocus } from './hooks/use-composer-focus.js';
|
|
30
31
|
import { useMenuRevalidation } from './hooks/use-file-search.js';
|
|
31
|
-
import { controlToOpenFor, localizedSlashActions } from './menus/slash-actions.js';
|
|
32
|
+
import { controlToOpenFor, hostSlashActions, localizedSlashActions, slashActionShown } from './menus/slash-actions.js';
|
|
32
33
|
import { createSubmitLatch } from './submit-latch.js';
|
|
33
34
|
/**
|
|
34
35
|
* The composer's outer shell — max width, centering, and the horizontal gutter
|
|
@@ -95,6 +96,8 @@ const NO_QUOTE_REQUESTS = [];
|
|
|
95
96
|
const NO_SUBSCRIPTION = () => { };
|
|
96
97
|
const NO_COMMAND_CHIP = () => null;
|
|
97
98
|
const EMPTY_DOCUMENT = textToDocument('');
|
|
99
|
+
const NO_SLASH_ACTIONS = [];
|
|
100
|
+
const useNoSlashActions = () => NO_SLASH_ACTIONS;
|
|
98
101
|
function setDocumentWithoutStealingFocus(handle, doc) {
|
|
99
102
|
if (!handle)
|
|
100
103
|
return;
|
|
@@ -107,7 +110,7 @@ function setDocumentWithoutStealingFocus(handle, doc) {
|
|
|
107
110
|
}
|
|
108
111
|
}
|
|
109
112
|
function ComposerImpl(props) {
|
|
110
|
-
const { onSend, promptAttachments: hostPromptAttachments, isBusy = false, sessionWorking, runtimeReady = true, onStop, stopDisabled = false, canStop = true, isSending = false, rewind, agents = EMPTY_AGENTS, selectedAgent = null, onAgentChange, noAccessibleAgents = false, commands = EMPTY_COMMANDS,
|
|
113
|
+
const { onSend, promptAttachments: hostPromptAttachments, isBusy = false, sessionWorking, runtimeReady = true, onStop, stopDisabled = false, canStop = true, isSending = false, rewind, agents = EMPTY_AGENTS, selectedAgent = null, onAgentChange, noAccessibleAgents = false, commands: runtimeCommands = EMPTY_COMMANDS,
|
|
111
114
|
/** Whether the live command list has EVER resolved — see `planUnresolvedCommandChip`. */
|
|
112
115
|
commandsResolved = false, slashFiles = EMPTY_SLASH_FILES, onCommand, onHoldCommand, models = EMPTY_MODELS, selectedModel = null, onModelChange, modelDefaultControls, variants = EMPTY_VARIANTS, selectedVariant = null, onVariantChange, messages, sessionId, projectId, draftScope = null, draftActive = true, disabled = false, notice = null, onNoticeRetry, clearOnSend = true, modelRequired = false, modelsLoading = false, autoFocus, placeholder = 'Ask anything…', hint, onArrowUpAtStart, prefill = null, onPrefillApplied, attachRequestId = null, providers, onContextClick, onCompactClick, inputSlot, toolbarSlot, underbarPlacement = 'below', slashMenuPlacement = 'above', cardClassName, quoteRequests = NO_QUOTE_REQUESTS, onQuoteRequestsApplied, lockForQuestion = false, lockForApproval = false, onCustomAnswer, questionButtonLabel = null, questionCanAct = true, onQuestionAction, escCount = 0, parentClassName, } = props;
|
|
113
116
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
@@ -190,6 +193,11 @@ function ComposerImpl(props) {
|
|
|
190
193
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
191
194
|
useMenuRevalidation(menuOpen, projectId);
|
|
192
195
|
const fileInputRef = useRef(null);
|
|
196
|
+
const { takeFiles, modelPickerEnabled, contextMeterEnabled, runtimeSlashEnabled, useSlashActions } = useUiHostConfig();
|
|
197
|
+
// The host's own `/` rows. The config is fixed for the app's life, so the hook is the same one every render.
|
|
198
|
+
const hostSlashRows = (useSlashActions ?? useNoSlashActions)();
|
|
199
|
+
// A host that hides the harness lists none of its commands, and a typed "/init" stays text.
|
|
200
|
+
const commands = runtimeSlashEnabled ? runtimeCommands : EMPTY_COMMANDS;
|
|
193
201
|
const dragDepthRef = useRef(0);
|
|
194
202
|
const savedDocBeforeQuestionRef = useRef(null);
|
|
195
203
|
// A host that owns the controller leaves this composer's own one idle: hooks
|
|
@@ -312,6 +320,11 @@ function ComposerImpl(props) {
|
|
|
312
320
|
const primaryAgents = useMemo(() => agents.filter((a) => !a.hidden && a.mode !== 'subagent'), [agents]);
|
|
313
321
|
const editorDisabled = disabled || lockForApproval;
|
|
314
322
|
const appendAttachedFiles = useCallback((files) => {
|
|
323
|
+
// The host keeps files in its own store: they go there, not to the session.
|
|
324
|
+
if (takeFiles) {
|
|
325
|
+
takeFiles(Array.from(files));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
315
328
|
// One empty or oversized file must not refuse the whole drop: the SDK
|
|
316
329
|
// checks a batch at once, so the files that fit are split off first and
|
|
317
330
|
// each refused one is named (KRTX-2557).
|
|
@@ -339,7 +352,7 @@ function ComposerImpl(props) {
|
|
|
339
352
|
catch (error) {
|
|
340
353
|
errorToast(error instanceof Error ? error.message : tComposerAttachments('couldNotAttach'));
|
|
341
354
|
}
|
|
342
|
-
}, [addPromptAttachments, tComposerAttachments]);
|
|
355
|
+
}, [addPromptAttachments, tComposerAttachments, takeFiles]);
|
|
343
356
|
useEffect(() => () => {
|
|
344
357
|
for (const file of attachedFilesRef.current) {
|
|
345
358
|
if (file.kind === 'local')
|
|
@@ -917,7 +930,11 @@ function ComposerImpl(props) {
|
|
|
917
930
|
// Rows whose handler this host did not provide are dropped, not shown
|
|
918
931
|
// dead — the `set-scope` lesson in `slash-actions.ts`: a row that
|
|
919
932
|
// highlights, offers "Use", and does nothing is worse than no row.
|
|
933
|
+
// A row whose control the host hides is dropped too (`slashActionShown`).
|
|
934
|
+
const host = { modelPickerEnabled, contextMeterEnabled, runtimeSlashEnabled };
|
|
920
935
|
const available = localizedSlashActions(tI18nComplete).filter((action) => {
|
|
936
|
+
if (!slashActionShown(action.id, host))
|
|
937
|
+
return false;
|
|
921
938
|
if (action.id === 'switch-agent')
|
|
922
939
|
return canSwitchAgent;
|
|
923
940
|
if (action.id === 'compact-session')
|
|
@@ -936,9 +953,19 @@ function ComposerImpl(props) {
|
|
|
936
953
|
return { ...action, value: `${contextUsage.percent}%`, context: contextUsage };
|
|
937
954
|
}
|
|
938
955
|
return action;
|
|
939
|
-
});
|
|
940
|
-
}, [
|
|
956
|
+
}).concat(hostSlashActions(hostSlashRows));
|
|
957
|
+
}, [
|
|
958
|
+
selectedAgent, canSwitchAgent, onCompactClick, onContextClick, contextUsage, tI18nComplete,
|
|
959
|
+
modelPickerEnabled, contextMeterEnabled, runtimeSlashEnabled, hostSlashRows,
|
|
960
|
+
]);
|
|
941
961
|
const handleSelectAction = useCallback((action) => {
|
|
962
|
+
// A host row runs the host's handler; it may hand back text for the composer.
|
|
963
|
+
if (action.onSelect) {
|
|
964
|
+
const out = action.onSelect();
|
|
965
|
+
if (out?.insertText)
|
|
966
|
+
editorRef.current?.insertAtCursor(out.insertText);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
942
969
|
// Which control a row opens lives in `controlToOpenFor`
|
|
943
970
|
// (menus/slash-actions.ts) so it can be tested; this file cannot be.
|
|
944
971
|
const control = controlToOpenFor(action.id);
|
|
@@ -1362,6 +1389,8 @@ function ComposerImpl(props) {
|
|
|
1362
1389
|
const showQueueStrip = Boolean(inputSlot);
|
|
1363
1390
|
const cardProps = {
|
|
1364
1391
|
tHardcodedUi,
|
|
1392
|
+
// The filtered list (`runtimeSlashEnabled`), over the host's `commands` prop in `props`.
|
|
1393
|
+
commands,
|
|
1365
1394
|
isDragOver,
|
|
1366
1395
|
// Inside a `ComposerDropZone` the zone owns every file drag.
|
|
1367
1396
|
handleDragEnter: inDropZone ? undefined : handleDragEnter,
|
|
@@ -76,14 +76,14 @@ export function deserializeDraft(raw, currentUserId) {
|
|
|
76
76
|
if (!Array.isArray(candidate.files))
|
|
77
77
|
return null;
|
|
78
78
|
const legacy = liftLegacyQuoteNodes(candidate.doc);
|
|
79
|
-
const quotes =
|
|
79
|
+
const quotes = new Set();
|
|
80
80
|
const stored = Array.isArray(candidate.quotes) ? candidate.quotes : [];
|
|
81
81
|
for (const quote of [...stored, ...legacy.quotes]) {
|
|
82
82
|
if (typeof quote !== 'string')
|
|
83
83
|
continue;
|
|
84
84
|
const text = quote.trim();
|
|
85
|
-
if (text
|
|
86
|
-
quotes.
|
|
85
|
+
if (text)
|
|
86
|
+
quotes.add(text);
|
|
87
87
|
}
|
|
88
88
|
// Only `{ id, text }` with both set crosses back; anything else in storage is dropped.
|
|
89
89
|
const storedPastes = Array.isArray(candidate.pastes) ? candidate.pastes : [];
|
|
@@ -101,7 +101,7 @@ export function deserializeDraft(raw, currentUserId) {
|
|
|
101
101
|
u: candidate.u,
|
|
102
102
|
doc: legacy.doc,
|
|
103
103
|
files: candidate.files.filter(isRemote),
|
|
104
|
-
quotes,
|
|
104
|
+
quotes: [...quotes],
|
|
105
105
|
pastes,
|
|
106
106
|
uploads,
|
|
107
107
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { UiTranslator } from '../../../i18n/index.js';
|
|
2
2
|
import type { ContextUsage } from '../context-ring.js';
|
|
3
|
+
import type { UiSlashAction } from '../../../providers/ui-provider.js';
|
|
3
4
|
export type SlashActionId = 'switch-model' | 'switch-agent' | 'set-reasoning-effort' | 'attach-file' | 'compact-session' | 'show-context';
|
|
4
5
|
export interface SlashAction {
|
|
5
|
-
id
|
|
6
|
+
/** A built-in action, or `host:<id>` for a row the host added (`UiHostConfig.useSlashActions`). */
|
|
7
|
+
id: SlashActionId | `host:${string}`;
|
|
6
8
|
label: string;
|
|
7
9
|
/** Also searched, so "thinking" finds reasoning effort. */
|
|
8
10
|
description: string;
|
|
@@ -29,7 +31,25 @@ export interface SlashAction {
|
|
|
29
31
|
* row falls back to a static muted ring and a plain text description.
|
|
30
32
|
*/
|
|
31
33
|
context?: ContextUsage;
|
|
34
|
+
/** A host row's heading (`UiSlashAction.group`). Built-in actions have none. */
|
|
35
|
+
group?: string;
|
|
36
|
+
/** A host row's handler (`UiSlashAction.onSelect`). */
|
|
37
|
+
onSelect?: UiSlashAction['onSelect'];
|
|
32
38
|
}
|
|
39
|
+
/** The host's `/` rows as slash actions. */
|
|
40
|
+
export declare function hostSlashActions(rows: readonly UiSlashAction[]): SlashAction[];
|
|
41
|
+
/**
|
|
42
|
+
* Which built-in rows a host shows. A row whose control the host hides is
|
|
43
|
+
* dropped, not shown dead: the model picker (`modelPickerEnabled`) owns Switch
|
|
44
|
+
* model and Set reasoning effort, the context meter (`contextMeterEnabled`)
|
|
45
|
+
* owns Show context, and the runtime's commands (`runtimeSlashEnabled`) own
|
|
46
|
+
* Compact session.
|
|
47
|
+
*/
|
|
48
|
+
export declare function slashActionShown(id: SlashAction['id'], host: {
|
|
49
|
+
modelPickerEnabled: boolean;
|
|
50
|
+
contextMeterEnabled: boolean;
|
|
51
|
+
runtimeSlashEnabled: boolean;
|
|
52
|
+
}): boolean;
|
|
33
53
|
/**
|
|
34
54
|
* Composer operations, executed locally. These never reach the agent — they
|
|
35
55
|
* open the control they name. Distinct from the Commands section, which runs
|
|
@@ -54,5 +74,5 @@ export type SlashActionControl = 'model' | 'reasoning' | 'agent';
|
|
|
54
74
|
* `bun test` has no DOM, so a mapping left inline there could not be asserted
|
|
55
75
|
* at all.
|
|
56
76
|
*/
|
|
57
|
-
export declare function controlToOpenFor(id:
|
|
77
|
+
export declare function controlToOpenFor(id: SlashAction['id']): SlashActionControl | null;
|
|
58
78
|
export declare function filterSlashActions(actions: SlashAction[], query: string): SlashAction[];
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { localizeUiCatalog } from '../../../i18n/ui-catalog/localize-ui-catalog.js';
|
|
2
2
|
import { REMAINING_UI_TRANSLATION_KEYS } from '../../../i18n/ui-catalog/remaining-ui-translation-keys.generated.js';
|
|
3
|
+
/** The host's `/` rows as slash actions. */
|
|
4
|
+
export function hostSlashActions(rows) {
|
|
5
|
+
return rows.map((r) => ({ id: `host:${r.id}`, label: r.label, description: r.description, group: r.group, onSelect: r.onSelect }));
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Which built-in rows a host shows. A row whose control the host hides is
|
|
9
|
+
* dropped, not shown dead: the model picker (`modelPickerEnabled`) owns Switch
|
|
10
|
+
* model and Set reasoning effort, the context meter (`contextMeterEnabled`)
|
|
11
|
+
* owns Show context, and the runtime's commands (`runtimeSlashEnabled`) own
|
|
12
|
+
* Compact session.
|
|
13
|
+
*/
|
|
14
|
+
export function slashActionShown(id, host) {
|
|
15
|
+
if (id === 'switch-model' || id === 'set-reasoning-effort')
|
|
16
|
+
return host.modelPickerEnabled;
|
|
17
|
+
if (id === 'show-context')
|
|
18
|
+
return host.contextMeterEnabled;
|
|
19
|
+
if (id === 'compact-session')
|
|
20
|
+
return host.runtimeSlashEnabled;
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
3
23
|
/**
|
|
4
24
|
* Composer operations, executed locally. These never reach the agent — they
|
|
5
25
|
* open the control they name. Distinct from the Commands section, which runs
|