@blokkli/editor 2.0.0-alpha.55 → 2.0.0-alpha.57
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/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/modules/agent/runtime/app/composables/index.d.ts +3 -1
- package/dist/modules/agent/runtime/app/composables/index.js +1 -0
- package/dist/modules/agent/runtime/app/composables/useAgent.d.ts +6 -0
- package/dist/modules/agent/runtime/app/composables/useAgent.js +11 -0
- package/dist/modules/agent/runtime/app/features/agent/Container.vue +83 -88
- package/dist/modules/agent/runtime/app/helpers/buildPageContext.d.ts +7 -0
- package/dist/modules/agent/runtime/app/helpers/buildPageContext.js +81 -0
- package/dist/modules/agent/runtime/app/helpers/index.d.ts +15 -1
- package/dist/modules/agent/runtime/app/helpers/index.js +16 -0
- package/dist/modules/agent/runtime/app/helpers/injections.d.ts +3 -0
- package/dist/modules/agent/runtime/app/helpers/injections.js +3 -0
- package/dist/modules/agent/runtime/app/providers/agentProvider.d.ts +33 -0
- package/dist/modules/agent/runtime/app/providers/agentProvider.js +315 -0
- package/dist/modules/agent/runtime/app/providers/conversationProvider.d.ts +50 -0
- package/dist/modules/agent/runtime/app/providers/conversationProvider.js +263 -0
- package/dist/modules/agent/runtime/app/providers/planProvider.d.ts +15 -0
- package/dist/modules/agent/runtime/app/providers/planProvider.js +34 -0
- package/dist/modules/agent/runtime/app/providers/socketProvider.d.ts +17 -0
- package/dist/modules/agent/runtime/app/providers/socketProvider.js +110 -0
- package/dist/modules/agent/runtime/app/providers/toolsProvider.d.ts +44 -0
- package/dist/modules/agent/runtime/app/providers/toolsProvider.js +298 -0
- package/dist/modules/agent/runtime/app/types/index.d.ts +47 -0
- package/dist/modules/agent/runtime/app/types/index.js +3 -1
- package/dist/modules/agent/runtime/server/helpers.js +2 -1
- package/dist/modules/readability/index.mjs +1 -1
- package/dist/runtime/editor/components/DiffViewer/State.vue +51 -67
- package/dist/runtime/editor/components/EditProvider.vue +30 -10
- package/dist/runtime/editor/components/PreviewProvider.vue +14 -8
- package/dist/runtime/editor/css/output.css +1 -1
- package/dist/runtime/editor/events/index.d.ts +4 -3
- package/dist/runtime/editor/features/add-list/index.vue +2 -1
- package/dist/runtime/editor/features/changelog/changelog.json +8 -0
- package/dist/runtime/editor/features/dev-mode/index.vue +1 -10
- package/dist/runtime/editor/features/responsive-preview/Frame/index.vue +2 -2
- package/dist/runtime/editor/features/translations/index.vue +1 -0
- package/dist/runtime/editor/plugins/Sidebar/index.vue +9 -2
- package/dist/runtime/editor/providers/state.js +4 -1
- package/package.json +1 -1
- package/dist/modules/agent/runtime/app/composables/agentProvider.d.ts +0 -62
- package/dist/modules/agent/runtime/app/composables/agentProvider.js +0 -1048
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { ref, watch } from "#imports";
|
|
2
|
+
import { routeRoute } from "#blokkli-build/agent-client";
|
|
3
|
+
import { buildPageContext } from "#blokkli/agent/app/helpers/buildPageContext";
|
|
4
|
+
export default function agentProvider({
|
|
5
|
+
app,
|
|
6
|
+
adapter,
|
|
7
|
+
agentName,
|
|
8
|
+
socket,
|
|
9
|
+
conversation,
|
|
10
|
+
plan,
|
|
11
|
+
tools
|
|
12
|
+
}) {
|
|
13
|
+
const { ui } = app;
|
|
14
|
+
const isReady = ref(false);
|
|
15
|
+
const hasBeenReady = ref(false);
|
|
16
|
+
const isProcessing = ref(false);
|
|
17
|
+
const isThinking = ref(false);
|
|
18
|
+
let pendingPrompt = null;
|
|
19
|
+
let pendingInit = null;
|
|
20
|
+
let sentToolNames = [];
|
|
21
|
+
watch(isProcessing, (processing) => {
|
|
22
|
+
ui.setTransform(processing ? agentName : null);
|
|
23
|
+
});
|
|
24
|
+
function resetActivityFlags() {
|
|
25
|
+
isProcessing.value = false;
|
|
26
|
+
isThinking.value = false;
|
|
27
|
+
}
|
|
28
|
+
function teardown() {
|
|
29
|
+
isReady.value = false;
|
|
30
|
+
resetActivityFlags();
|
|
31
|
+
ui.setTransform(null);
|
|
32
|
+
tools.cancelPending();
|
|
33
|
+
}
|
|
34
|
+
function connect() {
|
|
35
|
+
socket.connect();
|
|
36
|
+
}
|
|
37
|
+
function disconnect() {
|
|
38
|
+
socket.disconnect();
|
|
39
|
+
teardown();
|
|
40
|
+
}
|
|
41
|
+
async function onSocketOpen() {
|
|
42
|
+
const { toolNames } = await tools.init();
|
|
43
|
+
sentToolNames = toolNames;
|
|
44
|
+
let contentSearchTabs;
|
|
45
|
+
if (adapter.getContentSearchTabs) {
|
|
46
|
+
try {
|
|
47
|
+
contentSearchTabs = await adapter.getContentSearchTabs();
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.warn("[blokkli agent] Failed to fetch content search tabs:", e);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (adapter.getAgentAuthToken) {
|
|
53
|
+
try {
|
|
54
|
+
const authToken = await adapter.getAgentAuthToken();
|
|
55
|
+
if (!authToken) {
|
|
56
|
+
conversation.pushError({ errorType: "unauthorized" });
|
|
57
|
+
disconnect();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const pageContext2 = await buildPageContext(app, contentSearchTabs);
|
|
61
|
+
tools.setPageContext(pageContext2);
|
|
62
|
+
pendingInit = { toolNames, pageContext: pageContext2 };
|
|
63
|
+
socket.send({ type: "authenticate", authToken });
|
|
64
|
+
return;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.error("Failed to obtain agent auth token:", e);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const pageContext = await buildPageContext(app, contentSearchTabs);
|
|
70
|
+
tools.setPageContext(pageContext);
|
|
71
|
+
sendInit(toolNames, pageContext);
|
|
72
|
+
}
|
|
73
|
+
async function sendInit(toolNames, pageContext) {
|
|
74
|
+
socket.send({ type: "init", toolNames, pageContext });
|
|
75
|
+
isReady.value = true;
|
|
76
|
+
hasBeenReady.value = true;
|
|
77
|
+
if (adapter.agentConversations && !pendingPrompt) {
|
|
78
|
+
const latest = await conversation.loadLatestFromAdapter();
|
|
79
|
+
if (latest) {
|
|
80
|
+
conversation.activeConversationId.value = latest.uuid;
|
|
81
|
+
conversation.applyRestoredData(latest.parsed);
|
|
82
|
+
socket.send({
|
|
83
|
+
type: "restore_conversation",
|
|
84
|
+
state: latest.parsed.serverState
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (pendingPrompt) {
|
|
89
|
+
const queued = pendingPrompt;
|
|
90
|
+
pendingPrompt = null;
|
|
91
|
+
sendPrompt(
|
|
92
|
+
queued.prompt,
|
|
93
|
+
queued.displayPrompt,
|
|
94
|
+
queued.selectedUuids,
|
|
95
|
+
queued.attachments,
|
|
96
|
+
queued.autoLoadTools,
|
|
97
|
+
queued.autoLoadSkills,
|
|
98
|
+
queued.preSeededResults,
|
|
99
|
+
queued.autoExecuteTools
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function handleServerMessage(data) {
|
|
104
|
+
switch (data.type) {
|
|
105
|
+
case "authenticated":
|
|
106
|
+
if (pendingInit) {
|
|
107
|
+
sendInit(pendingInit.toolNames, pendingInit.pageContext);
|
|
108
|
+
pendingInit = null;
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case "thinking":
|
|
112
|
+
conversation.setActive(null);
|
|
113
|
+
isThinking.value = true;
|
|
114
|
+
break;
|
|
115
|
+
case "text":
|
|
116
|
+
case "text_delta":
|
|
117
|
+
isThinking.value = false;
|
|
118
|
+
conversation.appendToActive(data.content);
|
|
119
|
+
break;
|
|
120
|
+
case "tool_call":
|
|
121
|
+
conversation.finalizeActive();
|
|
122
|
+
tools.dispatch(data.callId, data.tool, data.params);
|
|
123
|
+
break;
|
|
124
|
+
case "usage":
|
|
125
|
+
conversation.pushUsage(data.usage);
|
|
126
|
+
break;
|
|
127
|
+
case "done":
|
|
128
|
+
conversation.finalizeActive();
|
|
129
|
+
resetActivityFlags();
|
|
130
|
+
if (data.message) {
|
|
131
|
+
conversation.pushAssistantUnlessDuplicate(data.message);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
case "error":
|
|
135
|
+
conversation.finalizeActive();
|
|
136
|
+
resetActivityFlags();
|
|
137
|
+
if (data.detail) {
|
|
138
|
+
console.warn(`[blokkli agent] ${data.errorType} error:`, data.detail);
|
|
139
|
+
}
|
|
140
|
+
conversation.pushError({
|
|
141
|
+
errorType: data.errorType,
|
|
142
|
+
retryable: data.retryable
|
|
143
|
+
});
|
|
144
|
+
break;
|
|
145
|
+
case "server_tool_result":
|
|
146
|
+
conversation.finalizeActive();
|
|
147
|
+
conversation.pushServerTool({ tool: data.tool, label: data.label });
|
|
148
|
+
break;
|
|
149
|
+
case "plan_update":
|
|
150
|
+
plan.applyUpdate(data.plan);
|
|
151
|
+
break;
|
|
152
|
+
case "transcript":
|
|
153
|
+
conversation.setTranscript(data.transcript);
|
|
154
|
+
break;
|
|
155
|
+
case "conversation_state":
|
|
156
|
+
conversation.saveToAdapter(data.state);
|
|
157
|
+
break;
|
|
158
|
+
case "conversation_restored":
|
|
159
|
+
break;
|
|
160
|
+
case "conversation_restore_failed": {
|
|
161
|
+
console.warn(
|
|
162
|
+
"[blokkli agent] Conversation restore failed:",
|
|
163
|
+
data.reason
|
|
164
|
+
);
|
|
165
|
+
const failedId = conversation.activeConversationId.value;
|
|
166
|
+
conversation.clearAll();
|
|
167
|
+
if (failedId) {
|
|
168
|
+
conversation.deleteFromAdapter(failedId).catch(() => {
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
socket.setMessageHandler(handleServerMessage);
|
|
176
|
+
socket.setLifecycleHandlers({
|
|
177
|
+
onOpen: () => {
|
|
178
|
+
onSocketOpen();
|
|
179
|
+
},
|
|
180
|
+
onClose: teardown,
|
|
181
|
+
onMaxReconnects: () => {
|
|
182
|
+
conversation.pushError({ errorType: "connection" });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
async function sendPrompt(prompt, displayPrompt, selectedUuids, attachments, autoLoadTools, autoLoadSkills, preSeededResults, autoExecuteTools) {
|
|
186
|
+
if (!prompt.trim() || isProcessing.value) return;
|
|
187
|
+
if (!isReady.value) {
|
|
188
|
+
pendingPrompt = {
|
|
189
|
+
prompt,
|
|
190
|
+
displayPrompt,
|
|
191
|
+
selectedUuids,
|
|
192
|
+
attachments,
|
|
193
|
+
autoLoadTools,
|
|
194
|
+
autoLoadSkills,
|
|
195
|
+
preSeededResults,
|
|
196
|
+
autoExecuteTools
|
|
197
|
+
};
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
isProcessing.value = true;
|
|
201
|
+
conversation.ensureConversationId();
|
|
202
|
+
conversation.pushUser(displayPrompt ?? prompt, attachments);
|
|
203
|
+
isThinking.value = true;
|
|
204
|
+
const isFirstMessage = conversation.items.value.filter((v) => v.type === "user").length === 1;
|
|
205
|
+
const hasClientDirectives = !!(autoLoadTools?.length || autoLoadSkills?.length);
|
|
206
|
+
let resolvedAutoLoadTools = autoLoadTools;
|
|
207
|
+
let resolvedAutoLoadSkills = autoLoadSkills;
|
|
208
|
+
if (isFirstMessage && !hasClientDirectives && tools.pageContext.value) {
|
|
209
|
+
try {
|
|
210
|
+
const routingResult = await fetch(routeRoute, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: { "Content-Type": "application/json" },
|
|
213
|
+
body: JSON.stringify({
|
|
214
|
+
prompt,
|
|
215
|
+
toolNames: sentToolNames,
|
|
216
|
+
pageContext: tools.pageContext.value
|
|
217
|
+
})
|
|
218
|
+
}).then(
|
|
219
|
+
(r) => r.json()
|
|
220
|
+
);
|
|
221
|
+
if (routingResult.usage) {
|
|
222
|
+
conversation.pushUsage(routingResult.usage);
|
|
223
|
+
}
|
|
224
|
+
if (routingResult.tools?.length) {
|
|
225
|
+
resolvedAutoLoadTools = [
|
|
226
|
+
...resolvedAutoLoadTools || [],
|
|
227
|
+
...routingResult.tools
|
|
228
|
+
];
|
|
229
|
+
}
|
|
230
|
+
if (routingResult.skills?.length) {
|
|
231
|
+
resolvedAutoLoadSkills = [
|
|
232
|
+
...resolvedAutoLoadSkills || [],
|
|
233
|
+
...routingResult.skills
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const serverPreSeeded = preSeededResults?.length ? preSeededResults.map(({ toolName, params, result }) => ({
|
|
240
|
+
toolName,
|
|
241
|
+
params,
|
|
242
|
+
result
|
|
243
|
+
})) : void 0;
|
|
244
|
+
socket.send({
|
|
245
|
+
type: "start",
|
|
246
|
+
prompt,
|
|
247
|
+
selectedUuids: selectedUuids?.length ? selectedUuids : void 0,
|
|
248
|
+
autoLoadTools: resolvedAutoLoadTools?.length ? resolvedAutoLoadTools : void 0,
|
|
249
|
+
autoLoadSkills: resolvedAutoLoadSkills?.length ? resolvedAutoLoadSkills : void 0,
|
|
250
|
+
preSeededResults: serverPreSeeded,
|
|
251
|
+
autoExecuteTools: autoExecuteTools?.length ? autoExecuteTools : void 0
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function retry() {
|
|
255
|
+
if (isProcessing.value || !isReady.value) return;
|
|
256
|
+
const lastUserItem = [...conversation.items.value].reverse().find((item) => item.type === "user");
|
|
257
|
+
if (!lastUserItem || lastUserItem.type !== "user") return;
|
|
258
|
+
conversation.items.value = conversation.items.value.filter(
|
|
259
|
+
(item) => !(item.type === "error" && "retryable" in item && item.retryable)
|
|
260
|
+
);
|
|
261
|
+
isProcessing.value = true;
|
|
262
|
+
socket.send({ type: "start", prompt: lastUserItem.content });
|
|
263
|
+
}
|
|
264
|
+
function cancel() {
|
|
265
|
+
tools.cancelPending();
|
|
266
|
+
conversation.setActive(null);
|
|
267
|
+
socket.send({ type: "cancel" });
|
|
268
|
+
resetActivityFlags();
|
|
269
|
+
conversation.pushAssistant(app.$t("aiAgentCancelled", "Cancelled"));
|
|
270
|
+
}
|
|
271
|
+
function newConversation() {
|
|
272
|
+
tools.cancelPending();
|
|
273
|
+
conversation.clearAll();
|
|
274
|
+
plan.clear();
|
|
275
|
+
resetActivityFlags();
|
|
276
|
+
socket.send({ type: "new_conversation" });
|
|
277
|
+
}
|
|
278
|
+
function getTranscript() {
|
|
279
|
+
socket.send({ type: "get_transcript" });
|
|
280
|
+
}
|
|
281
|
+
async function switchConversation(id) {
|
|
282
|
+
if (isProcessing.value) return;
|
|
283
|
+
const parsed = await conversation.loadFromAdapter(id);
|
|
284
|
+
if (!parsed) return;
|
|
285
|
+
conversation.applyRestoredData(parsed);
|
|
286
|
+
conversation.activeConversationId.value = id;
|
|
287
|
+
socket.send({ type: "restore_conversation", state: parsed.serverState });
|
|
288
|
+
conversation.showConversationList.value = false;
|
|
289
|
+
}
|
|
290
|
+
async function deleteConversation(id) {
|
|
291
|
+
await conversation.deleteFromAdapter(id);
|
|
292
|
+
if (conversation.activeConversationId.value === id) {
|
|
293
|
+
conversation.clearAll();
|
|
294
|
+
resetActivityFlags();
|
|
295
|
+
socket.send({ type: "new_conversation" });
|
|
296
|
+
}
|
|
297
|
+
await conversation.refreshList();
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
isReady,
|
|
301
|
+
hasBeenReady,
|
|
302
|
+
isProcessing,
|
|
303
|
+
isThinking,
|
|
304
|
+
connect,
|
|
305
|
+
disconnect,
|
|
306
|
+
sendPrompt,
|
|
307
|
+
retry,
|
|
308
|
+
cancel,
|
|
309
|
+
newConversation,
|
|
310
|
+
getTranscript,
|
|
311
|
+
switchConversation,
|
|
312
|
+
deleteConversation,
|
|
313
|
+
refreshConversationList: conversation.refreshList
|
|
314
|
+
};
|
|
315
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type Ref } from '#imports';
|
|
2
|
+
import { type ConversationItem, type ActiveItem, type ToolConversationItem, type ServerToolConversationItem, type ErrorConversationItem, type Attachment } from '#blokkli/agent/app/types';
|
|
3
|
+
import type { ConversationStateSnapshot, UsageTurn, Transcript } from '#blokkli/agent/shared/types';
|
|
4
|
+
import type { AgentConversationData, AgentConversationSummary } from '#blokkli/agent/app/features/agent/types';
|
|
5
|
+
import type { FullBlokkliAdapter } from '#blokkli/editor/adapter';
|
|
6
|
+
export type ParsedConversation = {
|
|
7
|
+
conversation: ConversationItem[];
|
|
8
|
+
usageTurns: UsageTurn[];
|
|
9
|
+
serverState: ConversationStateSnapshot;
|
|
10
|
+
feedbackItemIds: string[];
|
|
11
|
+
};
|
|
12
|
+
export type ConversationProvider = {
|
|
13
|
+
items: Ref<ConversationItem[]>;
|
|
14
|
+
activeItem: Ref<ActiveItem | null>;
|
|
15
|
+
usageTurns: Ref<UsageTurn[]>;
|
|
16
|
+
feedbackItemIds: Ref<Set<string>>;
|
|
17
|
+
toolDetails: Map<string, unknown>;
|
|
18
|
+
activeConversationId: Ref<string | null>;
|
|
19
|
+
conversationList: Ref<AgentConversationSummary[]>;
|
|
20
|
+
showConversationList: Ref<boolean>;
|
|
21
|
+
transcriptContent: Ref<Transcript | null>;
|
|
22
|
+
showTranscript: Ref<boolean>;
|
|
23
|
+
pushUser: (content: string, attachments?: Attachment[]) => void;
|
|
24
|
+
appendToActive: (text: string) => void;
|
|
25
|
+
setActive: (item: ActiveItem | null) => void;
|
|
26
|
+
finalizeActive: () => void;
|
|
27
|
+
pushAssistant: (content: string) => void;
|
|
28
|
+
pushAssistantUnlessDuplicate: (content: string) => void;
|
|
29
|
+
pushTool: (item: ToolConversationItem) => void;
|
|
30
|
+
pushServerTool: (item: Pick<ServerToolConversationItem, 'tool' | 'label'>) => void;
|
|
31
|
+
pushError: (item: Pick<ErrorConversationItem, 'errorType' | 'retryable'>) => void;
|
|
32
|
+
pushUsage: (turn: UsageTurn) => void;
|
|
33
|
+
setToolDetail: (callId: string, details: unknown) => void;
|
|
34
|
+
clearAll: () => void;
|
|
35
|
+
ensureConversationId: () => string;
|
|
36
|
+
setTranscript: (transcript: Transcript) => void;
|
|
37
|
+
applyRestoredData: (parsed: ParsedConversation) => void;
|
|
38
|
+
parseFromAdapter: (data: AgentConversationData) => ParsedConversation | null;
|
|
39
|
+
saveToAdapter: (serverState: ConversationStateSnapshot) => Promise<void>;
|
|
40
|
+
loadFromAdapter: (uuid: string) => Promise<ParsedConversation | null>;
|
|
41
|
+
loadLatestFromAdapter: () => Promise<{
|
|
42
|
+
parsed: ParsedConversation;
|
|
43
|
+
uuid: string;
|
|
44
|
+
} | null>;
|
|
45
|
+
deleteFromAdapter: (uuid: string) => Promise<void>;
|
|
46
|
+
refreshList: () => Promise<void>;
|
|
47
|
+
};
|
|
48
|
+
export default function conversationProvider({ adapter, }: {
|
|
49
|
+
adapter: FullBlokkliAdapter<any>;
|
|
50
|
+
}): ConversationProvider;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { ref, reactive } from "#imports";
|
|
2
|
+
import {
|
|
3
|
+
conversationItemSchema
|
|
4
|
+
} from "#blokkli/agent/app/types";
|
|
5
|
+
import { generateUUID } from "#blokkli/editor/helpers/uuid";
|
|
6
|
+
function generateId() {
|
|
7
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
8
|
+
}
|
|
9
|
+
export default function conversationProvider({
|
|
10
|
+
adapter
|
|
11
|
+
}) {
|
|
12
|
+
const items = ref([]);
|
|
13
|
+
const activeItem = ref(null);
|
|
14
|
+
const usageTurns = ref([]);
|
|
15
|
+
const feedbackItemIds = ref(/* @__PURE__ */ new Set());
|
|
16
|
+
const toolDetails = reactive(/* @__PURE__ */ new Map());
|
|
17
|
+
const activeConversationId = ref(null);
|
|
18
|
+
const conversationList = ref([]);
|
|
19
|
+
const showConversationList = ref(false);
|
|
20
|
+
const transcriptContent = ref(null);
|
|
21
|
+
const showTranscript = ref(false);
|
|
22
|
+
function pushUser(content, attachments) {
|
|
23
|
+
items.value.push({
|
|
24
|
+
type: "user",
|
|
25
|
+
id: generateId(),
|
|
26
|
+
content,
|
|
27
|
+
timestamp: Date.now(),
|
|
28
|
+
attachments: attachments?.length ? attachments : void 0
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function appendToActive(text) {
|
|
32
|
+
if (activeItem.value?.type === "assistant") {
|
|
33
|
+
activeItem.value = {
|
|
34
|
+
...activeItem.value,
|
|
35
|
+
content: activeItem.value.content + text
|
|
36
|
+
};
|
|
37
|
+
} else {
|
|
38
|
+
activeItem.value = {
|
|
39
|
+
type: "assistant",
|
|
40
|
+
id: generateId(),
|
|
41
|
+
content: text,
|
|
42
|
+
timestamp: Date.now()
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function setActive(item) {
|
|
47
|
+
activeItem.value = item;
|
|
48
|
+
}
|
|
49
|
+
function finalizeActive() {
|
|
50
|
+
const item = activeItem.value;
|
|
51
|
+
if (!item) return;
|
|
52
|
+
if (item.type === "assistant" && item.content) {
|
|
53
|
+
const finalized = {
|
|
54
|
+
type: "assistant",
|
|
55
|
+
id: item.id,
|
|
56
|
+
content: item.content,
|
|
57
|
+
timestamp: item.timestamp
|
|
58
|
+
};
|
|
59
|
+
items.value.push(finalized);
|
|
60
|
+
}
|
|
61
|
+
activeItem.value = null;
|
|
62
|
+
}
|
|
63
|
+
function pushAssistant(content) {
|
|
64
|
+
items.value.push({
|
|
65
|
+
type: "assistant",
|
|
66
|
+
id: generateId(),
|
|
67
|
+
content,
|
|
68
|
+
timestamp: Date.now()
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function pushAssistantUnlessDuplicate(content) {
|
|
72
|
+
const last = items.value[items.value.length - 1];
|
|
73
|
+
if (last?.type === "assistant" && last.content === content) return;
|
|
74
|
+
pushAssistant(content);
|
|
75
|
+
}
|
|
76
|
+
function pushTool(item) {
|
|
77
|
+
items.value.push(item);
|
|
78
|
+
}
|
|
79
|
+
function pushServerTool(item) {
|
|
80
|
+
items.value.push({
|
|
81
|
+
type: "server_tool",
|
|
82
|
+
id: generateId(),
|
|
83
|
+
timestamp: Date.now(),
|
|
84
|
+
tool: item.tool,
|
|
85
|
+
label: item.label
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function pushError(item) {
|
|
89
|
+
items.value.push({
|
|
90
|
+
type: "error",
|
|
91
|
+
id: generateId(),
|
|
92
|
+
timestamp: Date.now(),
|
|
93
|
+
errorType: item.errorType,
|
|
94
|
+
...item.retryable ? { retryable: true } : {}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function pushUsage(turn) {
|
|
98
|
+
usageTurns.value = [...usageTurns.value, turn];
|
|
99
|
+
}
|
|
100
|
+
function setToolDetail(callId, details) {
|
|
101
|
+
toolDetails.set(callId, details);
|
|
102
|
+
}
|
|
103
|
+
function clearAll() {
|
|
104
|
+
items.value = [];
|
|
105
|
+
activeItem.value = null;
|
|
106
|
+
activeConversationId.value = null;
|
|
107
|
+
usageTurns.value = [];
|
|
108
|
+
feedbackItemIds.value = /* @__PURE__ */ new Set();
|
|
109
|
+
toolDetails.clear();
|
|
110
|
+
}
|
|
111
|
+
function ensureConversationId() {
|
|
112
|
+
if (!activeConversationId.value) {
|
|
113
|
+
activeConversationId.value = generateUUID();
|
|
114
|
+
}
|
|
115
|
+
return activeConversationId.value;
|
|
116
|
+
}
|
|
117
|
+
function setTranscript(transcript) {
|
|
118
|
+
transcriptContent.value = transcript;
|
|
119
|
+
showTranscript.value = true;
|
|
120
|
+
}
|
|
121
|
+
function parseFromAdapter(data) {
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(data.clientState);
|
|
124
|
+
if (!parsed.conversation?.length) return null;
|
|
125
|
+
const clientConversation = parsed.conversation.map(
|
|
126
|
+
(item) => {
|
|
127
|
+
const result = conversationItemSchema.safeParse(item);
|
|
128
|
+
if (result.success) return result.data;
|
|
129
|
+
return {
|
|
130
|
+
type: "unknown",
|
|
131
|
+
id: generateId(),
|
|
132
|
+
timestamp: Date.now()
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
const serverParsed = JSON.parse(data.serverState);
|
|
137
|
+
if (!serverParsed?.messages?.length) return null;
|
|
138
|
+
return {
|
|
139
|
+
conversation: clientConversation,
|
|
140
|
+
usageTurns: parsed.usageTurns ?? [],
|
|
141
|
+
feedbackItemIds: data.feedbackItemIds ?? [],
|
|
142
|
+
serverState: {
|
|
143
|
+
messages: serverParsed.messages,
|
|
144
|
+
activatedLazyTools: serverParsed.activatedLazyTools,
|
|
145
|
+
hash: data.hash
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function applyRestoredData(parsed) {
|
|
153
|
+
items.value = parsed.conversation;
|
|
154
|
+
usageTurns.value = parsed.usageTurns;
|
|
155
|
+
feedbackItemIds.value = new Set(parsed.feedbackItemIds);
|
|
156
|
+
activeItem.value = null;
|
|
157
|
+
}
|
|
158
|
+
async function saveToAdapter(serverState) {
|
|
159
|
+
if (!adapter.agentConversations) return;
|
|
160
|
+
if (!items.value.length) return;
|
|
161
|
+
const id = ensureConversationId();
|
|
162
|
+
const firstUser = items.value.find((item) => item.type === "user");
|
|
163
|
+
const titleText = firstUser && "content" in firstUser ? firstUser.content : "";
|
|
164
|
+
const title = titleText.length > 80 ? titleText.slice(0, 80) + "\u2026" : titleText;
|
|
165
|
+
try {
|
|
166
|
+
await adapter.agentConversations.upsert({
|
|
167
|
+
uuid: id,
|
|
168
|
+
title,
|
|
169
|
+
clientState: JSON.stringify({
|
|
170
|
+
conversation: items.value.filter((item) => item.type !== "error"),
|
|
171
|
+
usageTurns: usageTurns.value
|
|
172
|
+
}),
|
|
173
|
+
serverState: JSON.stringify({
|
|
174
|
+
messages: serverState.messages,
|
|
175
|
+
activatedLazyTools: serverState.activatedLazyTools
|
|
176
|
+
}),
|
|
177
|
+
hash: serverState.hash
|
|
178
|
+
});
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.warn("[blokkli agent] Failed to save conversation:", e);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function loadFromAdapter(uuid) {
|
|
184
|
+
if (!adapter.agentConversations) return null;
|
|
185
|
+
try {
|
|
186
|
+
const data = await adapter.agentConversations.load(uuid);
|
|
187
|
+
if (!data) return null;
|
|
188
|
+
return parseFromAdapter(data);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
console.warn("[blokkli agent] Failed to load conversation:", e);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function loadLatestFromAdapter() {
|
|
195
|
+
if (!adapter.agentConversations) return null;
|
|
196
|
+
try {
|
|
197
|
+
const latest = await adapter.agentConversations.loadLatest();
|
|
198
|
+
if (!latest) return null;
|
|
199
|
+
const parsed = parseFromAdapter(latest);
|
|
200
|
+
if (!parsed) return null;
|
|
201
|
+
return { parsed, uuid: latest.uuid };
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.warn("[blokkli agent] Failed to load latest conversation:", e);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function deleteFromAdapter(uuid) {
|
|
208
|
+
if (!adapter.agentConversations) return;
|
|
209
|
+
try {
|
|
210
|
+
await adapter.agentConversations.delete(uuid);
|
|
211
|
+
} catch (e) {
|
|
212
|
+
console.warn("[blokkli agent] Failed to delete conversation:", e);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async function refreshList() {
|
|
216
|
+
if (!adapter.agentConversations) {
|
|
217
|
+
conversationList.value = [];
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const list = await adapter.agentConversations.list();
|
|
222
|
+
conversationList.value = list.sort(
|
|
223
|
+
(a, b) => b.updatedAt.localeCompare(a.updatedAt)
|
|
224
|
+
);
|
|
225
|
+
} catch (e) {
|
|
226
|
+
console.warn("[blokkli agent] Failed to list conversations:", e);
|
|
227
|
+
conversationList.value = [];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
items,
|
|
232
|
+
activeItem,
|
|
233
|
+
usageTurns,
|
|
234
|
+
feedbackItemIds,
|
|
235
|
+
toolDetails,
|
|
236
|
+
activeConversationId,
|
|
237
|
+
conversationList,
|
|
238
|
+
showConversationList,
|
|
239
|
+
transcriptContent,
|
|
240
|
+
showTranscript,
|
|
241
|
+
pushUser,
|
|
242
|
+
appendToActive,
|
|
243
|
+
setActive,
|
|
244
|
+
finalizeActive,
|
|
245
|
+
pushAssistant,
|
|
246
|
+
pushAssistantUnlessDuplicate,
|
|
247
|
+
pushTool,
|
|
248
|
+
pushServerTool,
|
|
249
|
+
pushError,
|
|
250
|
+
pushUsage,
|
|
251
|
+
setToolDetail,
|
|
252
|
+
clearAll,
|
|
253
|
+
ensureConversationId,
|
|
254
|
+
setTranscript,
|
|
255
|
+
applyRestoredData,
|
|
256
|
+
parseFromAdapter,
|
|
257
|
+
saveToAdapter,
|
|
258
|
+
loadFromAdapter,
|
|
259
|
+
loadLatestFromAdapter,
|
|
260
|
+
deleteFromAdapter,
|
|
261
|
+
refreshList
|
|
262
|
+
};
|
|
263
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Ref } from '#imports';
|
|
2
|
+
import type { ClientPlanState } from '#blokkli/agent/shared/types';
|
|
3
|
+
import type { SocketProvider } from './socketProvider.js';
|
|
4
|
+
import type { ConversationProvider } from './conversationProvider.js';
|
|
5
|
+
export type PlanProvider = {
|
|
6
|
+
plan: Ref<ClientPlanState | null>;
|
|
7
|
+
approve: () => void;
|
|
8
|
+
reject: () => void;
|
|
9
|
+
applyUpdate: (state: ClientPlanState | null) => void;
|
|
10
|
+
clear: () => void;
|
|
11
|
+
};
|
|
12
|
+
export default function planProvider({ socket, conversation, }: {
|
|
13
|
+
socket: SocketProvider;
|
|
14
|
+
conversation: ConversationProvider;
|
|
15
|
+
}): PlanProvider;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ref } from "#imports";
|
|
2
|
+
export default function planProvider({
|
|
3
|
+
socket,
|
|
4
|
+
conversation
|
|
5
|
+
}) {
|
|
6
|
+
const plan = ref(null);
|
|
7
|
+
function approve() {
|
|
8
|
+
socket.send({ type: "plan_approve" });
|
|
9
|
+
}
|
|
10
|
+
function reject() {
|
|
11
|
+
socket.send({ type: "plan_reject" });
|
|
12
|
+
}
|
|
13
|
+
function applyUpdate(state) {
|
|
14
|
+
if (state && state.steps.length > 0 && state.steps.every((s) => s.status === "completed")) {
|
|
15
|
+
conversation.pushServerTool({
|
|
16
|
+
tool: "plan_completed",
|
|
17
|
+
label: state.title
|
|
18
|
+
});
|
|
19
|
+
plan.value = null;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
plan.value = state;
|
|
23
|
+
}
|
|
24
|
+
function clear() {
|
|
25
|
+
plan.value = null;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
plan,
|
|
29
|
+
approve,
|
|
30
|
+
reject,
|
|
31
|
+
applyUpdate,
|
|
32
|
+
clear
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Ref } from '#imports';
|
|
2
|
+
import type { ClientMessage, ServerMessage } from '#blokkli/agent/shared/types';
|
|
3
|
+
export type SocketLifecycleHandlers = {
|
|
4
|
+
onOpen?: () => void;
|
|
5
|
+
onClose?: () => void;
|
|
6
|
+
onError?: (e: Event) => void;
|
|
7
|
+
onMaxReconnects?: () => void;
|
|
8
|
+
};
|
|
9
|
+
export type SocketProvider = {
|
|
10
|
+
isConnected: Readonly<Ref<boolean>>;
|
|
11
|
+
send: (msg: ClientMessage) => void;
|
|
12
|
+
connect: () => void;
|
|
13
|
+
disconnect: () => void;
|
|
14
|
+
setMessageHandler: (handler: (data: ServerMessage) => void) => void;
|
|
15
|
+
setLifecycleHandlers: (handlers: SocketLifecycleHandlers) => void;
|
|
16
|
+
};
|
|
17
|
+
export default function socketProvider(): SocketProvider;
|