@parallelworks/ui 0.76.0 → 0.77.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/adapter/types.d.ts +1 -0
- package/dist/ai/components/ChatActivity.d.ts +2 -0
- package/dist/ai/components/ChatActivity.js +31 -0
- package/dist/ai/components/SessionView.js +4 -1
- package/dist/ai/components/agent/AgentMessageParts.d.ts +1 -1
- package/dist/ai/components/agent/AgentMessageParts.js +31 -5
- package/dist/ai/core/ChatProvider.js +10 -1
- package/dist/ai/core/config.d.ts +10 -1
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/strings.d.ts +2 -0
- package/dist/ai/strings.js +2 -1
- package/dist/ai/types.d.ts +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { toolHeaderFor } from "../agent/toolfmt.js";
|
|
2
|
+
import { useChatConfig } from "../core/config.js";
|
|
3
|
+
import { useChat, useChatStream } from "../core/ChatProvider.js";
|
|
4
|
+
import ActivityLine from "./agent/ActivityLine.js";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import { useEffect, useState } from "react";
|
|
7
|
+
import { DateTime } from "luxon";
|
|
8
|
+
//#region src/ai/components/ChatActivity.tsx
|
|
9
|
+
/** An agent session's working line for the provider's reply, naming its current step. */
|
|
10
|
+
function ChatActivity() {
|
|
11
|
+
const { isStreaming, tools } = useChat();
|
|
12
|
+
const { streamingParts } = useChatStream();
|
|
13
|
+
const { strings } = useChatConfig();
|
|
14
|
+
const [startedAt, setStartedAt] = useState(null);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
setStartedAt(isStreaming ? DateTime.now().toUTC().toISO() : null);
|
|
17
|
+
}, [isStreaming]);
|
|
18
|
+
if (!isStreaming) return null;
|
|
19
|
+
const waiting = streamingParts.some((p) => p.kind === "approval" && !p.resolved);
|
|
20
|
+
const running = streamingParts.findLast((p) => p.kind === "tool_call" && p.status === "running");
|
|
21
|
+
let word;
|
|
22
|
+
if (waiting) word = strings.activity.waitingForAnswer;
|
|
23
|
+
else if (running?.kind === "tool_call") word = tools.find((t) => t.id === running.name)?.label ?? toolHeaderFor(running.name).label;
|
|
24
|
+
return /* @__PURE__ */ jsx(ActivityLine, {
|
|
25
|
+
testId: "chat-activity",
|
|
26
|
+
startedAt,
|
|
27
|
+
word
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { ChatActivity as default };
|
|
@@ -4,6 +4,7 @@ import DragOverlay from "./DragOverlay.js";
|
|
|
4
4
|
import ChatInput from "./ChatInput.js";
|
|
5
5
|
import { escapeClosesLayer } from "../core/useKeyboardShortcuts.js";
|
|
6
6
|
import ChatMessageList from "./ChatMessageList.js";
|
|
7
|
+
import ChatActivity from "./ChatActivity.js";
|
|
7
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
9
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
9
10
|
//#region src/ai/components/SessionView.tsx
|
|
@@ -36,9 +37,11 @@ function SessionView({ sessionKey, messages, allMessages, streamingMessage, queu
|
|
|
36
37
|
inputRef.current?.addFiles(files);
|
|
37
38
|
}, []);
|
|
38
39
|
const { isDragging, dragHandlers } = useDragDrop({ onFilesDropped });
|
|
40
|
+
const activity = composer?.activity ?? (!turn && running ? /* @__PURE__ */ jsx(ChatActivity, {}) : void 0);
|
|
39
41
|
const input = /* @__PURE__ */ jsx(ChatInput, {
|
|
40
42
|
ref: inputRef,
|
|
41
43
|
...composer,
|
|
44
|
+
activity,
|
|
42
45
|
onSend,
|
|
43
46
|
conversationId: sessionKey,
|
|
44
47
|
disabled: loading || !!composer?.disabled,
|
|
@@ -87,7 +90,7 @@ function SessionView({ sessionKey, messages, allMessages, streamingMessage, queu
|
|
|
87
90
|
...footer ? { footer } : {},
|
|
88
91
|
...label ? { label } : {},
|
|
89
92
|
...flush ? { flush } : {},
|
|
90
|
-
|
|
93
|
+
workingIndicator: workingIndicator ?? !activity,
|
|
91
94
|
inputElement: input
|
|
92
95
|
}),
|
|
93
96
|
error && input
|
|
@@ -20,7 +20,7 @@ type RenderItem = {
|
|
|
20
20
|
kind: 'rollup';
|
|
21
21
|
members: RunMember[];
|
|
22
22
|
};
|
|
23
|
-
export declare function buildRenderItems(parts: MessagePart[]): RenderItem[];
|
|
23
|
+
export declare function buildRenderItems(parts: MessagePart[], hostDrawn?: (toolName: string) => boolean): RenderItem[];
|
|
24
24
|
export default function AgentMessageParts({ message, isStreaming, onApprovalAnswer, }: {
|
|
25
25
|
message: PartRendererProps['message'];
|
|
26
26
|
isStreaming?: boolean;
|
|
@@ -232,7 +232,10 @@ function ToolStatusDot({ part }) {
|
|
|
232
232
|
}
|
|
233
233
|
/** A part's toggle row: its status mark and name, across the transcript's width. */
|
|
234
234
|
var rowSummaryClasses = "min-h-6 w-full";
|
|
235
|
-
|
|
235
|
+
/** A call's row: its status and signature, opening to its full arguments and
|
|
236
|
+
* result. `children` is a host's drawing of the call, in place of the
|
|
237
|
+
* one-line summary. */
|
|
238
|
+
function ToolCallBlock({ part, children }) {
|
|
236
239
|
const { strings } = useChatConfig();
|
|
237
240
|
const t = strings.agentTranscript;
|
|
238
241
|
const [expanded, setExpanded] = useState(false);
|
|
@@ -279,7 +282,11 @@ function ToolCallBlock({ part }) {
|
|
|
279
282
|
children: fullToolArgs(header.arg, part.args)
|
|
280
283
|
}), /* @__PURE__ */ jsx(ResultBody, { part })]
|
|
281
284
|
}),
|
|
282
|
-
|
|
285
|
+
children && /* @__PURE__ */ jsx("div", {
|
|
286
|
+
className: "ml-5 mt-1.5 min-w-0",
|
|
287
|
+
children
|
|
288
|
+
}),
|
|
289
|
+
!children && !isDiff && !expanded && part.status !== "waiting" && /* @__PURE__ */ jsx("div", {
|
|
283
290
|
className: cx("ml-5 mt-0.5 truncate text-[13px]", part.status === "error" ? "text-(--theme-error-text)" : "theme-muted-text"),
|
|
284
291
|
children: summary
|
|
285
292
|
}),
|
|
@@ -735,6 +742,21 @@ function TextBlock({ part, isStreaming }) {
|
|
|
735
742
|
}
|
|
736
743
|
function PartDispatch(props) {
|
|
737
744
|
const { slots } = useChatConfig();
|
|
745
|
+
const part = props.part;
|
|
746
|
+
const Tool = part.kind === "tool_call" ? slots.toolRenderers?.[part.name] : void 0;
|
|
747
|
+
if (Tool && part.kind === "tool_call") {
|
|
748
|
+
const approval = props.message.parts?.find((p) => p.kind === "approval" && p.id === part.id);
|
|
749
|
+
return /* @__PURE__ */ jsx(ToolCallBlock, {
|
|
750
|
+
part,
|
|
751
|
+
children: /* @__PURE__ */ jsx(Tool, {
|
|
752
|
+
part,
|
|
753
|
+
message: props.message,
|
|
754
|
+
...approval ? { approval } : {},
|
|
755
|
+
...props.isStreaming !== void 0 ? { isStreaming: props.isStreaming } : {},
|
|
756
|
+
...props.onApprovalAnswer ? { onApprovalAnswer: props.onApprovalAnswer } : {}
|
|
757
|
+
})
|
|
758
|
+
});
|
|
759
|
+
}
|
|
738
760
|
const Override = slots.partRenderers?.[props.part.kind];
|
|
739
761
|
if (Override) return /* @__PURE__ */ jsx(Override, { ...props });
|
|
740
762
|
return /* @__PURE__ */ jsx(BuiltinPart, {
|
|
@@ -783,7 +805,7 @@ function mergeReasoning(parts) {
|
|
|
783
805
|
...durationMs > 0 ? { durationMs } : {}
|
|
784
806
|
};
|
|
785
807
|
}
|
|
786
|
-
function buildRenderItems(parts) {
|
|
808
|
+
function buildRenderItems(parts, hostDrawn = () => false) {
|
|
787
809
|
const items = [];
|
|
788
810
|
let run = [];
|
|
789
811
|
const emitThoughts = (members) => {
|
|
@@ -825,9 +847,11 @@ function buildRenderItems(parts) {
|
|
|
825
847
|
run = [];
|
|
826
848
|
};
|
|
827
849
|
const decided = new Set(parts.flatMap((p) => p.kind === "tool_call" && p.decision ? [p.id] : []));
|
|
850
|
+
const drawnByHost = new Set(parts.flatMap((p) => p.kind === "tool_call" && hostDrawn(p.name) ? [p.id] : []));
|
|
828
851
|
parts.forEach((part, index) => {
|
|
829
852
|
if (part.kind === "approval" && part.resolved && decided.has(part.id)) return;
|
|
830
|
-
if (part.kind === "
|
|
853
|
+
if (part.kind === "approval" && drawnByHost.has(part.id)) return;
|
|
854
|
+
if (part.kind === "reasoning" || part.kind === "tool_call" && part.status === "ok" && !part.decision && !drawnByHost.has(part.id) && isGroupableTool(part.name)) {
|
|
831
855
|
run.push({
|
|
832
856
|
part,
|
|
833
857
|
index
|
|
@@ -871,7 +895,9 @@ function RollupGroup({ members, message }) {
|
|
|
871
895
|
}
|
|
872
896
|
function AgentMessageParts({ message, isStreaming, onApprovalAnswer }) {
|
|
873
897
|
const parts = message.parts ?? [];
|
|
874
|
-
const
|
|
898
|
+
const { slots } = useChatConfig();
|
|
899
|
+
const toolRenderers = slots.toolRenderers;
|
|
900
|
+
const items = useMemo(() => buildRenderItems(parts, (name) => !!toolRenderers?.[name]), [parts, toolRenderers]);
|
|
875
901
|
const lastIndex = parts.length - 1;
|
|
876
902
|
return /* @__PURE__ */ jsx("div", {
|
|
877
903
|
className: "w-full",
|
|
@@ -637,6 +637,14 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
637
637
|
at: DateTime.now().toUTC().toISO()
|
|
638
638
|
}
|
|
639
639
|
});
|
|
640
|
+
const instead = answer.allowed ? answer.instead : void 0;
|
|
641
|
+
if (instead) {
|
|
642
|
+
pausedParts = pausedParts.map((p) => p.kind === "tool_call" && p.id === approvalId ? {
|
|
643
|
+
...p,
|
|
644
|
+
name: instead
|
|
645
|
+
} : p);
|
|
646
|
+
updatePaused({ parts: pausedParts });
|
|
647
|
+
}
|
|
640
648
|
streamingToConversationRef.current = targetConversationId;
|
|
641
649
|
startStreaming(targetConversationId);
|
|
642
650
|
try {
|
|
@@ -652,7 +660,8 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
652
660
|
messageId: paused.id,
|
|
653
661
|
toolCallId: approvalId,
|
|
654
662
|
allowed: answer.allowed,
|
|
655
|
-
...answer.denyMessage ? { feedback: answer.denyMessage } : {}
|
|
663
|
+
...answer.denyMessage ? { feedback: answer.denyMessage } : {},
|
|
664
|
+
...instead ? { tool: instead } : {}
|
|
656
665
|
}
|
|
657
666
|
}, paused.id, (message) => updatePaused({ error: message }), (delta) => {
|
|
658
667
|
if (delta.type !== "tool_end" || delta.id !== approvalId) return false;
|
package/dist/ai/core/config.d.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { type ComponentType, type MouseEventHandler, type ReactNode } from 'react';
|
|
2
2
|
import type { Components } from 'streamdown';
|
|
3
3
|
import { type ChatStrings } from '../strings.js';
|
|
4
|
-
import type { ApprovalAnswerValue, ChatLinkTarget, ChatMessage, CspKind, MessagePart, MessagePartKind } from '../types.js';
|
|
4
|
+
import type { ApprovalAnswerValue, ApprovalPart, ChatLinkTarget, ChatMessage, CspKind, MessagePart, MessagePartKind, ToolCallPart } from '../types.js';
|
|
5
5
|
export interface PartRendererProps {
|
|
6
6
|
part: MessagePart;
|
|
7
7
|
message: ChatMessage;
|
|
8
8
|
isStreaming?: boolean;
|
|
9
9
|
onApprovalAnswer?: (id: string, answer: ApprovalAnswerValue) => void;
|
|
10
10
|
}
|
|
11
|
+
export interface ToolRendererProps {
|
|
12
|
+
part: ToolCallPart;
|
|
13
|
+
approval?: ApprovalPart;
|
|
14
|
+
message: ChatMessage;
|
|
15
|
+
isStreaming?: boolean;
|
|
16
|
+
/** Absent when the reader may not answer, such as a call someone else asked for. */
|
|
17
|
+
onApprovalAnswer?: (id: string, answer: ApprovalAnswerValue) => void;
|
|
18
|
+
}
|
|
11
19
|
export interface ChatLinkProps {
|
|
12
20
|
target: ChatLinkTarget;
|
|
13
21
|
className?: string;
|
|
@@ -27,6 +35,7 @@ export interface ChatUIConfig {
|
|
|
27
35
|
composerUsage?: ReactNode;
|
|
28
36
|
providerIcon?: (cspKind: CspKind, className?: string) => ReactNode;
|
|
29
37
|
partRenderers?: Partial<Record<MessagePartKind, ComponentType<PartRendererProps>>>;
|
|
38
|
+
toolRenderers?: Record<string, ComponentType<ToolRendererProps>>;
|
|
30
39
|
};
|
|
31
40
|
strings?: {
|
|
32
41
|
[S in keyof ChatStrings]?: Partial<ChatStrings[S]>;
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export { type SlashCommandOption, type SlashMenuConfig, slashMatches, } from './
|
|
|
27
27
|
export type { ComposerPastes, PasteUploadResult, } from './components/usePasteCards.js';
|
|
28
28
|
export { ChatProvider, type ChatProviderProps, type ChatStream, useChat, useChatStream, } from './core/ChatProvider.js';
|
|
29
29
|
export { type ChatAction, type ChatState, chatReducer, initialState, } from './core/chatReducer.js';
|
|
30
|
-
export type { PartRendererProps } from './core/config.js';
|
|
30
|
+
export type { PartRendererProps, ToolRendererProps } from './core/config.js';
|
|
31
31
|
export { type ChatLinkProps, type ChatUIConfig, resolveChatConfig, useChatConfig, } from './core/config.js';
|
|
32
32
|
export { FOCUS_SIDEBAR_SEARCH_EVENT } from './core/events.js';
|
|
33
33
|
export { getGreeting } from './core/greeting.js';
|
package/dist/ai/strings.d.ts
CHANGED
|
@@ -91,6 +91,8 @@ export interface ChatStrings {
|
|
|
91
91
|
stop: string;
|
|
92
92
|
/** The status words an agent's working line picks from, one per turn. */
|
|
93
93
|
words: readonly string[];
|
|
94
|
+
/** The working line while a call waits for the reader to allow it. */
|
|
95
|
+
waitingForAnswer: string;
|
|
94
96
|
};
|
|
95
97
|
emptyState: {
|
|
96
98
|
greeting: string | null;
|
package/dist/ai/strings.js
CHANGED
package/dist/ai/types.d.ts
CHANGED
|
@@ -100,6 +100,9 @@ export type ApprovalAnswerValue = {
|
|
|
100
100
|
allowed: boolean;
|
|
101
101
|
allowDir?: boolean;
|
|
102
102
|
denyMessage?: string;
|
|
103
|
+
/** A sibling tool to run in place of the one asked for, with the same
|
|
104
|
+
* arguments, such as destroying a cluster the model asked to stop. */
|
|
105
|
+
instead?: string;
|
|
103
106
|
} | {
|
|
104
107
|
kind: 'ask';
|
|
105
108
|
text: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parallelworks/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.0",
|
|
4
4
|
"description": "React UI components: a theme contract, icons, brand logos, primitives, lists, forms, a job graph, a code editor, a log viewer, a file explorer and an AI chat, each on its own subpath",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|