@parallelworks/ui 0.40.0 → 0.41.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/README.md +6 -0
- package/dist/ai/adapter/memoryStore.js +7 -0
- package/dist/ai/adapter/mock/mockAdapter.js +52 -17
- package/dist/ai/adapter/openai/createOpenAIStream.js +1 -1
- package/dist/ai/adapter/openai/index.d.ts +1 -1
- package/dist/ai/adapter/openai/sse.d.ts +8 -2
- package/dist/ai/adapter/openai/sse.js +40 -4
- package/dist/ai/adapter/openai/wire.d.ts +6 -0
- package/dist/ai/ai.css +4 -2
- package/dist/ai/core/ChatProvider.js +3 -3
- package/dist/ai/core/parts.d.ts +5 -0
- package/dist/ai/core/parts.js +28 -1
- package/dist/ai/ui/Markdown.js +4 -1
- package/dist/graph/Connectors.d.ts +11 -0
- package/dist/graph/Connectors.js +158 -0
- package/dist/graph/DependencyGraph.d.ts +2 -16
- package/dist/graph/DependencyGraph.js +375 -725
- package/dist/graph/GraphNode.d.ts +35 -0
- package/dist/graph/GraphNode.js +70 -0
- package/dist/graph/MatrixGroup.d.ts +4 -8
- package/dist/graph/MatrixGroup.js +27 -40
- package/dist/graph/connectorGeometry.d.ts +44 -0
- package/dist/graph/connectorGeometry.js +103 -0
- package/dist/graph/graphLayout.d.ts +24 -0
- package/dist/graph/graphLayout.js +205 -0
- package/dist/graph/lineage.d.ts +51 -0
- package/dist/graph/lineage.js +94 -0
- package/dist/graph/store.d.ts +10 -0
- package/dist/graph/store.js +25 -0
- package/dist/graph/viewport.d.ts +40 -0
- package/dist/graph/viewport.js +56 -0
- package/dist/styles.css +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -99,6 +99,12 @@ const engine = { ...createWorkflowEngine(), editing: workflowPackage }
|
|
|
99
99
|
|
|
100
100
|
Without `editing`, the graph lays its jobs out by their needs alone.
|
|
101
101
|
|
|
102
|
+
The graph draws thousands of jobs, and subworkflows opened inline several levels deep. Layout is
|
|
103
|
+
near linear in jobs and needs; hovering a job re-renders only the nodes and connectors whose
|
|
104
|
+
highlight changes, and panning, zooming and a running run's clock re-render none. The view fits
|
|
105
|
+
the whole graph, zooms out to 1% to show the shape of the largest, and in to 200% to read a node;
|
|
106
|
+
the wheel zooms by the same factor at every scale, about the pointer.
|
|
107
|
+
|
|
102
108
|
The editor checks YAML against the schemas a host registers with `configureEditorYaml` from
|
|
103
109
|
`@parallelworks/ui/editor`. The job, step, input and settings dialogs edit their YAML at their own
|
|
104
110
|
paths (`JOB_YAML_PATH`, `STEP_YAML_PATH`, `INPUT_YAML_PATH`, `SETTINGS_YAML_PATH`); `settingsSchemas`
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ChatAdapterError } from "./types.js";
|
|
2
|
+
import { applyPartDelta, finalizeParts } from "../core/parts.js";
|
|
2
3
|
//#region src/ai/adapter/memoryStore.ts
|
|
3
4
|
function newId() {
|
|
4
5
|
return crypto.randomUUID();
|
|
@@ -103,6 +104,7 @@ function createMemoryConversationStore(options) {
|
|
|
103
104
|
return async (req, handlers, signal) => {
|
|
104
105
|
let partialContent = "";
|
|
105
106
|
let partialReasoning = "";
|
|
107
|
+
let parts = [];
|
|
106
108
|
const wrappedHandlers = {
|
|
107
109
|
...handlers,
|
|
108
110
|
onContent: (chunk) => {
|
|
@@ -112,6 +114,10 @@ function createMemoryConversationStore(options) {
|
|
|
112
114
|
onReasoning: (chunk) => {
|
|
113
115
|
partialReasoning += chunk;
|
|
114
116
|
handlers.onReasoning?.(chunk);
|
|
117
|
+
},
|
|
118
|
+
onPart: (delta) => {
|
|
119
|
+
parts = applyPartDelta(parts, delta);
|
|
120
|
+
handlers.onPart?.(delta);
|
|
115
121
|
}
|
|
116
122
|
};
|
|
117
123
|
recordUser(req);
|
|
@@ -126,6 +132,7 @@ function createMemoryConversationStore(options) {
|
|
|
126
132
|
...result.reasoning ? { reasoning: result.reasoning } : {},
|
|
127
133
|
...result.responsesOutput.length ? { responsesOutput: result.responsesOutput } : {},
|
|
128
134
|
...result.toolCalls.length ? { toolCalls: result.toolCalls } : {},
|
|
135
|
+
...parts.length ? { parts: finalizeParts(parts) } : {},
|
|
129
136
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
130
137
|
});
|
|
131
138
|
return {
|
|
@@ -5,6 +5,7 @@ var CANNED_REPLIES = [
|
|
|
5
5
|
"Everything you see is generated locally. The mock adapter implements the same `ChatAdapter` interface as the OpenAI-compatible adapter and any backend's own, so the UI can't tell the difference.\n\n```ts\nconst adapter = createMockChatAdapter()\n```\n\nMarkdown, `inline code`, and code blocks render through the same pipeline.",
|
|
6
6
|
"Branching works too: edit one of your earlier messages or hit regenerate on a reply, then use the arrows to flip between branches."
|
|
7
7
|
];
|
|
8
|
+
var TOOL_REPLY = "Two clusters are running: **gpu-a** with 4 nodes, and **cpu-batch**, idle and scaled to zero.";
|
|
8
9
|
var THINKING_PREAMBLE = "Thinking about the question, weighing a few canned options, and picking the next reply in the rotation...";
|
|
9
10
|
function sleep(ms, signal) {
|
|
10
11
|
return new Promise((resolve, reject) => {
|
|
@@ -30,21 +31,32 @@ function createMockChatAdapter(options) {
|
|
|
30
31
|
conversations: store.conversations,
|
|
31
32
|
models: { async list() {
|
|
32
33
|
return {
|
|
33
|
-
models: [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
34
|
+
models: [
|
|
35
|
+
{
|
|
36
|
+
id: "mock:local/canned-small",
|
|
37
|
+
object: "model",
|
|
38
|
+
created: 0,
|
|
39
|
+
owned_by: "mock",
|
|
40
|
+
provider: "Mock Provider",
|
|
41
|
+
tool_calling_mode: "none"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "mock:local/canned-thinking",
|
|
45
|
+
object: "model",
|
|
46
|
+
created: 0,
|
|
47
|
+
owned_by: "mock",
|
|
48
|
+
provider: "Mock Provider",
|
|
49
|
+
tool_calling_mode: "none"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "mock:local/canned-tools",
|
|
53
|
+
object: "model",
|
|
54
|
+
created: 0,
|
|
55
|
+
owned_by: "mock",
|
|
56
|
+
provider: "Mock Provider",
|
|
57
|
+
tool_calling_mode: "native"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
48
60
|
unreachableSessions: [],
|
|
49
61
|
providerIssues: []
|
|
50
62
|
};
|
|
@@ -57,8 +69,31 @@ function createMockChatAdapter(options) {
|
|
|
57
69
|
reasoning += word;
|
|
58
70
|
handlers.onReasoning?.(word);
|
|
59
71
|
}
|
|
60
|
-
const
|
|
61
|
-
|
|
72
|
+
const usesTools = req.model.includes("tools");
|
|
73
|
+
if (usesTools) {
|
|
74
|
+
const id = `call-${crypto.randomUUID()}`;
|
|
75
|
+
handlers.onPart?.({
|
|
76
|
+
type: "tool_start",
|
|
77
|
+
id,
|
|
78
|
+
name: "list_clusters",
|
|
79
|
+
args: "{}"
|
|
80
|
+
});
|
|
81
|
+
await sleep(900, signal);
|
|
82
|
+
handlers.onPart?.({
|
|
83
|
+
type: "tool_end",
|
|
84
|
+
id,
|
|
85
|
+
result: JSON.stringify([{
|
|
86
|
+
name: "gpu-a",
|
|
87
|
+
status: "on",
|
|
88
|
+
nodes: 4
|
|
89
|
+
}, {
|
|
90
|
+
name: "cpu-batch",
|
|
91
|
+
status: "on",
|
|
92
|
+
nodes: 0
|
|
93
|
+
}])
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const reply = usesTools ? TOOL_REPLY : CANNED_REPLIES[replyIndex++ % CANNED_REPLIES.length] ?? "";
|
|
62
97
|
await sleep(250, signal);
|
|
63
98
|
let content = "";
|
|
64
99
|
for (const word of reply.split(/(?<= )/)) {
|
|
@@ -27,7 +27,7 @@ function createOpenAICompatibleStream(config) {
|
|
|
27
27
|
if (!res.ok) throw new ChatAdapterError(await errorMessageFromResponse(res), { status: res.status });
|
|
28
28
|
const reader = res.body?.getReader();
|
|
29
29
|
if (!reader) throw new ChatAdapterError("No response body");
|
|
30
|
-
return processSSEStream(reader, handlers.onContent, void 0, void 0, handlers.onReasoning, handlers.onMessageId, useResponses ? parseResponsesSSELine : void 0);
|
|
30
|
+
return processSSEStream(reader, handlers.onContent, void 0, void 0, handlers.onReasoning, handlers.onMessageId, useResponses ? parseResponsesSSELine : void 0, handlers.onPart);
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
//#endregion
|
|
@@ -3,4 +3,4 @@ export { apiErrorMessage, errorMessageFromResponse, statusFallbackMessage, } fro
|
|
|
3
3
|
export { createOpenAIChatAdapter, type OpenAIChatAdapterOptions, } from './openaiAdapter.js';
|
|
4
4
|
export { buildResponsesRequestBody, parseResponsesSSELine, } from './responses.js';
|
|
5
5
|
export { type ParsedSSELine, parseSSELine, processSSEStream, type SSELineParser, } from './sse.js';
|
|
6
|
-
export type { CompletionChunk, CompletionChunkChoice, CompletionMessage, CompletionRequest, ModelsWireResponse, } from './wire.js';
|
|
6
|
+
export type { CompletionChunk, CompletionChunkChoice, CompletionMessage, CompletionRequest, ModelsWireResponse, ToolResultChunk, } from './wire.js';
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { ToolCall, ToolCallDelta } from '../../types.js';
|
|
2
|
+
import type { PartDelta } from '../types.js';
|
|
3
|
+
import type { ToolResultChunk } from './wire.js';
|
|
2
4
|
export type ParsedSSELine = {
|
|
3
5
|
id?: string | undefined;
|
|
4
6
|
messageId?: string | undefined;
|
|
5
7
|
content?: string | undefined;
|
|
6
8
|
reasoningContent?: string | undefined;
|
|
7
9
|
toolCalls?: ToolCallDelta[] | undefined;
|
|
10
|
+
toolResult?: ToolResultChunk | undefined;
|
|
8
11
|
finishReason?: string | null | undefined;
|
|
9
12
|
model?: string | undefined;
|
|
10
13
|
error?: string | undefined;
|
|
@@ -12,8 +15,11 @@ export type ParsedSSELine = {
|
|
|
12
15
|
} | null;
|
|
13
16
|
export type SSELineParser = (line: string) => ParsedSSELine;
|
|
14
17
|
export declare function parseSSELine(line: string): ParsedSSELine;
|
|
15
|
-
/** Process an SSE stream from a ReadableStream reader
|
|
16
|
-
|
|
18
|
+
/** Process an SSE stream from a ReadableStream reader. With `onPart`, tool
|
|
19
|
+
* calls stream as parts; a call the gateway answered with a result is left
|
|
20
|
+
* off the returned `toolCalls`, which then hold only calls the caller must
|
|
21
|
+
* run. */
|
|
22
|
+
export declare function processSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>, onContent: (content: string) => void, onToolCall?: (toolCalls: ToolCall[]) => void, onError?: (error: string) => void, onReasoning?: (reasoning: string) => void, onMessageId?: (messageId: string) => void, parseLine?: SSELineParser, onPart?: (delta: PartDelta) => void): Promise<{
|
|
17
23
|
content: string;
|
|
18
24
|
messageId: string;
|
|
19
25
|
toolCalls: ToolCall[];
|
|
@@ -14,6 +14,7 @@ function parseSSELine(line) {
|
|
|
14
14
|
content: choice?.delta?.content,
|
|
15
15
|
reasoningContent: choice?.delta?.reasoning_content,
|
|
16
16
|
toolCalls: choice?.delta?.tool_calls ?? void 0,
|
|
17
|
+
toolResult: chunk.toolResult,
|
|
17
18
|
finishReason: choice?.finish_reason,
|
|
18
19
|
model: chunk.model
|
|
19
20
|
};
|
|
@@ -21,8 +22,11 @@ function parseSSELine(line) {
|
|
|
21
22
|
return null;
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
|
-
/** Process an SSE stream from a ReadableStream reader
|
|
25
|
-
|
|
25
|
+
/** Process an SSE stream from a ReadableStream reader. With `onPart`, tool
|
|
26
|
+
* calls stream as parts; a call the gateway answered with a result is left
|
|
27
|
+
* off the returned `toolCalls`, which then hold only calls the caller must
|
|
28
|
+
* run. */
|
|
29
|
+
async function processSSEStream(reader, onContent, onToolCall, onError, onReasoning, onMessageId, parseLine = parseSSELine, onPart) {
|
|
26
30
|
const decoder = new TextDecoder();
|
|
27
31
|
let assistantContent = "";
|
|
28
32
|
let assistantReasoning = "";
|
|
@@ -31,6 +35,19 @@ async function processSSEStream(reader, onContent, onToolCall, onError, onReason
|
|
|
31
35
|
let resolvedModel = null;
|
|
32
36
|
let responsesOutput = [];
|
|
33
37
|
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
38
|
+
const startedToolCalls = /* @__PURE__ */ new Set();
|
|
39
|
+
const answeredToolCalls = /* @__PURE__ */ new Set();
|
|
40
|
+
const startToolCall = (index) => {
|
|
41
|
+
const tc = toolCallsMap.get(index);
|
|
42
|
+
if (!onPart || !tc?.id || !tc.name || startedToolCalls.has(index)) return;
|
|
43
|
+
startedToolCalls.add(index);
|
|
44
|
+
onPart({
|
|
45
|
+
type: "tool_start",
|
|
46
|
+
id: tc.id,
|
|
47
|
+
name: tc.name,
|
|
48
|
+
args: tc.arguments
|
|
49
|
+
});
|
|
50
|
+
};
|
|
34
51
|
let lineBuffer = "";
|
|
35
52
|
while (true) {
|
|
36
53
|
const result = await reader.read();
|
|
@@ -64,13 +81,32 @@ async function processSSEStream(reader, onContent, onToolCall, onError, onReason
|
|
|
64
81
|
if (tc.index === void 0) continue;
|
|
65
82
|
const existing = toolCallsMap.get(tc.index);
|
|
66
83
|
if (existing) {
|
|
67
|
-
|
|
84
|
+
existing.id ||= tc.id || "";
|
|
85
|
+
existing.name ||= tc.function?.name || "";
|
|
86
|
+
if (tc.function?.arguments) {
|
|
87
|
+
existing.arguments += tc.function.arguments;
|
|
88
|
+
if (startedToolCalls.has(tc.index)) onPart?.({
|
|
89
|
+
type: "tool_update",
|
|
90
|
+
id: existing.id,
|
|
91
|
+
args: tc.function.arguments
|
|
92
|
+
});
|
|
93
|
+
}
|
|
68
94
|
} else toolCallsMap.set(tc.index, {
|
|
69
95
|
id: tc.id || "",
|
|
70
96
|
type: tc.type || "function",
|
|
71
97
|
name: tc.function?.name || "",
|
|
72
98
|
arguments: tc.function?.arguments || ""
|
|
73
99
|
});
|
|
100
|
+
startToolCall(tc.index);
|
|
101
|
+
}
|
|
102
|
+
if (parsed.toolResult) {
|
|
103
|
+
answeredToolCalls.add(parsed.toolResult.toolCallId);
|
|
104
|
+
onPart?.({
|
|
105
|
+
type: "tool_end",
|
|
106
|
+
id: parsed.toolResult.toolCallId,
|
|
107
|
+
result: parsed.toolResult.content,
|
|
108
|
+
...parsed.toolResult.isError ? { isError: true } : {}
|
|
109
|
+
});
|
|
74
110
|
}
|
|
75
111
|
}
|
|
76
112
|
}
|
|
@@ -89,7 +125,7 @@ async function processSSEStream(reader, onContent, onToolCall, onError, onReason
|
|
|
89
125
|
if (parsed.finishReason) finishReason = parsed.finishReason;
|
|
90
126
|
}
|
|
91
127
|
}
|
|
92
|
-
const toolCalls = Array.from(toolCallsMap.values()).map((tc) => ({
|
|
128
|
+
const toolCalls = Array.from(toolCallsMap.values()).filter((tc) => !answeredToolCalls.has(tc.id)).map((tc) => ({
|
|
93
129
|
id: tc.id,
|
|
94
130
|
type: tc.type,
|
|
95
131
|
function: {
|
|
@@ -30,9 +30,15 @@ export interface CompletionChunk {
|
|
|
30
30
|
object?: string;
|
|
31
31
|
created?: number;
|
|
32
32
|
messageId?: string;
|
|
33
|
+
toolResult?: ToolResultChunk;
|
|
33
34
|
choices?: CompletionChunkChoice[] | null;
|
|
34
35
|
usage?: TokenUsage;
|
|
35
36
|
}
|
|
37
|
+
export interface ToolResultChunk {
|
|
38
|
+
toolCallId: string;
|
|
39
|
+
content: string;
|
|
40
|
+
isError?: boolean;
|
|
41
|
+
}
|
|
36
42
|
export interface ModelsWireResponse {
|
|
37
43
|
object: string;
|
|
38
44
|
data: ChatModel[] | null;
|
package/dist/ai/ai.css
CHANGED
|
@@ -471,8 +471,10 @@
|
|
|
471
471
|
color: color-mix(in oklab, var(--theme-accent, #06354f) 70%, var(--theme-panel-bg, #fff));
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
-
/* The reply being written ends in the accent's caret.
|
|
475
|
-
|
|
474
|
+
/* The reply being written ends in the accent's caret. Matched by a class, not by the inline style
|
|
475
|
+
that sets the caret: a rule reaching past an element its style attribute selects makes every
|
|
476
|
+
inline style change anywhere on the page restyle that element's whole subtree. */
|
|
477
|
+
.chat-thread .markdown-streaming > :last-child::after {
|
|
476
478
|
color: var(--chat-accent);
|
|
477
479
|
}
|
|
478
480
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyPartDelta, finalizeParts } from "./parts.js";
|
|
1
|
+
import { applyPartDelta, finalizeParts, toolTurnMessages } from "./parts.js";
|
|
2
2
|
import { chatReducer, initialState } from "./chatReducer.js";
|
|
3
3
|
import { ChatConfigProvider, resolveChatConfig } from "./config.js";
|
|
4
4
|
import { providerIssueFor } from "./providerIssues.js";
|
|
@@ -392,7 +392,7 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
|
|
|
392
392
|
});
|
|
393
393
|
};
|
|
394
394
|
const apiMessages = [
|
|
395
|
-
...(isNewConversation ? [] : state.currentConversation?.messages || []).filter((m) => m.content || m.toolCalls?.length).
|
|
395
|
+
...(isNewConversation ? [] : state.currentConversation?.messages || []).filter((m) => m.content || m.toolCalls?.length || m.parts?.length).flatMap((m) => {
|
|
396
396
|
const msg = {
|
|
397
397
|
role: m.role,
|
|
398
398
|
content: m.content || null
|
|
@@ -401,7 +401,7 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
|
|
|
401
401
|
if (m.role === "assistant" && m.toolCalls?.length) msg.tool_calls = m.toolCalls;
|
|
402
402
|
if (m.role === "assistant" && m.responsesOutput?.length && m.model === state.selectedProvider) msg.responses_output = m.responsesOutput;
|
|
403
403
|
if (m.role === "tool" && m.toolCallId) msg.tool_call_id = m.toolCallId;
|
|
404
|
-
return msg;
|
|
404
|
+
return m.role === "assistant" ? toolTurnMessages(m.parts, msg) : [msg];
|
|
405
405
|
}),
|
|
406
406
|
...precedingApiMessages,
|
|
407
407
|
{
|
package/dist/ai/core/parts.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CompletionMessage } from '../adapter/openai/wire.js';
|
|
1
2
|
import type { PartDelta } from '../adapter/types.js';
|
|
2
3
|
import type { MessagePart } from '../types.js';
|
|
3
4
|
/** Fold one streamed PartDelta into a parts timeline. Pure: returns a new
|
|
@@ -5,6 +6,10 @@ import type { MessagePart } from '../types.js';
|
|
|
5
6
|
* and the provider's local accumulation. Unknown target ids are dropped
|
|
6
7
|
* rather than invented — an end without a start has nothing to render. */
|
|
7
8
|
export declare function applyPartDelta(parts: MessagePart[], delta: PartDelta): MessagePart[];
|
|
9
|
+
/** The wire history for an assistant turn whose tool calls the gateway ran:
|
|
10
|
+
* the calls, each result, then the answer. A turn with no finished tool
|
|
11
|
+
* calls comes back as the answer alone. */
|
|
12
|
+
export declare function toolTurnMessages(parts: MessagePart[] | null | undefined, answer: CompletionMessage): CompletionMessage[];
|
|
8
13
|
/** A turn can end (completed or stopped) with tool calls still marked
|
|
9
14
|
* running — the transport died before their tool_end arrived. A stored
|
|
10
15
|
* message must not spin forever, so close them out as interrupted. */
|
package/dist/ai/core/parts.js
CHANGED
|
@@ -90,6 +90,33 @@ function applyPartDelta(parts, delta) {
|
|
|
90
90
|
function isStreamedText(part) {
|
|
91
91
|
return part?.kind === "text" || part?.kind === "reasoning";
|
|
92
92
|
}
|
|
93
|
+
/** The wire history for an assistant turn whose tool calls the gateway ran:
|
|
94
|
+
* the calls, each result, then the answer. A turn with no finished tool
|
|
95
|
+
* calls comes back as the answer alone. */
|
|
96
|
+
function toolTurnMessages(parts, answer) {
|
|
97
|
+
const calls = (parts ?? []).filter((p) => p.kind === "tool_call" && p.status !== "running" && p.result !== void 0);
|
|
98
|
+
if (calls.length === 0) return [answer];
|
|
99
|
+
return [
|
|
100
|
+
{
|
|
101
|
+
role: "assistant",
|
|
102
|
+
content: null,
|
|
103
|
+
tool_calls: calls.map((c) => ({
|
|
104
|
+
id: c.id,
|
|
105
|
+
type: "function",
|
|
106
|
+
function: {
|
|
107
|
+
name: c.name,
|
|
108
|
+
arguments: c.args
|
|
109
|
+
}
|
|
110
|
+
}))
|
|
111
|
+
},
|
|
112
|
+
...calls.map((c) => ({
|
|
113
|
+
role: "tool",
|
|
114
|
+
tool_call_id: c.id,
|
|
115
|
+
content: c.result
|
|
116
|
+
})),
|
|
117
|
+
...answer.content ? [answer] : []
|
|
118
|
+
];
|
|
119
|
+
}
|
|
93
120
|
/** A turn can end (completed or stopped) with tool calls still marked
|
|
94
121
|
* running — the transport died before their tool_end arrived. A stored
|
|
95
122
|
* message must not spin forever, so close them out as interrupted. */
|
|
@@ -101,4 +128,4 @@ function finalizeParts(parts) {
|
|
|
101
128
|
} : p);
|
|
102
129
|
}
|
|
103
130
|
//#endregion
|
|
104
|
-
export { applyPartDelta, finalizeParts };
|
|
131
|
+
export { applyPartDelta, finalizeParts, toolTurnMessages };
|
package/dist/ai/ui/Markdown.js
CHANGED
|
@@ -57,7 +57,10 @@ function Markdown({ children, isStreaming }) {
|
|
|
57
57
|
children: /* @__PURE__ */ jsx(Streamdown, {
|
|
58
58
|
mode: isStreaming ? "streaming" : "static",
|
|
59
59
|
...isStreaming === void 0 ? {} : { isAnimating: isStreaming },
|
|
60
|
-
...isStreaming ? {
|
|
60
|
+
...isStreaming ? {
|
|
61
|
+
caret: "block",
|
|
62
|
+
className: "markdown-streaming"
|
|
63
|
+
} : {},
|
|
61
64
|
plugins,
|
|
62
65
|
rehypePlugins,
|
|
63
66
|
shikiTheme,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DrawnEdge } from './connectorGeometry.js';
|
|
2
|
+
/**
|
|
3
|
+
* A graph level's connectors in three layers: lines behind the nodes, the lineage shown above the
|
|
4
|
+
* nodes outside it, and the dots above all. However many connectors there are, the lines are one
|
|
5
|
+
* path and the dots two, each dimming as one; only the lineage shown draws connectors of its own,
|
|
6
|
+
* and a hover re-renders only those.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ConnectorLayers: import("react").MemoExoticComponent<({ edges, zBase, }: {
|
|
9
|
+
edges: DrawnEdge[];
|
|
10
|
+
zBase: number;
|
|
11
|
+
}) => import("react").JSX.Element>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { highlightOf, useLineage, useLineageStore } from "./lineage.js";
|
|
2
|
+
import { dimClasses } from "./GraphNode.js";
|
|
3
|
+
import { memo, useMemo } from "react";
|
|
4
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/graph/Connectors.tsx
|
|
6
|
+
var CONNECTOR_COLOR = "var(--theme-border)";
|
|
7
|
+
var HIGHLIGHT_COLOR = "var(--theme-element)";
|
|
8
|
+
var PANEL_COLOR = "var(--theme-panel-bg)";
|
|
9
|
+
var STROKE = 6;
|
|
10
|
+
var KEYFRAMES = [
|
|
11
|
+
"@keyframes dag-draw-in{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}",
|
|
12
|
+
"@keyframes dag-draw-out{from{stroke-dashoffset:0}to{stroke-dashoffset:1}}",
|
|
13
|
+
`@keyframes dag-light{from{fill:${CONNECTOR_COLOR}}to{fill:${HIGHLIGHT_COLOR}}}`,
|
|
14
|
+
`@keyframes dag-unlight{from{fill:${HIGHLIGHT_COLOR}}to{fill:${CONNECTOR_COLOR}}}`
|
|
15
|
+
].join("");
|
|
16
|
+
/** Plays the named keyframes once `delay` seconds pass, holding the first until then and the last after. */
|
|
17
|
+
var play = (name, seconds, delay) => ({ animation: `${name} ${seconds}s ease ${delay}s both` });
|
|
18
|
+
var LitEdge = memo(function LitEdge({ d, reversed, highlight, animT }) {
|
|
19
|
+
const { lit, delay, back } = highlightOf(highlight);
|
|
20
|
+
return /* @__PURE__ */ jsx("path", {
|
|
21
|
+
d: back ? reversed : d,
|
|
22
|
+
pathLength: 1,
|
|
23
|
+
fill: "none",
|
|
24
|
+
stroke: HIGHLIGHT_COLOR,
|
|
25
|
+
strokeWidth: STROKE,
|
|
26
|
+
strokeDasharray: "1 1",
|
|
27
|
+
strokeDashoffset: lit ? 0 : 1,
|
|
28
|
+
style: play(lit ? "dag-draw-in" : "dag-draw-out", animT, delay)
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
var LitDot = memo(function LitDot({ x, y, highlight, animT }) {
|
|
32
|
+
const { lit, delay } = highlightOf(highlight);
|
|
33
|
+
return /* @__PURE__ */ jsx("circle", {
|
|
34
|
+
cx: x,
|
|
35
|
+
cy: y,
|
|
36
|
+
r: 8,
|
|
37
|
+
fill: lit ? HIGHLIGHT_COLOR : CONNECTOR_COLOR,
|
|
38
|
+
style: play(lit ? "dag-light" : "dag-unlight", animT, delay)
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
function LitEdges({ edges }) {
|
|
42
|
+
const lineage = useLineageStore();
|
|
43
|
+
useLineage((l) => l.version, 0);
|
|
44
|
+
if (!lineage?.showing) return null;
|
|
45
|
+
const { animT } = lineage;
|
|
46
|
+
const lines = [];
|
|
47
|
+
const lit = [];
|
|
48
|
+
for (const { from, to, path } of edges) {
|
|
49
|
+
const highlight = lineage.between(from, to);
|
|
50
|
+
if (highlight) {
|
|
51
|
+
if (lineage.hovering) lines.push(path.d);
|
|
52
|
+
lit.push(/* @__PURE__ */ jsx(LitEdge, {
|
|
53
|
+
d: path.d,
|
|
54
|
+
reversed: path.reversed,
|
|
55
|
+
highlight,
|
|
56
|
+
animT
|
|
57
|
+
}, `${from}→${to}`));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [lines.length > 0 && /* @__PURE__ */ jsx("path", {
|
|
61
|
+
d: lines.join(" "),
|
|
62
|
+
stroke: CONNECTOR_COLOR,
|
|
63
|
+
strokeWidth: STROKE,
|
|
64
|
+
fill: "none"
|
|
65
|
+
}), lit] });
|
|
66
|
+
}
|
|
67
|
+
function LitDots({ ports }) {
|
|
68
|
+
const lineage = useLineageStore();
|
|
69
|
+
useLineage((l) => l.version, 0);
|
|
70
|
+
if (!lineage?.showing) return null;
|
|
71
|
+
const at = [];
|
|
72
|
+
const lit = [];
|
|
73
|
+
for (const [key, port] of ports) {
|
|
74
|
+
const highlight = lineage.between(port.job, port.job);
|
|
75
|
+
if (highlight) {
|
|
76
|
+
at.push(port);
|
|
77
|
+
lit.push(/* @__PURE__ */ jsx(LitDot, {
|
|
78
|
+
x: port.x,
|
|
79
|
+
y: port.y,
|
|
80
|
+
highlight,
|
|
81
|
+
animT: lineage.animT
|
|
82
|
+
}, key));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [at.length > 0 && /* @__PURE__ */ jsx("path", {
|
|
86
|
+
d: squaresAt(at),
|
|
87
|
+
fill: PANEL_COLOR
|
|
88
|
+
}), lit] });
|
|
89
|
+
}
|
|
90
|
+
/** A square of panel color around each dot, which leaves a gap at the node's edge. */
|
|
91
|
+
var squaresAt = (at) => at.map(({ x, y }) => `M ${x - 16} ${y - 16} h 32 v 32 h -32 Z`).join(" ");
|
|
92
|
+
/**
|
|
93
|
+
* A graph level's connectors in three layers: lines behind the nodes, the lineage shown above the
|
|
94
|
+
* nodes outside it, and the dots above all. However many connectors there are, the lines are one
|
|
95
|
+
* path and the dots two, each dimming as one; only the lineage shown draws connectors of its own,
|
|
96
|
+
* and a hover re-renders only those.
|
|
97
|
+
*/
|
|
98
|
+
var ConnectorLayers = memo(function ConnectorLayers({ edges, zBase }) {
|
|
99
|
+
const fade = dimClasses(useLineageStore()?.depth ?? 0).edge;
|
|
100
|
+
const { lines, squares, dots, ports } = useMemo(() => {
|
|
101
|
+
const ports = /* @__PURE__ */ new Map();
|
|
102
|
+
for (const { from, to, start, end } of edges) {
|
|
103
|
+
ports.set(`${from}\0out`, {
|
|
104
|
+
job: from,
|
|
105
|
+
...start
|
|
106
|
+
});
|
|
107
|
+
ports.set(`${to}\0in`, {
|
|
108
|
+
job: to,
|
|
109
|
+
...end
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const at = [...ports.values()];
|
|
113
|
+
return {
|
|
114
|
+
lines: edges.map(({ path }) => path.d).join(" "),
|
|
115
|
+
squares: squaresAt(at),
|
|
116
|
+
dots: at.map(({ x, y }) => `M ${x - 8} ${y} a 8 8 0 1 0 16 0 a 8 8 0 1 0 -16 0`).join(" "),
|
|
117
|
+
ports
|
|
118
|
+
};
|
|
119
|
+
}, [edges]);
|
|
120
|
+
const keyframes = /* @__PURE__ */ jsx("style", {
|
|
121
|
+
href: "dependency-graph-highlight",
|
|
122
|
+
precedence: "default",
|
|
123
|
+
children: KEYFRAMES
|
|
124
|
+
});
|
|
125
|
+
if (edges.length === 0) return keyframes;
|
|
126
|
+
const layer = (z, children) => /* @__PURE__ */ jsx("svg", {
|
|
127
|
+
"aria-hidden": "true",
|
|
128
|
+
overflow: "visible",
|
|
129
|
+
className: "absolute pointer-events-none",
|
|
130
|
+
style: { zIndex: z + zBase },
|
|
131
|
+
children
|
|
132
|
+
});
|
|
133
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
134
|
+
keyframes,
|
|
135
|
+
layer(-1, /* @__PURE__ */ jsx("path", {
|
|
136
|
+
d: lines,
|
|
137
|
+
stroke: CONNECTOR_COLOR,
|
|
138
|
+
strokeWidth: STROKE,
|
|
139
|
+
fill: "none",
|
|
140
|
+
className: fade
|
|
141
|
+
})),
|
|
142
|
+
layer(20, /* @__PURE__ */ jsx(LitEdges, { edges })),
|
|
143
|
+
layer(30, /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
144
|
+
/* @__PURE__ */ jsx("path", {
|
|
145
|
+
d: squares,
|
|
146
|
+
fill: PANEL_COLOR
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ jsx("path", {
|
|
149
|
+
d: dots,
|
|
150
|
+
fill: CONNECTOR_COLOR,
|
|
151
|
+
className: fade
|
|
152
|
+
}),
|
|
153
|
+
/* @__PURE__ */ jsx(LitDots, { ports })
|
|
154
|
+
] }))
|
|
155
|
+
] });
|
|
156
|
+
});
|
|
157
|
+
//#endregion
|
|
158
|
+
export { ConnectorLayers };
|
|
@@ -1,24 +1,10 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import type { GraphLayout } from '../editing.js';
|
|
3
|
-
import type { MatrixGroup, WorkflowEngine } from '../engine.js';
|
|
4
2
|
import type { DependencyGraphEditor } from './editorApi.js';
|
|
5
3
|
import { type RunLink, type WorkflowJob } from './types.js';
|
|
6
4
|
export type ViewMode = 'dag' | 'tree';
|
|
7
5
|
export declare const addCleanupSteps: ({ jobs }: {
|
|
8
6
|
jobs: Record<string, WorkflowJob>;
|
|
9
7
|
}) => Record<string, WorkflowJob>;
|
|
10
|
-
export declare function computeGraphLayout(engine: WorkflowEngine, jobs: Record<string, WorkflowJob>, matrixGroups: Record<string, MatrixGroup>, layout?: GraphLayout): {
|
|
11
|
-
dependencyCols: string[][][];
|
|
12
|
-
/** Each box's row; above a box, rows the layout leaves empty stay as space. */
|
|
13
|
-
rowSlots: number[][];
|
|
14
|
-
/** Each column's number; between two drawn columns, numbers skipped stay as space. */
|
|
15
|
-
colSlots: number[];
|
|
16
|
-
directDeps: Record<string, string[]>;
|
|
17
|
-
displayJobs: Record<string, WorkflowJob>;
|
|
18
|
-
ancestorDists: Record<string, Map<string, number>>;
|
|
19
|
-
descendantDists: Record<string, Map<string, number>>;
|
|
20
|
-
};
|
|
21
|
-
export declare function intoView(start: number, end: number, low: number, high: number): number;
|
|
22
8
|
interface GraphRun {
|
|
23
9
|
id?: string | undefined;
|
|
24
10
|
number?: number | undefined;
|
|
@@ -44,7 +30,7 @@ export default function DependencyGraph({ run, preview, removeBorder, viewMode,
|
|
|
44
30
|
/** The workflow as written, for what the editor's expressions can read. */
|
|
45
31
|
workflow?: Record<string, unknown> | undefined;
|
|
46
32
|
fixedHeight?: string | undefined;
|
|
47
|
-
}):
|
|
33
|
+
}): import("react").JSX.Element | undefined;
|
|
48
34
|
export declare function DependencyGraphPreview(inputs: {
|
|
49
35
|
yml?: Record<string, unknown>;
|
|
50
36
|
removeBorder?: boolean;
|
|
@@ -52,5 +38,5 @@ export declare function DependencyGraphPreview(inputs: {
|
|
|
52
38
|
editor?: DependencyGraphEditor | undefined;
|
|
53
39
|
/** A fixed panel height; without one the panel is only as tall as the graph. */
|
|
54
40
|
height?: string | undefined;
|
|
55
|
-
}):
|
|
41
|
+
}): import("react").JSX.Element;
|
|
56
42
|
export {};
|