@jskit-ai/assistant-core 0.1.171 → 0.1.172
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/examples/conversation/App.vue +62 -10
- package/examples/conversation/README.md +29 -4
- package/examples/conversation/attachments.js +45 -0
- package/examples/conversation/integrations.json +28 -0
- package/examples/conversation/package.json +6 -4
- package/examples/conversation/server.js +61 -10
- package/fixtures/responsive-assistant/ConversationFixture.vue +54 -1
- package/package.json +21 -7
- package/src/client/conversation/AssistantAttachmentPreview.vue +38 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +563 -0
- package/src/client/conversation/AssistantComposerSupport.vue +235 -0
- package/src/client/conversation/AssistantConversationElement.vue +99 -8
- package/src/client/conversation/AssistantGoalControl.vue +81 -0
- package/src/client/conversation/AssistantMessageAttachments.vue +114 -0
- package/src/client/conversation/AssistantModelControl.vue +334 -0
- package/src/client/conversation/AssistantQuestionInputs.vue +124 -0
- package/src/client/conversation/AssistantTranscript.vue +2 -1
- package/src/client/conversation/index.js +9 -0
- package/src/client/conversation/useAssistantAttachments.js +991 -0
- package/src/client/conversation/useAssistantSuggestions.js +99 -0
- package/src/server/index.js +1 -0
- package/src/server/lib/aiConnectionClient.js +90 -0
- package/src/server/lib/providers/anthropicClient.js +6 -21
- package/src/shared/assistantResource.js +7 -0
- package/src/shared/conversation/attachments.js +13 -0
- package/src/shared/conversation/index.js +1 -0
- package/test/aiConnectionClient.test.js +159 -0
- package/test/assistantScroll.browser.test.js +144 -0
- package/test/suggestions.test.js +114 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { effectScope, ref } from "vue";
|
|
4
|
+
import { useAssistantSuggestions } from "../src/client/conversation/useAssistantSuggestions.js";
|
|
5
|
+
|
|
6
|
+
const flush = () => new Promise(resolve => setImmediate(resolve));
|
|
7
|
+
const suggestion = prompt => ({ label: "Suggested next step", prompt });
|
|
8
|
+
|
|
9
|
+
test("suggestions debounce drafts and independently configure their generator", async t => {
|
|
10
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
11
|
+
const scope = effectScope();
|
|
12
|
+
t.after(() => scope.stop());
|
|
13
|
+
const draft = ref("");
|
|
14
|
+
const configuration = ref({ agent: "quick-suggestions", model: "small", instructions: "Suggest a next step" });
|
|
15
|
+
const calls = [];
|
|
16
|
+
const state = scope.run(() => useAssistantSuggestions({ draft, configuration,
|
|
17
|
+
generate: async input => { calls.push(input); return [suggestion(input.draft || input.configuration.model)]; }
|
|
18
|
+
}));
|
|
19
|
+
draft.value = "First";
|
|
20
|
+
t.mock.timers.tick(500);
|
|
21
|
+
draft.value = "Second";
|
|
22
|
+
t.mock.timers.tick(749);
|
|
23
|
+
assert.equal(calls.length, 0);
|
|
24
|
+
t.mock.timers.tick(1);
|
|
25
|
+
await flush();
|
|
26
|
+
assert.equal(calls.length, 1);
|
|
27
|
+
assert.equal(calls[0].draft, "Second");
|
|
28
|
+
assert.deepEqual(calls[0].configuration, configuration.value);
|
|
29
|
+
assert.deepEqual(state.items.value, [suggestion("Second")]);
|
|
30
|
+
configuration.value.model = "other";
|
|
31
|
+
t.mock.timers.tick(750);
|
|
32
|
+
await flush();
|
|
33
|
+
assert.equal(calls[0].configuration.model, "small", "The in-flight configuration is a snapshot");
|
|
34
|
+
assert.equal(calls[1].configuration.model, "other");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("obsolete suggestions cannot return after scope, draft or agent changes", async t => {
|
|
38
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
39
|
+
const scope = effectScope();
|
|
40
|
+
const requestKey = ref("conversation-one");
|
|
41
|
+
const draft = ref("");
|
|
42
|
+
const calls = [];
|
|
43
|
+
const generate = ref(input => new Promise(resolve => calls.push({ ...input, resolve })));
|
|
44
|
+
const state = scope.run(() => useAssistantSuggestions({ requestKey, draft, generate }));
|
|
45
|
+
t.mock.timers.tick(750);
|
|
46
|
+
requestKey.value = "conversation-two";
|
|
47
|
+
assert.equal(calls[0].signal.aborted, true);
|
|
48
|
+
t.mock.timers.tick(750);
|
|
49
|
+
draft.value = "New draft";
|
|
50
|
+
assert.equal(calls[1].signal.aborted, true);
|
|
51
|
+
t.mock.timers.tick(750);
|
|
52
|
+
generate.value = async () => [suggestion("New agent")];
|
|
53
|
+
assert.equal(calls[2].signal.aborted, true);
|
|
54
|
+
t.mock.timers.tick(750);
|
|
55
|
+
await flush();
|
|
56
|
+
for (const call of calls) call.resolve([suggestion("Obsolete")]);
|
|
57
|
+
await flush();
|
|
58
|
+
assert.deepEqual(state.items.value, [suggestion("New agent")]);
|
|
59
|
+
requestKey.value = "conversation-three";
|
|
60
|
+
scope.stop();
|
|
61
|
+
t.mock.timers.tick(750);
|
|
62
|
+
assert.equal(state.loading.value, false);
|
|
63
|
+
assert.equal(state.items.value.length, 0);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("suggestion previews preserve a draft and stale selections cannot submit", async t => {
|
|
67
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
68
|
+
const scope = effectScope();
|
|
69
|
+
t.after(() => scope.stop());
|
|
70
|
+
const draft = ref("");
|
|
71
|
+
const selected = [];
|
|
72
|
+
const state = scope.run(() => useAssistantSuggestions({ draft,
|
|
73
|
+
generate: async () => [suggestion("Next step")], onSelect: text => selected.push(text)
|
|
74
|
+
}));
|
|
75
|
+
t.mock.timers.tick(750);
|
|
76
|
+
await flush();
|
|
77
|
+
state.focus();
|
|
78
|
+
assert.equal(state.previewSuggestion(state.items.value[0]), true);
|
|
79
|
+
assert.equal(draft.value, "");
|
|
80
|
+
assert.equal(state.preview.value, "Next step");
|
|
81
|
+
assert.equal(state.select(suggestion("Next step")), true);
|
|
82
|
+
assert.deepEqual(selected, ["Next step"]);
|
|
83
|
+
state.dismiss();
|
|
84
|
+
assert.equal(state.visible.value, false);
|
|
85
|
+
assert.equal(state.select(suggestion("Next step")), false);
|
|
86
|
+
state.blur();
|
|
87
|
+
state.focus();
|
|
88
|
+
t.mock.timers.tick(750);
|
|
89
|
+
await flush();
|
|
90
|
+
assert.equal(state.visible.value, true);
|
|
91
|
+
draft.value = "Keep my text";
|
|
92
|
+
assert.equal(state.preview.value, "");
|
|
93
|
+
assert.equal(state.select(suggestion("Next step")), false);
|
|
94
|
+
assert.equal(draft.value, "Keep my text");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("a failing suggestion agent leaves the composer independent and stops retrying", async t => {
|
|
98
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
99
|
+
const scope = effectScope();
|
|
100
|
+
t.after(() => scope.stop());
|
|
101
|
+
const draft = ref("Keep this");
|
|
102
|
+
let requests = 0;
|
|
103
|
+
const state = scope.run(() => useAssistantSuggestions({ draft,
|
|
104
|
+
generate: async () => { requests++; throw new Error("Suggestion service unavailable"); }
|
|
105
|
+
}));
|
|
106
|
+
t.mock.timers.tick(750);
|
|
107
|
+
await flush();
|
|
108
|
+
assert.equal(state.error.value, "Suggestion service unavailable");
|
|
109
|
+
assert.equal(state.loading.value, false);
|
|
110
|
+
assert.equal(state.visible.value, false);
|
|
111
|
+
assert.equal(draft.value, "Keep this");
|
|
112
|
+
t.mock.timers.tick(60_000);
|
|
113
|
+
assert.equal(requests, 1);
|
|
114
|
+
});
|