@jini-ai/chat 0.5.4 → 0.5.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/CHANGELOG.md +8 -0
- package/dist/core/ag-ui/entry.d.ts +33 -0
- package/dist/core/ag-ui/entry.js +80 -0
- package/dist/core/agentic/chat-capabilities.d.ts +2 -4
- package/dist/core/agentic/index.d.ts +4 -17
- package/dist/core/agentic/index.js +4 -17
- package/dist/core/assistant-client/a2ui-action-poster.d.ts +46 -0
- package/dist/core/assistant-client/a2ui-action-poster.js +113 -0
- package/dist/core/assistant-client/ag-ui-transport.d.ts +68 -0
- package/dist/core/assistant-client/ag-ui-transport.js +147 -0
- package/dist/core/assistant-client/attachment-liveness.d.ts +37 -0
- package/dist/core/assistant-client/attachment-liveness.js +93 -0
- package/dist/core/assistant-client/durable-run-subscription.d.ts +39 -0
- package/dist/core/assistant-client/durable-run-subscription.js +96 -0
- package/dist/core/assistant-client/entry.d.ts +13 -0
- package/dist/core/assistant-client/entry.js +7 -0
- package/dist/core/assistant-client/messages.d.ts +73 -0
- package/dist/core/assistant-client/messages.js +68 -0
- package/dist/core/assistant-client/transport.d.ts +164 -0
- package/dist/core/assistant-client/transport.js +734 -0
- package/dist/core/browser/page-actions.d.ts +121 -0
- package/dist/core/browser/page-actions.js +113 -0
- package/dist/core/durable-runs/continuation.d.ts +18 -0
- package/dist/core/durable-runs/continuation.js +61 -0
- package/dist/core/durable-runs/ledger-contracts.d.ts +53 -0
- package/dist/core/durable-runs/ledger-contracts.js +2 -0
- package/dist/core/durable-runs/policies.d.ts +6 -0
- package/dist/core/durable-runs/policies.js +6 -0
- package/dist/core/durable-runs/ports.d.ts +123 -0
- package/dist/core/durable-runs/ports.js +2 -0
- package/dist/core/durable-runs/run-contracts.d.ts +17 -0
- package/dist/core/durable-runs/run-contracts.js +2 -0
- package/dist/core/durable-runs/run-lock.d.ts +4 -0
- package/dist/core/durable-runs/run-lock.js +4 -0
- package/dist/core/index.d.ts +6 -19
- package/dist/core/index.js +6 -19
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/question-form/format.d.ts +0 -9
- package/dist/core/question-form/format.js +16 -2
- package/dist/core/question-form/normalize.js +1 -0
- package/dist/core/question-form/secret-answers.d.ts +38 -0
- package/dist/core/question-form/secret-answers.js +45 -0
- package/dist/core/question-form/types.d.ts +2 -0
- package/dist/core/question-form.d.ts +1 -0
- package/dist/core/question-form.js +1 -0
- package/dist/core/run-events/entry.d.ts +208 -16
- package/dist/core/run-events/entry.js +273 -39
- package/dist/core/transcript.d.ts +3 -2
- package/dist/core/transcript.js +19 -3
- package/dist/core/user-text-redaction.d.ts +56 -0
- package/dist/core/user-text-redaction.js +89 -0
- package/dist/embed-widget/browser/page-actions.d.ts +1 -120
- package/dist/embed-widget/browser/page-actions.js +2 -112
- package/dist/embed-widget/react/embed.d.ts +1 -1
- package/dist/embed-widget/react/embed.js +1 -1
- package/dist/react/artifact-types.d.ts +9 -15
- package/dist/react/components/QuestionForm.d.ts +2 -1
- package/dist/react/components/QuestionForm.js +18 -12
- package/dist/react/components/QuestionsPanel.d.ts +3 -2
- package/dist/react/components/QuestionsPanel.js +2 -2
- package/dist/react/features/chat-pane/agent-tools.d.ts +1 -1
- package/dist/react/features/chat-pane/agent-tools.js +0 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +5 -2
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +5 -2
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +2 -1
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +33 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +3 -2
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +4 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +43 -22
- package/dist/react/features/chat-pane/model-options.d.ts +12 -0
- package/dist/react/features/chat-pane/model-options.js +55 -0
- package/dist/react/features/chat-pane/rules.d.ts +3 -2
- package/dist/react/features/chat-pane/rules.js +12 -4
- package/dist/react/features/chat-pane/types.d.ts +25 -1
- package/dist/react/hooks/assistant-chats-contracts.d.ts +164 -0
- package/dist/react/hooks/assistant-chats-contracts.js +2 -0
- package/dist/react/hooks/assistant-chats.d.ts +7 -0
- package/dist/react/hooks/assistant-chats.js +960 -0
- package/dist/react/hooks/attachment-preview-cache.d.ts +1 -1
- package/dist/react/hooks/attachment-preview-cache.js +1 -1
- package/dist/react/hooks/composer-draft-cache.d.ts +2 -2
- package/dist/react/hooks/composer-draft-cache.js +2 -2
- package/dist/react/hooks/fab-position.d.ts +90 -0
- package/dist/react/hooks/fab-position.js +271 -0
- package/dist/react/hooks/useConversation.d.ts +4 -1
- package/dist/react/hooks/useConversation.js +14 -12
- package/dist/react/hooks/useUserTextGuard.d.ts +11 -0
- package/dist/react/hooks/useUserTextGuard.js +19 -0
- package/dist/react/index.d.ts +12 -21
- package/dist/react/index.js +7 -0
- package/dist/server/durable-runs/accept.d.ts +25 -0
- package/dist/server/durable-runs/accept.js +21 -0
- package/dist/server/durable-runs/entry.d.ts +5 -0
- package/dist/server/durable-runs/entry.js +4 -0
- package/dist/server/durable-runs/recover.d.ts +5 -0
- package/dist/server/durable-runs/recover.js +147 -0
- package/dist/server/run-finalizer/ports.d.ts +2 -16
- package/dist/store/legacy/sqlite/messages.d.ts +24 -18
- package/dist/store/legacy/sqlite/messages.js +12 -2
- package/dist/store/pglite/index.d.ts +2 -1
- package/dist/store/postgres/index.d.ts +2 -1
- package/dist/store/runtime/chat-expiry-sweep.d.ts +40 -0
- package/dist/store/runtime/chat-expiry-sweep.js +70 -0
- package/dist/store/runtime/chat-history-store.d.ts +20 -0
- package/dist/store/runtime/chat-history-store.js +50 -0
- package/dist/store/runtime/durable-run-store.d.ts +21 -0
- package/dist/store/runtime/durable-run-store.js +342 -0
- package/dist/store/runtime/entry.d.ts +7 -0
- package/dist/store/runtime/entry.js +6 -0
- package/dist/store/runtime/kernel.d.ts +29 -0
- package/dist/store/runtime/kernel.js +2 -0
- package/dist/store/runtime/run-guarded-chat-store.d.ts +10 -0
- package/dist/store/runtime/run-guarded-chat-store.js +42 -0
- package/dist/store/runtime/run-ledger.d.ts +15 -0
- package/dist/store/runtime/run-ledger.js +206 -0
- package/dist/store/sql/boundary.d.ts +6 -0
- package/dist/store/sql/boundary.js +7 -1
- package/dist/store/sql/store.d.ts +3 -2
- package/dist/store/sql/store.js +9 -2
- package/dist/store/sqlite/index.d.ts +3 -2
- package/package.json +75 -10
|
@@ -1,121 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export interface ResolvedPageTarget {
|
|
3
|
-
readonly slug: string;
|
|
4
|
-
readonly title: string;
|
|
5
|
-
readonly path: string;
|
|
6
|
-
}
|
|
7
|
-
export type PageAction = {
|
|
8
|
-
readonly type: 'navigate';
|
|
9
|
-
readonly target: ResolvedPageTarget;
|
|
10
|
-
readonly auto: boolean;
|
|
11
|
-
} | {
|
|
12
|
-
readonly type: 'scroll_to' | 'highlight';
|
|
13
|
-
readonly target: ResolvedPageTarget;
|
|
14
|
-
};
|
|
15
|
-
export type NavigateAction = Extract<PageAction, {
|
|
16
|
-
type: 'navigate';
|
|
17
|
-
}>;
|
|
18
|
-
export type NonNavigateAction = Exclude<PageAction, {
|
|
19
|
-
type: 'navigate';
|
|
20
|
-
}>;
|
|
21
|
-
export interface DirectiveCarryingEvent {
|
|
22
|
-
readonly kind: string;
|
|
23
|
-
readonly name?: string;
|
|
24
|
-
readonly data?: unknown;
|
|
25
|
-
}
|
|
26
|
-
export interface SplitPageActions {
|
|
27
|
-
readonly navigate: NavigateAction | null;
|
|
28
|
-
readonly other: NonNavigateAction | null;
|
|
29
|
-
}
|
|
30
|
-
/** Narrows an untrusted, bare queued action. URL authorization remains a separate host policy. O(1). */
|
|
31
|
-
export declare function isQueuedPageAction(args: {
|
|
32
|
-
value: unknown;
|
|
33
|
-
}): args is {
|
|
34
|
-
value: PageAction;
|
|
35
|
-
};
|
|
36
|
-
/** Narrows an untrusted directive envelope, dropping unknown kinds. O(1). */
|
|
37
|
-
export declare function isPageActionDirective(args: {
|
|
38
|
-
value: unknown;
|
|
39
|
-
}): args is {
|
|
40
|
-
value: {
|
|
41
|
-
kind: 'page_action';
|
|
42
|
-
action: PageAction;
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
/** Preserves valid action order while ignoring unrelated events. O(events) time and output space. */
|
|
46
|
-
export declare function extractPageActions({ events, directiveEventName }: {
|
|
47
|
-
events: readonly DirectiveCarryingEvent[] | undefined;
|
|
48
|
-
directiveEventName: string;
|
|
49
|
-
}): PageAction[];
|
|
50
|
-
/** Returns only the first navigation and first page treatment. O(actions), O(1) extra space. */
|
|
51
|
-
export declare function splitPageActions({ actions }: {
|
|
52
|
-
actions: readonly PageAction[];
|
|
53
|
-
}): SplitPageActions;
|
|
54
|
-
export interface TargetSelectionPolicy {
|
|
55
|
-
readonly candidateSelector: string;
|
|
56
|
-
readonly excludedAncestorSelector: string;
|
|
57
|
-
}
|
|
58
|
-
/** Exact title match first, case-insensitive fallback; never searches excluded UI. O(candidates). */
|
|
59
|
-
export declare function findTargetElement({ title, root, selection }: {
|
|
60
|
-
title: string;
|
|
61
|
-
root: Pick<ParentNode, 'querySelectorAll'>;
|
|
62
|
-
selection: TargetSelectionPolicy;
|
|
63
|
-
}): Element | null;
|
|
64
|
-
export interface PageActionTimerPort {
|
|
65
|
-
schedule(args: {
|
|
66
|
-
callback: () => void;
|
|
67
|
-
delayMs: number;
|
|
68
|
-
}): unknown;
|
|
69
|
-
cancel(args: {
|
|
70
|
-
handle: unknown;
|
|
71
|
-
}): void;
|
|
72
|
-
}
|
|
73
|
-
export interface PageActionPort {
|
|
74
|
-
allows(args: {
|
|
75
|
-
action: PageAction;
|
|
76
|
-
}): boolean;
|
|
77
|
-
navigate(args: {
|
|
78
|
-
action: NavigateAction;
|
|
79
|
-
}): boolean;
|
|
80
|
-
execute(args: {
|
|
81
|
-
action: NonNavigateAction;
|
|
82
|
-
}): boolean;
|
|
83
|
-
clearHighlight(args: Record<string, never>): void;
|
|
84
|
-
}
|
|
85
|
-
export interface BrowserPageActionController extends PageActionPort {
|
|
86
|
-
applyHighlight(args: {
|
|
87
|
-
element: Element;
|
|
88
|
-
}): void;
|
|
89
|
-
scrollToElement(args: {
|
|
90
|
-
element: Element;
|
|
91
|
-
}): void;
|
|
92
|
-
}
|
|
93
|
-
export interface BrowserPageActionsArgs {
|
|
94
|
-
readonly root: Pick<ParentNode, 'querySelectorAll'>;
|
|
95
|
-
readonly selection: TargetSelectionPolicy;
|
|
96
|
-
readonly allowPath: (args: {
|
|
97
|
-
path: string;
|
|
98
|
-
}) => boolean;
|
|
99
|
-
readonly navigate: (args: {
|
|
100
|
-
path: string;
|
|
101
|
-
}) => void;
|
|
102
|
-
readonly reducedMotion: (args: Record<string, never>) => boolean;
|
|
103
|
-
readonly scroll: (args: {
|
|
104
|
-
element: Element;
|
|
105
|
-
options: ScrollIntoViewOptions;
|
|
106
|
-
}) => void;
|
|
107
|
-
readonly timers: PageActionTimerPort;
|
|
108
|
-
readonly highlightClass: string;
|
|
109
|
-
readonly fadeAnimationName: string;
|
|
110
|
-
readonly cleanupFallbackMs: number;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Creates page effects with injected DOM, navigation, URL policy, scroll and timers.
|
|
114
|
-
* At most one highlight belongs to this instance; cleanup and navigation remove it.
|
|
115
|
-
* URL policy is checked before queueing, navigation or page effects. Stale timers and descendant
|
|
116
|
-
* animation events cannot remove the current highlight. Navigation paths are resolved by the host,
|
|
117
|
-
* never guessed from a title; each controller owns its mutable highlight state.
|
|
118
|
-
* @complexity Execute O(candidates); other operations O(1), with one timer/listener retained.
|
|
119
|
-
*/
|
|
120
|
-
export declare function createBrowserPageActions(args: BrowserPageActionsArgs): BrowserPageActionController;
|
|
1
|
+
export * from '../../core/browser/page-actions.js';
|
|
121
2
|
//# sourceMappingURL=page-actions.d.ts.map
|
|
@@ -1,113 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
}
|
|
4
|
-
function target(value) {
|
|
5
|
-
return record(value) && typeof value.slug === 'string' && typeof value.title === 'string' && typeof value.path === 'string';
|
|
6
|
-
}
|
|
7
|
-
/** Narrows an untrusted, bare queued action. URL authorization remains a separate host policy. O(1). */
|
|
8
|
-
export function isQueuedPageAction(args) {
|
|
9
|
-
const { value } = args;
|
|
10
|
-
if (!record(value) || !target(value.target))
|
|
11
|
-
return false;
|
|
12
|
-
return value.type === 'navigate' ? typeof value.auto === 'boolean' : value.type === 'scroll_to' || value.type === 'highlight';
|
|
13
|
-
}
|
|
14
|
-
/** Narrows an untrusted directive envelope, dropping unknown kinds. O(1). */
|
|
15
|
-
export function isPageActionDirective(args) {
|
|
16
|
-
const { value } = args;
|
|
17
|
-
return record(value) && value.kind === 'page_action' && isQueuedPageAction({ value: value.action });
|
|
18
|
-
}
|
|
19
|
-
/** Preserves valid action order while ignoring unrelated events. O(events) time and output space. */
|
|
20
|
-
export function extractPageActions({ events, directiveEventName }) {
|
|
21
|
-
const actions = [];
|
|
22
|
-
for (const event of events ?? []) {
|
|
23
|
-
const envelope = { value: event.data };
|
|
24
|
-
if (event.kind === 'ext' && event.name === directiveEventName && isPageActionDirective(envelope)) {
|
|
25
|
-
actions.push(envelope.value.action);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return actions;
|
|
29
|
-
}
|
|
30
|
-
/** Returns only the first navigation and first page treatment. O(actions), O(1) extra space. */
|
|
31
|
-
export function splitPageActions({ actions }) {
|
|
32
|
-
return {
|
|
33
|
-
navigate: actions.find((a) => a.type === 'navigate') ?? null,
|
|
34
|
-
other: actions.find((a) => a.type !== 'navigate') ?? null,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
/** Exact title match first, case-insensitive fallback; never searches excluded UI. O(candidates). */
|
|
38
|
-
export function findTargetElement({ title, root, selection }) {
|
|
39
|
-
const needle = title.trim();
|
|
40
|
-
if (!needle)
|
|
41
|
-
return null;
|
|
42
|
-
const candidates = Array.from(root.querySelectorAll(selection.candidateSelector))
|
|
43
|
-
.filter(el => !el.closest(selection.excludedAncestorSelector));
|
|
44
|
-
return candidates.find(el => (el.textContent ?? '').trim() === needle)
|
|
45
|
-
?? candidates.find(el => (el.textContent ?? '').trim().toLowerCase() === needle.toLowerCase()) ?? null;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Creates page effects with injected DOM, navigation, URL policy, scroll and timers.
|
|
49
|
-
* At most one highlight belongs to this instance; cleanup and navigation remove it.
|
|
50
|
-
* URL policy is checked before queueing, navigation or page effects. Stale timers and descendant
|
|
51
|
-
* animation events cannot remove the current highlight. Navigation paths are resolved by the host,
|
|
52
|
-
* never guessed from a title; each controller owns its mutable highlight state.
|
|
53
|
-
* @complexity Execute O(candidates); other operations O(1), with one timer/listener retained.
|
|
54
|
-
*/
|
|
55
|
-
export function createBrowserPageActions(args) {
|
|
56
|
-
if (!Number.isFinite(args.cleanupFallbackMs) || args.cleanupFallbackMs < 0)
|
|
57
|
-
throw new Error('Invalid highlight deadline');
|
|
58
|
-
let current = null;
|
|
59
|
-
const clearHighlight = (_args) => {
|
|
60
|
-
if (!current)
|
|
61
|
-
return;
|
|
62
|
-
const old = current;
|
|
63
|
-
current = null;
|
|
64
|
-
args.timers.cancel({ handle: old.handle });
|
|
65
|
-
old.element.removeEventListener('animationend', old.onEnd);
|
|
66
|
-
old.element.classList.remove(args.highlightClass);
|
|
67
|
-
};
|
|
68
|
-
function highlight(element) {
|
|
69
|
-
if (current?.element === element)
|
|
70
|
-
return;
|
|
71
|
-
clearHighlight({});
|
|
72
|
-
const onEnd = event => {
|
|
73
|
-
if (event.target === element && event.animationName === args.fadeAnimationName)
|
|
74
|
-
clearHighlight({});
|
|
75
|
-
};
|
|
76
|
-
element.classList.add(args.highlightClass);
|
|
77
|
-
element.addEventListener('animationend', onEnd);
|
|
78
|
-
// Identity check makes even a late callback from a canceled timer harmless.
|
|
79
|
-
const handle = args.timers.schedule({ delayMs: args.cleanupFallbackMs, callback: () => {
|
|
80
|
-
if (current?.element === element && current.onEnd === onEnd)
|
|
81
|
-
clearHighlight({});
|
|
82
|
-
} });
|
|
83
|
-
current = { element, handle, onEnd };
|
|
84
|
-
}
|
|
85
|
-
const scrollToElement = ({ element }) => {
|
|
86
|
-
args.scroll({ element, options: { behavior: args.reducedMotion({}) ? 'auto' : 'smooth', block: 'center' } });
|
|
87
|
-
};
|
|
88
|
-
return {
|
|
89
|
-
applyHighlight({ element }) { highlight(element); },
|
|
90
|
-
scrollToElement,
|
|
91
|
-
allows({ action }) { return args.allowPath({ path: action.target.path }); },
|
|
92
|
-
navigate({ action }) {
|
|
93
|
-
if (!args.allowPath({ path: action.target.path }))
|
|
94
|
-
return false;
|
|
95
|
-
clearHighlight({});
|
|
96
|
-
args.navigate({ path: action.target.path });
|
|
97
|
-
return true;
|
|
98
|
-
},
|
|
99
|
-
execute({ action }) {
|
|
100
|
-
if (!args.allowPath({ path: action.target.path }))
|
|
101
|
-
return false;
|
|
102
|
-
const element = findTargetElement({ title: action.target.title, root: args.root, selection: args.selection });
|
|
103
|
-
if (!element)
|
|
104
|
-
return false;
|
|
105
|
-
scrollToElement({ element });
|
|
106
|
-
if (action.type === 'highlight')
|
|
107
|
-
highlight(element);
|
|
108
|
-
return true;
|
|
109
|
-
},
|
|
110
|
-
clearHighlight,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
1
|
+
// Compatibility path; shared page-action decisions belong to chat core.
|
|
2
|
+
export * from '../../core/browser/page-actions.js';
|
|
113
3
|
//# sourceMappingURL=page-actions.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ChatTransport } from '../../core/transport.js';
|
|
2
2
|
import type { ChatPaneAgent } from '../../react/chat-pane.js';
|
|
3
3
|
import type { BrowserSessionStore } from '../browser/session-store.js';
|
|
4
|
-
import type { PageActionPort } from '
|
|
4
|
+
import type { PageActionPort } from '../../core/browser/page-actions.js';
|
|
5
5
|
import type { EmbedHeaderCopy, EscapeKeyPort } from './header.js';
|
|
6
6
|
export { EmbedChatHeader } from './header.js';
|
|
7
7
|
export type { EmbedChatHeaderArgs, EmbedHeaderCopy, EscapeKeyPort } from './header.js';
|
|
@@ -5,7 +5,7 @@ import { isTerminalRunStatus } from '../../core/messages.js';
|
|
|
5
5
|
import { ChatPane } from '../../react/chat-pane.js';
|
|
6
6
|
import { ChatFab } from '../../react/components/ChatFab.js';
|
|
7
7
|
import { JiniChatProvider } from '../../react/components/JiniChatProvider.js';
|
|
8
|
-
import { extractPageActions, splitPageActions, isQueuedPageAction } from '
|
|
8
|
+
import { extractPageActions, splitPageActions, isQueuedPageAction } from '../../core/browser/page-actions.js';
|
|
9
9
|
import { EmbedChatHeader } from './header.js';
|
|
10
10
|
export { EmbedChatHeader } from './header.js';
|
|
11
11
|
const shellStyles = `
|
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module artifact-types
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* shape r4b §2 specifies, with the intent that a future pass re-points every
|
|
12
|
-
* import below at `@jini-ai/renderers-react`'s real exports once that package
|
|
13
|
-
* lands, deletes this file, and removes the re-export from `index.ts`.
|
|
4
|
+
* Chat's public streaming artifact contracts, used by useArtifactStream and
|
|
5
|
+
* ProjectContextValue. The renderer implementation is available in @jini-ai/ui,
|
|
6
|
+
* but direct delegation would change this compatibility surface: chat registers
|
|
7
|
+
* mutably, resolves newest-first, returns unregister handles, accepts files
|
|
8
|
+
* without manifests and returns the matched file. UI's registry is immutable,
|
|
9
|
+
* replaces registrations by ID, requires a manifest and returns that manifest;
|
|
10
|
+
* its predicate and resolve signatures also pass hints as a separate option.
|
|
14
11
|
*
|
|
15
|
-
* TODO(
|
|
16
|
-
*
|
|
17
|
-
* re-exports) once `@jini-ai/renderers-react` ships real `ArtifactFile`/
|
|
18
|
-
* `ArtifactRenderer`/`RendererRegistry` implementations. Do not block this
|
|
19
|
-
* package's other work on that package landing first.
|
|
12
|
+
* TODO(renderer-delegation): Coordinator must resolve these contracts before
|
|
13
|
+
* delegating to UI. A wildcard re-export is not a behavior-preserving migration.
|
|
20
14
|
*/
|
|
21
15
|
import type { ArtifactManifest } from '../core/index.js';
|
|
22
16
|
/** Generic artifact-file shape a host's project/workspace file maps onto. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type UserTextRedactionOptions } from '../../core/user-text-redaction.js';
|
|
1
2
|
import type { QuestionForm as QuestionFormType } from '../../core/index.js';
|
|
2
3
|
export interface QuestionFormFileSubmission {
|
|
3
4
|
questionId: string;
|
|
@@ -9,7 +10,7 @@ export interface QuestionFormHandle {
|
|
|
9
10
|
/** Submit with no answers — every question is optional, so this records each as "(skipped)" and moves on. */
|
|
10
11
|
skipAll: () => void;
|
|
11
12
|
}
|
|
12
|
-
export interface QuestionFormProps {
|
|
13
|
+
export interface QuestionFormProps extends UserTextRedactionOptions {
|
|
13
14
|
form: QuestionFormType;
|
|
14
15
|
/** Whether the user can still submit answers — the host disables this once the turn is no longer the most recent one. */
|
|
15
16
|
interactive: boolean;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CREDENTIAL_CARD_GUIDANCE, SECRET_REDACTED_NOTICE } from '../../core/user-text-redaction.js';
|
|
3
|
+
import { useUserTextGuard } from '../hooks/useUserTextGuard.js';
|
|
2
4
|
/**
|
|
3
5
|
* @module QuestionForm
|
|
4
6
|
*
|
|
@@ -13,15 +15,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
13
15
|
* string wrapped in `useT()`.
|
|
14
16
|
*/
|
|
15
17
|
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
|
|
16
|
-
import { formatFormAnswers, formOptionValueForLabel } from '../../core/index.js';
|
|
18
|
+
import { formatFormAnswers, formOptionValueForLabel, redactQuestionAnswers, isSecretQuestion, withoutSecretQuestionDefaults, questionTextInputProps, questionTextareaProps } from '../../core/index.js';
|
|
17
19
|
import { useT } from '../hooks/context.js';
|
|
18
|
-
export const QuestionForm = forwardRef(function QuestionForm({ form, interactive, submittedAnswers, hideInternalSubmit = false, draftAnswers, onReadyChange, onDraftChange, onAnswerChange, onSubmit }, ref) {
|
|
20
|
+
export const QuestionForm = forwardRef(function QuestionForm({ form: incomingForm, interactive, submittedAnswers, hideInternalSubmit = false, draftAnswers, onReadyChange, onDraftChange, onAnswerChange, onSubmit, redactUserText, onSecretRedacted }, ref) {
|
|
19
21
|
const t = useT();
|
|
22
|
+
const form = useMemo(() => withoutSecretQuestionDefaults({ form: incomingForm }), [incomingForm]);
|
|
23
|
+
const guard = useUserTextGuard({ ...(redactUserText ? { redactUserText } : {}), ...(onSecretRedacted ? { onSecretRedacted } : {}) });
|
|
20
24
|
const initial = useMemo(() => buildInitialState(form, submittedAnswers, draftAnswers), [form, submittedAnswers, draftAnswers]);
|
|
21
25
|
const [answers, setAnswers] = useState(initial);
|
|
22
26
|
const [fileAnswers, setFileAnswers] = useState({});
|
|
23
27
|
const locked = !interactive || !onSubmit || submittedAnswers !== undefined;
|
|
24
|
-
const currentAnswers = submittedAnswers
|
|
28
|
+
const currentAnswers = submittedAnswers ? redactQuestionAnswers({ form, answers: submittedAnswers }).answers : answers;
|
|
25
29
|
useEffect(() => {
|
|
26
30
|
setFileAnswers({});
|
|
27
31
|
}, [form.id]);
|
|
@@ -51,8 +55,8 @@ export const QuestionForm = forwardRef(function QuestionForm({ form, interactive
|
|
|
51
55
|
return;
|
|
52
56
|
const next = { ...answers, [id]: value };
|
|
53
57
|
setAnswers(next);
|
|
54
|
-
onDraftChange?.(draftSafeAnswers(form, next));
|
|
55
|
-
onAnswerChange?.(id, value);
|
|
58
|
+
onDraftChange?.(redactQuestionAnswers({ form, answers: draftSafeAnswers(form, next) }).answers);
|
|
59
|
+
onAnswerChange?.(id, redactQuestionAnswers({ form, answers: { [id]: value } }).answers[id]);
|
|
56
60
|
}
|
|
57
61
|
function toggleCheckbox(id, option, maxSelections) {
|
|
58
62
|
if (locked)
|
|
@@ -64,7 +68,7 @@ export const QuestionForm = forwardRef(function QuestionForm({ form, interactive
|
|
|
64
68
|
const next = has ? current.filter((v) => v !== option) : [...current, option];
|
|
65
69
|
const nextAnswers = { ...answers, [id]: next };
|
|
66
70
|
setAnswers(nextAnswers);
|
|
67
|
-
onDraftChange?.(draftSafeAnswers(form, nextAnswers));
|
|
71
|
+
onDraftChange?.(redactQuestionAnswers({ form, answers: draftSafeAnswers(form, nextAnswers) }).answers);
|
|
68
72
|
onAnswerChange?.(id, next);
|
|
69
73
|
}
|
|
70
74
|
function updateCheckboxCustom(q, raw) {
|
|
@@ -79,11 +83,13 @@ export const QuestionForm = forwardRef(function QuestionForm({ form, interactive
|
|
|
79
83
|
return;
|
|
80
84
|
if (!ready)
|
|
81
85
|
return;
|
|
86
|
+
const safe = redactQuestionAnswers({ form, answers }, { ...(redactUserText ? { redactUserText } : {}), onSecretRedacted: guard.notifyRedaction });
|
|
87
|
+
setAnswers(safe.answers);
|
|
82
88
|
const files = collectFileSubmissions(form, fileAnswers);
|
|
83
89
|
if (files.length > 0)
|
|
84
|
-
onSubmit(formatFormAnswers({ form: form, answers: answers }), answers, files);
|
|
90
|
+
onSubmit(formatFormAnswers({ form: form, answers: safe.answers }), safe.answers, files);
|
|
85
91
|
else
|
|
86
|
-
onSubmit(formatFormAnswers({ form: form, answers: answers }), answers);
|
|
92
|
+
onSubmit(formatFormAnswers({ form: form, answers: safe.answers }), safe.answers);
|
|
87
93
|
}
|
|
88
94
|
function handleSkipAll() {
|
|
89
95
|
if (locked || !onSubmit)
|
|
@@ -113,18 +119,18 @@ export const QuestionForm = forwardRef(function QuestionForm({ form, interactive
|
|
|
113
119
|
}, [onReadyChange, locked, ready]);
|
|
114
120
|
return (_jsxs("div", { className: `question-form${locked ? ' question-form-locked' : ''}`, "data-form-id": form.id, children: [_jsxs("div", { className: "question-form-head", children: [_jsx("span", { className: "question-form-icon", "aria-hidden": true, children: "?" }), _jsxs("div", { className: "question-form-titles", children: [_jsx("div", { className: "question-form-title", children: form.title }), form.description ? _jsx("div", { className: "question-form-desc", children: form.description }) : null] }), locked ? _jsx("span", { className: "question-form-pill", children: t('Answered') }) : null] }), _jsx("div", { className: "question-form-body", children: form.questions.map((q) => {
|
|
115
121
|
const value = currentAnswers[q.id];
|
|
116
|
-
return (_jsxs("div", { className: "qf-field", children: [_jsxs("label", { className: "qf-label", children: [_jsx("span", { children: q.label }), q.required ? (_jsx("span", { className: "qf-required", "aria-label": t('required'), children: "*" })) : null] }), q.help ? _jsx("div", { className: "qf-help", children: q.help }) : null, q.type === 'radio' && q.options ? (_jsx("div", { className: "qf-options", children: q.options.map((opt) => (_jsxs("label", { className: `qf-chip${value === opt.value ? ' qf-chip-on' : ''}`, title: opt.description, children: [_jsx("input", { type: "radio", name: `${form.id}-${q.id}`, value: opt.value, checked: value === opt.value, disabled: locked, "aria-label": opt.label, onChange: () => update(q.id, opt.value) }), _jsx(OptionCopy, { option: opt })] }, opt.value))) })) : null, q.type === 'radio' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null, q.type === 'checkbox' && q.options ? (_jsx("div", { className: "qf-options", children: q.options.map((opt) => {
|
|
122
|
+
return (_jsxs("div", { className: "qf-field", children: [_jsxs("label", { className: "qf-label", children: [_jsx("span", { children: q.label }), q.required ? (_jsx("span", { className: "qf-required", "aria-label": t('required'), children: "*" })) : null] }), q.help ? _jsx("div", { className: "qf-help", children: q.help }) : null, isSecretQuestion({ question: q }) ? _jsx("div", { className: "qf-help", children: t(CREDENTIAL_CARD_GUIDANCE) }) : null, q.type === 'radio' && q.options ? (_jsx("div", { className: "qf-options", children: q.options.map((opt) => (_jsxs("label", { className: `qf-chip${value === opt.value ? ' qf-chip-on' : ''}`, title: opt.description, children: [_jsx("input", { type: "radio", name: `${form.id}-${q.id}`, value: opt.value, checked: value === opt.value, disabled: locked, "aria-label": opt.label, onChange: () => update(q.id, opt.value) }), _jsx(OptionCopy, { option: opt })] }, opt.value))) })) : null, q.type === 'radio' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null, q.type === 'checkbox' && q.options ? (_jsx("div", { className: "qf-options", children: q.options.map((opt) => {
|
|
117
123
|
const arr = Array.isArray(value) ? value : [];
|
|
118
124
|
const on = arr.includes(opt.value);
|
|
119
125
|
const maxed = q.maxSelections !== undefined && !on && arr.length >= q.maxSelections;
|
|
120
126
|
return (_jsxs("label", { title: opt.description, className: `qf-chip${on ? ' qf-chip-on' : ''}${maxed ? ' qf-chip-disabled' : ''}`, children: [_jsx("input", { type: "checkbox", value: opt.value, checked: on, disabled: locked || maxed, "aria-label": opt.label, onChange: () => toggleCheckbox(q.id, opt.value, q.maxSelections) }), _jsx(OptionCopy, { option: opt })] }, opt.value));
|
|
121
|
-
}) })) : null, q.type === 'checkbox' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customCheckboxValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => updateCheckboxCustom(q, next) })) : null, q.type === 'select' && q.options ? (_jsxs("select", { className: "qf-select", value: typeof value === 'string' && questionValueIsKnown(q, value) ? value : '', disabled: locked, onChange: (e) => update(q.id, e.target.value), children: [_jsx("option", { value: "", disabled: true, children: q.placeholder ?? t('Choose one') }), q.options.map((opt) => (_jsx("option", { value: opt.value, title: opt.description, children: opt.label }, opt.value)))] })) : null, q.type === 'select' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null, q.type === 'text' ? _jsx("input", {
|
|
127
|
+
}) })) : null, q.type === 'checkbox' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customCheckboxValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => updateCheckboxCustom(q, next) })) : null, q.type === 'select' && q.options ? (_jsxs("select", { className: "qf-select", value: typeof value === 'string' && questionValueIsKnown(q, value) ? value : '', disabled: locked, onChange: (e) => update(q.id, e.target.value), children: [_jsx("option", { value: "", disabled: true, children: q.placeholder ?? t('Choose one') }), q.options.map((opt) => (_jsx("option", { value: opt.value, title: opt.description, children: opt.label }, opt.value)))] })) : null, q.type === 'select' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null, q.type === 'text' ? _jsx("input", { ...questionTextInputProps({ question: q }), className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'number' ? _jsx("input", { type: "number", className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, min: q.min, max: q.max, step: q.step, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'range' ? (_jsxs("div", { className: "qf-range-wrap", children: [_jsx("input", { type: "range", className: "qf-range", value: typeof value === 'string' && value.trim() ? value : String(q.min ?? 0), min: q.min, max: q.max, step: q.step, disabled: locked, onChange: (e) => update(q.id, e.target.value) }), _jsx("output", { className: "qf-range-value", children: typeof value === 'string' && value.trim() ? value : String(q.min ?? 0) })] })) : null, q.type === 'date' || q.type === 'time' || q.type === 'datetime-local' ? _jsx("input", { type: q.type, className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'color' ? _jsx("input", { type: "color", className: "qf-color", value: normalizeColorInputValue(value), disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'url' || q.type === 'email' || q.type === 'tel' ? _jsx("input", { type: q.type, className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'file' ? (_jsxs("div", { className: "qf-file-wrap", children: [_jsx("input", { type: "file", className: "qf-file", multiple: q.multiple, accept: q.accept, disabled: locked, onChange: (e) => {
|
|
122
128
|
const files = Array.from(e.target.files ?? []);
|
|
123
129
|
const names = files.map((file) => file.name);
|
|
124
130
|
setFileAnswers((current) => ({ ...current, [q.id]: files }));
|
|
125
131
|
update(q.id, q.multiple ? names : (names[0] ?? ''));
|
|
126
|
-
} }), fileValueLabel(value) ? _jsx("div", { className: "qf-file-summary", children: fileValueLabel(value) }) : null] })) : null, q.type === 'switch' ? (_jsxs("label", { className: "qf-switch", children: [_jsx("input", { type: "checkbox", role: "switch", checked: value === 'true', disabled: locked, onChange: (e) => update(q.id, e.target.checked ? 'true' : 'false') }), _jsx("span", { "aria-hidden": true })] })) : null, q.type === 'textarea' ? _jsx("textarea", { className: "qf-textarea", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, rows: 3, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'direction-cards' && q.cards && q.cards.length > 0 ? (_jsx("div", { className: "qf-direction-cards", children: q.cards.map((card) => (_jsx(DirectionCardView, { card: card, formId: form.id, questionId: q.id, selected: value === card.id || value === card.label, disabled: locked, onSelect: () => update(q.id, card.id) }, card.id))) })) : null, q.type === 'direction-cards' && q.cards && q.cards.length > 0 && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null] }, q.id));
|
|
127
|
-
}) }), hideInternalSubmit ? null : (_jsxs("div", { className: "question-form-foot", children: [locked ? _jsx("span", { className: "qf-locked-note", children: submittedAnswers ? t('You answered this') : t('A newer message answered this') }) : _jsx("span", { className: "qf-hint", children: t('Answer above, then continue') }), !locked ? (_jsx("button", { type: "button", className: "primary", onClick: handleSubmit, disabled: !ready, title: ready ? t('Submit your answers') : t('Answer the required questions first'), children: form.submitLabel ?? t('Continue') })) : null] }))] }));
|
|
132
|
+
} }), fileValueLabel(value) ? _jsx("div", { className: "qf-file-summary", children: fileValueLabel(value) }) : null] })) : null, q.type === 'switch' ? (_jsxs("label", { className: "qf-switch", children: [_jsx("input", { type: "checkbox", role: "switch", checked: value === 'true', disabled: locked, onChange: (e) => update(q.id, e.target.checked ? 'true' : 'false') }), _jsx("span", { "aria-hidden": true })] })) : null, q.type === 'textarea' ? _jsx("textarea", { ...questionTextareaProps({ question: q }), className: "qf-textarea", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, rows: 3, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'direction-cards' && q.cards && q.cards.length > 0 ? (_jsx("div", { className: "qf-direction-cards", children: q.cards.map((card) => (_jsx(DirectionCardView, { card: card, formId: form.id, questionId: q.id, selected: value === card.id || value === card.label, disabled: locked, onSelect: () => update(q.id, card.id) }, card.id))) })) : null, q.type === 'direction-cards' && q.cards && q.cards.length > 0 && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null] }, q.id));
|
|
133
|
+
}) }), guard.secretRedacted ? _jsx("div", { role: "status", children: t(SECRET_REDACTED_NOTICE) }) : null, hideInternalSubmit ? null : (_jsxs("div", { className: "question-form-foot", children: [locked ? _jsx("span", { className: "qf-locked-note", children: submittedAnswers ? t('You answered this') : t('A newer message answered this') }) : _jsx("span", { className: "qf-hint", children: t('Answer above, then continue') }), !locked ? (_jsx("button", { type: "button", className: "primary", onClick: handleSubmit, disabled: !ready, title: ready ? t('Submit your answers') : t('Answer the required questions first'), children: form.submitLabel ?? t('Continue') })) : null] }))] }));
|
|
128
134
|
});
|
|
129
135
|
function OptionCopy({ option }) {
|
|
130
136
|
return (_jsxs("span", { className: "qf-chip-copy", children: [_jsx("span", { children: option.label }), option.description ? _jsx("span", { className: "qf-chip-desc", children: option.description }) : null] }));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { UserTextRedactionOptions } from '../../core/user-text-redaction.js';
|
|
1
2
|
import type { QuestionForm as QuestionFormType } from '../../core/index.js';
|
|
2
|
-
export interface QuestionsPanelProps {
|
|
3
|
+
export interface QuestionsPanelProps extends UserTextRedactionOptions {
|
|
3
4
|
form: QuestionFormType | null;
|
|
4
5
|
interactive: boolean;
|
|
5
6
|
/** Disables Continue/Skip while the turn is busy, without locking the form itself. */
|
|
@@ -9,5 +10,5 @@ export interface QuestionsPanelProps {
|
|
|
9
10
|
generating?: boolean;
|
|
10
11
|
onSubmit: (text: string, answers: Record<string, string | string[]>) => void;
|
|
11
12
|
}
|
|
12
|
-
export declare function QuestionsPanel({ form, interactive, submitDisabled, submittedAnswers, generating, onSubmit }: QuestionsPanelProps): import("react").JSX.Element | null;
|
|
13
|
+
export declare function QuestionsPanel({ form, interactive, submitDisabled, submittedAnswers, generating, onSubmit, redactUserText, onSecretRedacted }: QuestionsPanelProps): import("react").JSX.Element | null;
|
|
13
14
|
//# sourceMappingURL=QuestionsPanel.d.ts.map
|
|
@@ -20,7 +20,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
20
20
|
import { useRef, useState } from 'react';
|
|
21
21
|
import { useT } from '../hooks/context.js';
|
|
22
22
|
import { QuestionForm } from './QuestionForm.js';
|
|
23
|
-
export function QuestionsPanel({ form, interactive, submitDisabled = false, submittedAnswers, generating = false, onSubmit }) {
|
|
23
|
+
export function QuestionsPanel({ form, interactive, submitDisabled = false, submittedAnswers, generating = false, onSubmit, redactUserText, onSecretRedacted }) {
|
|
24
24
|
const t = useT();
|
|
25
25
|
const formRef = useRef(null);
|
|
26
26
|
const [ready, setReady] = useState(false);
|
|
@@ -28,6 +28,6 @@ export function QuestionsPanel({ form, interactive, submitDisabled = false, subm
|
|
|
28
28
|
return null;
|
|
29
29
|
const answered = submittedAnswers !== undefined;
|
|
30
30
|
const busy = submitDisabled || generating;
|
|
31
|
-
return (_jsxs("div", { className: "qp-panel", children: [_jsx(QuestionForm, { ref: formRef, form: form, interactive: interactive, ...(submittedAnswers !== undefined ? { submittedAnswers } : {}), hideInternalSubmit: true, onReadyChange: setReady, onSubmit: onSubmit }), !answered ? (_jsxs("div", { className: "qp-foot", children: [generating ? _jsx("span", { className: "qp-hint", children: t('Still generating…') }) : null, _jsx("button", { type: "button", className: "qp-skip", disabled: busy, onClick: () => formRef.current?.skipAll(), children: t('Skip') }), _jsx("button", { type: "button", className: "qp-continue primary", disabled: busy || !ready, onClick: () => formRef.current?.submit(), children: t('Continue') })] })) : null] }));
|
|
31
|
+
return (_jsxs("div", { className: "qp-panel", children: [_jsx(QuestionForm, { ref: formRef, form: form, interactive: interactive, ...(submittedAnswers !== undefined ? { submittedAnswers } : {}), hideInternalSubmit: true, ...(redactUserText ? { redactUserText } : {}), ...(onSecretRedacted ? { onSecretRedacted } : {}), onReadyChange: setReady, onSubmit: onSubmit }), !answered ? (_jsxs("div", { className: "qp-foot", children: [generating ? _jsx("span", { className: "qp-hint", children: t('Still generating…') }) : null, _jsx("button", { type: "button", className: "qp-skip", disabled: busy, onClick: () => formRef.current?.skipAll(), children: t('Skip') }), _jsx("button", { type: "button", className: "qp-continue primary", disabled: busy || !ready, onClick: () => formRef.current?.submit(), children: t('Continue') })] })) : null] }));
|
|
32
32
|
}
|
|
33
33
|
//# sourceMappingURL=QuestionsPanel.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CapabilityDef, CapabilityInputSchema, CapabilityRisk } from '@jini-ai/agentic';
|
|
2
2
|
/** @deprecated Use `CapabilityRisk` from `@jini-ai/agentic`. */
|
|
3
3
|
export type ChatPaneAgentToolRisk = CapabilityRisk;
|
|
4
4
|
/** @deprecated Use `CapabilityInputSchema` from `@jini-ai/agentic`. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { pickerModelValue, pickerCatalogNote, pickerModelOptions } from '../model-options.js';
|
|
2
3
|
import { createPortal } from 'react-dom';
|
|
3
4
|
import { AgentIcon, RemixIcon, SearchableModelSelect } from '@jini-ai/ui';
|
|
4
5
|
import { useT } from '../../../hooks/context.js';
|
|
@@ -30,11 +31,13 @@ function RuntimeAgentList({ orderedAgents, value, onChange, agentIconBasePath, t
|
|
|
30
31
|
function RuntimeModelSelects({ selectedAgent, value, onChange, t }) {
|
|
31
32
|
if (!selectedAgent)
|
|
32
33
|
return null;
|
|
33
|
-
|
|
34
|
+
const models = pickerModelOptions(selectedAgent);
|
|
35
|
+
const note = pickerCatalogNote(selectedAgent, t);
|
|
36
|
+
return (_jsxs("div", { className: "jini-runtime-models", children: [note ? _jsx("small", { children: note }) : null, models.length > 0 ? (_jsxs("label", { className: "jini-runtime-select", children: [_jsx("span", { children: t('Model') }), _jsxs("select", { "aria-label": t('Model'), value: pickerModelValue(selectedAgent, value.model), onChange: (event) => onChange({
|
|
34
37
|
agentId: value.agentId,
|
|
35
38
|
model: event.target.value,
|
|
36
39
|
...(value.reasoning ? { reasoning: value.reasoning } : {}),
|
|
37
|
-
}), children:
|
|
40
|
+
}), children: [_jsx("option", { value: "", disabled: true, children: t('Choose model') }), models.map((model) => (_jsx("option", { value: model.id, children: model.label }, model.id)))] })] })) : null, (selectedAgent.reasoningOptions?.length ?? 0) > 0 ? (_jsxs("label", { className: "jini-runtime-select", children: [_jsx("span", { children: t('Reasoning') }), _jsx("select", { "aria-label": t('Reasoning'), value: value.reasoning ?? '', onChange: (event) => onChange({
|
|
38
41
|
agentId: value.agentId,
|
|
39
42
|
...(value.model ? { model: value.model } : {}),
|
|
40
43
|
reasoning: event.target.value,
|
|
@@ -6,5 +6,5 @@ import type { ChatPaneProps } from '../types.js';
|
|
|
6
6
|
* @complexity Time/space: O(n) in rendered messages, agents, and suggestions.
|
|
7
7
|
* @overallScore 100/100
|
|
8
8
|
*/
|
|
9
|
-
export declare function ChatPane({ transport, agents: injectedAgents, runtimeAccess, runtimeStatusPollMs, title, variant, initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, onRescanAgents, scanningAgents, daemonOnline, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions, workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement, projectFileNames, uploadAttachments, attachmentAccept, disabled, runtimePickerPlacement, composerSlots, header, leadingAccessory, footer, className, style, }: ChatPaneProps): import("react").JSX.Element;
|
|
9
|
+
export declare function ChatPane({ transport, agents: injectedAgents, runtimeAccess, runtimeStatusPollMs, title, variant, initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents, daemonOnline, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions, workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement, projectFileNames, uploadAttachments, attachmentAccept, disabled, runtimePickerPlacement, composerSlots, header, leadingAccessory, footer, className, style, }: ChatPaneProps): import("react").JSX.Element;
|
|
10
10
|
//# sourceMappingURL=ChatPane.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { SECRET_REDACTED_NOTICE } from '../../../../core/user-text-redaction.js';
|
|
2
3
|
import { useEffect, useRef } from 'react';
|
|
3
4
|
import { WorkingDirPicker } from '@jini-ai/ui';
|
|
4
5
|
import { Composer } from '../../../components/Composer.js';
|
|
@@ -178,7 +179,7 @@ function ChatPaneComposerArea({ fileDrop, uploadAttachments, pane, disabled, una
|
|
|
178
179
|
* @complexity Time/space: O(n) in rendered messages, agents, and suggestions.
|
|
179
180
|
* @overallScore 100/100
|
|
180
181
|
*/
|
|
181
|
-
export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, runtimeAccess, runtimeStatusPollMs = 5_000, title, variant = 'workspace', initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, onRescanAgents, scanningAgents = false, daemonOnline = true, executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions = [], workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement = 'below', projectFileNames, uploadAttachments, attachmentAccept, disabled = false, runtimePickerPlacement = 'up', composerSlots, header, leadingAccessory, footer, className, style, }) {
|
|
182
|
+
export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, runtimeAccess, runtimeStatusPollMs = 5_000, title, variant = 'workspace', initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents = false, daemonOnline = true, executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions = [], workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement = 'below', projectFileNames, uploadAttachments, attachmentAccept, disabled = false, runtimePickerPlacement = 'up', composerSlots, header, leadingAccessory, footer, className, style, }) {
|
|
182
183
|
const t = useT();
|
|
183
184
|
const composerPlaceholder = useChatPaneComposerPlaceholder({ placeholder: placeholder, placeholders: placeholders });
|
|
184
185
|
const inventory = useChatPaneRuntimeInventory(definedProps({ source: {
|
|
@@ -207,6 +208,8 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
|
|
|
207
208
|
uploadAttachments,
|
|
208
209
|
onActivityChange,
|
|
209
210
|
onMessagesChange,
|
|
211
|
+
redactUserText,
|
|
212
|
+
onSecretRedacted,
|
|
210
213
|
workingDirectory,
|
|
211
214
|
initialWorkingDirectory,
|
|
212
215
|
onChangeWorkingDirectory,
|
|
@@ -265,6 +268,6 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
|
|
|
265
268
|
footerAccessories: (_jsx(AgentRuntimePicker, { agents: runtimeView.agents, value: pane.selection, onChange: pane.setSelection, ...definedProps({ source: { onRescan: runtimeView.rescanAgents } }), scanning: runtimeView.scanningAgents, daemonOnline: runtimeView.daemonOnline, placement: runtimePickerPlacement, executionMode: executionMode, apiModeAvailable: apiModeAvailable, ...definedProps({ source: { onExecutionModeChange, byokRuntime, onByokModelChange } }) })),
|
|
266
269
|
} });
|
|
267
270
|
const { rootRef, controlsRef } = useChatPaneControlsHeight();
|
|
268
|
-
return (_jsxs("section", { className: chatPaneClassName(variant, className), style: style, "data-activity": pane.activity, ref: rootRef, children: [_jsx("style", { "data-jini-chat-pane-styles": "true", children: CHAT_PANE_STYLES }), resolveChatPaneHeader(header, title, pane.reset, t), _jsxs("div", { className: "jini-chat-pane__body", children: [_jsx(MessageList, { messages: pane.conversation.messages, isStreaming: pane.conversation.isStreaming, scrollIntent: pane.conversation.scrollIntent, onScrolled: pane.conversation.acknowledgeScroll, pendingPrompt: pane.queuedPrompt === null ? null : { text: pane.queuedPrompt, onCancel: pane.cancelQueued }, ...(projectFileNames === undefined ? {} : { projectFileNames: new Set(projectFileNames) }) }), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [_jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: pane.composer.setDraft, t: t }), _jsx(ChatPaneStatusMessages, { unavailable: unavailable, scanningAgents: runtimeView.scanningAgents, connectingAgents: runtimeView.connectingAgents, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, conversationError: pane.conversation.error, attachmentError: pane.attachmentError, dropReadError: fileDrop.dropReadError, workingDirectoryError: pane.workingDirectoryError, workingDirectoryPending: pane.workingDirectoryPending, workingDirectoryInvalid: pane.workingDirectoryInvalid, runtimeInventoryError: inventory.runtimeInventoryError, typedAnswerNotice: pane.typedAnswerNotice, onSendAsNewMessage: () => void pane.sendAsNewMessage(), t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: uploadAttachments, pane: pane, disabled: disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: attachmentAccept, workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: workingDirectoryControlPlacement, t: t })] })] }), footer] }));
|
|
271
|
+
return (_jsxs("section", { className: chatPaneClassName(variant, className), style: style, "data-activity": pane.activity, ref: rootRef, children: [_jsx("style", { "data-jini-chat-pane-styles": "true", children: CHAT_PANE_STYLES }), resolveChatPaneHeader(header, title, pane.reset, t), _jsxs("div", { className: "jini-chat-pane__body", children: [_jsx(MessageList, { messages: pane.conversation.messages, isStreaming: pane.conversation.isStreaming, scrollIntent: pane.conversation.scrollIntent, onScrolled: pane.conversation.acknowledgeScroll, pendingPrompt: pane.queuedPrompt === null ? null : { text: pane.queuedPrompt, onCancel: pane.cancelQueued }, ...(projectFileNames === undefined ? {} : { projectFileNames: new Set(projectFileNames) }) }), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [_jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: pane.composer.setDraft, t: t }), pane.secretRedacted ? _jsx("div", { className: "jini-chat-pane__notice", role: "status", children: t(SECRET_REDACTED_NOTICE) }) : null, _jsx(ChatPaneStatusMessages, { unavailable: unavailable, scanningAgents: runtimeView.scanningAgents, connectingAgents: runtimeView.connectingAgents, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, conversationError: pane.conversation.error, attachmentError: pane.attachmentError, dropReadError: fileDrop.dropReadError, workingDirectoryError: pane.workingDirectoryError, workingDirectoryPending: pane.workingDirectoryPending, workingDirectoryInvalid: pane.workingDirectoryInvalid, runtimeInventoryError: inventory.runtimeInventoryError, typedAnswerNotice: pane.typedAnswerNotice, onSendAsNewMessage: () => void pane.sendAsNewMessage(), t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: uploadAttachments, pane: pane, disabled: disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: attachmentAccept, workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: workingDirectoryControlPlacement, t: t })] })] }), footer] }));
|
|
269
272
|
}
|
|
270
273
|
//# sourceMappingURL=ChatPane.js.map
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
* new run: 202 delivered it; 409 means nothing is waiting any more (answered, expired, or never
|
|
27
27
|
* open); anything else is a failure the human can retry. A rejection would collapse the last two.
|
|
28
28
|
*/
|
|
29
|
+
import { type UserTextRedactionOptions } from '../../../core/user-text-redaction.js';
|
|
29
30
|
import type { DeliverTypedAnswer } from './types.js';
|
|
30
|
-
export interface CreateTypedAnswerPosterOptions {
|
|
31
|
+
export interface CreateTypedAnswerPosterOptions extends UserTextRedactionOptions {
|
|
31
32
|
/** Path appended to `baseUrl`. Defaults to `/api/mcp-ui/tool-calls`, as for `createMcpUiToolCaller`. */
|
|
32
33
|
readonly path?: string;
|
|
33
34
|
/** Abandon a post after this many ms (reported as `'failed'`). Defaults to 30s. */
|
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module create-typed-answer-poster
|
|
3
|
+
*
|
|
4
|
+
* The client half of a typed answer: a ready-made {@link DeliverTypedAnswer} that posts text the
|
|
5
|
+
* human typed into the composer to the MCP-UI tool-call route, for the question a running agent is
|
|
6
|
+
* holding open. Pass it as `ChatPane`'s `deliverTypedAnswer`.
|
|
7
|
+
*
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <ChatPane deliverTypedAnswer={createTypedAnswerPoster({ baseUrl: '', fetch, toolName: 'ask_choice' })} />
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Sibling of `create-mcp-ui-tool-caller.ts`, and the same wire shape: `{toolName, params}` to the
|
|
13
|
+
* same route. The difference is what the params carry and what the caller needs back.
|
|
14
|
+
*
|
|
15
|
+
* ## Why the body names no exchange
|
|
16
|
+
*
|
|
17
|
+
* A human typing into the composer has never seen an exchange id; the only client-side copy lives
|
|
18
|
+
* inside the surface's own (model-influenced, sandboxed) HTML. So the body carries only the tool and
|
|
19
|
+
* the text under {@link TYPED_ANSWER_PARAM}, and the server resolves which open exchange — for that
|
|
20
|
+
* tool and the request's own authenticated principal — the answer is for (`@jini-ai/daemon`'s
|
|
21
|
+
* `findTypedAnswerTarget`).
|
|
22
|
+
*
|
|
23
|
+
* ## Why it resolves an outcome instead of throwing
|
|
24
|
+
*
|
|
25
|
+
* The pane must tell three cases apart and act differently on each, never by queueing the text as a
|
|
26
|
+
* new run: 202 delivered it; 409 means nothing is waiting any more (answered, expired, or never
|
|
27
|
+
* open); anything else is a failure the human can retry. A rejection would collapse the last two.
|
|
28
|
+
*/
|
|
29
|
+
import { redactUserText } from '../../../core/user-text-redaction.js';
|
|
1
30
|
/**
|
|
2
31
|
* The params key a typed answer travels under. Spelled here rather than imported because this
|
|
3
32
|
* browser package cannot depend on `@jini-ai/daemon`, which owns the canonical
|
|
@@ -28,6 +57,9 @@ export function createTypedAnswerPoster({ baseUrl, fetch, toolName }, options =
|
|
|
28
57
|
const endpoint = `${baseUrl.replace(/\/$/u, '')}${options.path ?? DEFAULT_PATH}`;
|
|
29
58
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
30
59
|
const deliver = async ({ text }) => {
|
|
60
|
+
const result = (options.redactUserText ?? redactUserText)({ text });
|
|
61
|
+
if (result.secretRedacted)
|
|
62
|
+
options.onSecretRedacted?.({ secretRedacted: true, count: result.count });
|
|
31
63
|
const controller = new AbortController();
|
|
32
64
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
33
65
|
const outcome = await fetch(endpoint, {
|
|
@@ -35,7 +67,7 @@ export function createTypedAnswerPoster({ baseUrl, fetch, toolName }, options =
|
|
|
35
67
|
// Same-origin cookie session: the human's own credentials authorize the answer.
|
|
36
68
|
credentials: 'same-origin',
|
|
37
69
|
headers: { ...options.headers, 'content-type': 'application/json' },
|
|
38
|
-
body: JSON.stringify({ toolName, params: { [TYPED_ANSWER_PARAM]: text } }),
|
|
70
|
+
body: JSON.stringify({ toolName, params: { [TYPED_ANSWER_PARAM]: result.text } }),
|
|
39
71
|
signal: controller.signal,
|
|
40
72
|
}).then((response) => deliveryFor(response.status), () => 'failed');
|
|
41
73
|
clearTimeout(timer);
|