@jskit-ai/assistant-core 0.2.7 → 0.2.8
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/package.json +5 -5
- package/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +13 -10
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"build": "vite build"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@jskit-ai/assistant-core": "0.2.
|
|
12
|
-
"@jskit-ai/http-runtime": "0.2.
|
|
13
|
-
"@jskit-ai/kernel": "0.2.
|
|
11
|
+
"@jskit-ai/assistant-core": "0.2.8",
|
|
12
|
+
"@jskit-ai/http-runtime": "0.2.4",
|
|
13
|
+
"@jskit-ai/kernel": "0.2.4",
|
|
14
14
|
"@mdi/js": "^7.4.47",
|
|
15
15
|
"vue": "^3.5.13",
|
|
16
16
|
"vuetify": "^4.0.0",
|
|
17
|
-
"@jskit-ai/connectors-core": "0.2.
|
|
18
|
-
"@jskit-ai/connectors-catalog": "0.2.
|
|
17
|
+
"@jskit-ai/connectors-core": "0.2.4",
|
|
18
|
+
"@jskit-ai/connectors-catalog": "0.2.4"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@vitejs/plugin-vue": "^6.0.7",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test --test-concurrency=1"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
"./client": "./src/client/index.js",
|
|
10
10
|
"./server": "./src/server/index.js",
|
|
11
|
+
"./server/types": "./src/server/types.js",
|
|
11
12
|
"./shared": "./src/shared/index.js",
|
|
12
13
|
"./client/conversation": "./src/client/conversation/index.js",
|
|
13
14
|
"./server/codex-events": "./src/server/hosts/codexEvents.js",
|
|
@@ -35,10 +36,11 @@
|
|
|
35
36
|
"./testing/native-opencode": "./src/testing/nativeOpenCode.js",
|
|
36
37
|
"./server/codex-client": "./src/server/conversation/codexClient.js",
|
|
37
38
|
"./client/conversation-submit": "./src/client/conversation/submitText.js",
|
|
39
|
+
"./client/types": "./src/client/types.js",
|
|
38
40
|
"./locales/en.json": "./locales/en.json"
|
|
39
41
|
},
|
|
40
42
|
"dependencies": {
|
|
41
|
-
"@ai-sdk/anthropic": "^4.0.
|
|
43
|
+
"@ai-sdk/anthropic": "^4.0.93",
|
|
42
44
|
"@ai-sdk/cerebras": "^3.0.48",
|
|
43
45
|
"@ai-sdk/google": "^4.0.70",
|
|
44
46
|
"@ai-sdk/groq": "^4.0.41",
|
|
@@ -48,18 +50,19 @@
|
|
|
48
50
|
"@ai-sdk/perplexity": "^4.0.43",
|
|
49
51
|
"@ai-sdk/togetherai": "^3.0.49",
|
|
50
52
|
"@ai-sdk/xai": "^4.0.58",
|
|
51
|
-
"@jskit-ai/
|
|
52
|
-
"@jskit-ai/resource-
|
|
53
|
+
"@jskit-ai/agent": "0.1.0",
|
|
54
|
+
"@jskit-ai/resource-core": "0.1.195",
|
|
55
|
+
"@jskit-ai/resource-crud-core": "0.1.195",
|
|
53
56
|
"@mdi/js": "^7.4.47",
|
|
54
57
|
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
55
|
-
"ai": "^7.0.
|
|
58
|
+
"ai": "^7.0.152",
|
|
56
59
|
"json-rest-schema": "^1.0.17",
|
|
57
60
|
"openai": "^6.22.0",
|
|
58
61
|
"ws": "^8.20.1"
|
|
59
62
|
},
|
|
60
63
|
"peerDependencies": {
|
|
61
|
-
"@jskit-ai/http-runtime": "0.2.
|
|
62
|
-
"@jskit-ai/kernel": "0.2.
|
|
64
|
+
"@jskit-ai/http-runtime": "0.2.4",
|
|
65
|
+
"@jskit-ai/kernel": "0.2.4",
|
|
63
66
|
"vue": "^3.5.13",
|
|
64
67
|
"vuetify": "^4.0.0"
|
|
65
68
|
},
|
|
@@ -123,6 +126,10 @@
|
|
|
123
126
|
"subpath": "./server",
|
|
124
127
|
"summary": "Framework integration: Exports reusable assistant AI/provider and NDJSON streaming helpers."
|
|
125
128
|
},
|
|
129
|
+
{
|
|
130
|
+
"subpath": "./server/types",
|
|
131
|
+
"summary": "Framework integration: Types of the conversation runtime, tool catalogue and AI clients, and the contracts of what an application passes the runtime."
|
|
132
|
+
},
|
|
126
133
|
{
|
|
127
134
|
"subpath": "./shared",
|
|
128
135
|
"summary": "Framework integration: Exports reusable assistant validators, path helpers, query keys, and stream/settings events."
|
|
@@ -198,6 +205,10 @@
|
|
|
198
205
|
{
|
|
199
206
|
"subpath": "./client/conversation-submit",
|
|
200
207
|
"summary": "Optional UI: Original text submission helper forwards companion text through the application draft and canonical send action; also exported by ./client/conversation."
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"subpath": "./client/types",
|
|
211
|
+
"summary": "Framework integration: Types of the conversation adapter, actions, attachments, suggestions, questions, message delivery and API client that client code receives."
|
|
201
212
|
}
|
|
202
213
|
],
|
|
203
214
|
"capabilities": []
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The browser's timers and animation frames, for client code that also runs where there is no window (server
|
|
2
|
+
// rendering and node tests). Without one, timers and frames are not scheduled and each caller takes its own path.
|
|
3
|
+
|
|
4
|
+
/** Whether the page has a window whose `name` (a timer, frame or media function) can be called. */
|
|
5
|
+
function hasWindowFunction(name) {
|
|
6
|
+
// readable-check: allow method-probe - platform object: window exists only in a browser
|
|
7
|
+
return typeof window !== "undefined" && typeof window[name] === "function";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Resolves after the next animation frame, or at once without a window. */
|
|
11
|
+
function waitForLayoutFrame() {
|
|
12
|
+
if (!hasWindowFunction("requestAnimationFrame")) {
|
|
13
|
+
return Promise.resolve();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
window.requestAnimationFrame(resolve);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { hasWindowFunction, waitForLayoutFrame };
|
|
@@ -123,16 +123,29 @@
|
|
|
123
123
|
<script setup>
|
|
124
124
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
|
|
125
125
|
import {
|
|
126
|
-
mdiAlertCircleOutline,
|
|
127
|
-
mdiCheckCircleOutline,
|
|
128
126
|
mdiClose,
|
|
129
127
|
mdiCloseCircleOutline,
|
|
130
|
-
mdiFileOutline,
|
|
131
128
|
mdiRefresh
|
|
132
129
|
} from "@mdi/js";
|
|
133
130
|
|
|
131
|
+
import { hasWindowFunction } from "../browser.js";
|
|
134
132
|
import { useJskitText } from "../i18n.js";
|
|
135
133
|
import { attachmentSizeText } from "./attachmentSize.js";
|
|
134
|
+
import {
|
|
135
|
+
activePhases,
|
|
136
|
+
itemBytesLabel,
|
|
137
|
+
itemCancelable,
|
|
138
|
+
itemColor,
|
|
139
|
+
itemIcon,
|
|
140
|
+
itemKey,
|
|
141
|
+
itemName,
|
|
142
|
+
itemPercent,
|
|
143
|
+
itemPhase,
|
|
144
|
+
itemProgressIndeterminate,
|
|
145
|
+
itemProgressValue,
|
|
146
|
+
itemProgressVisible,
|
|
147
|
+
itemStatusLabel
|
|
148
|
+
} from "./attachmentQueueItems.js";
|
|
136
149
|
|
|
137
150
|
const { t } = useJskitText();
|
|
138
151
|
|
|
@@ -154,7 +167,6 @@ const props = defineProps({
|
|
|
154
167
|
}
|
|
155
168
|
});
|
|
156
169
|
|
|
157
|
-
const activePhases = new Set(["queued", "preparing", "uploading", "delivering"]);
|
|
158
170
|
const queueRoot = ref(null);
|
|
159
171
|
const reducedMotion = ref(false);
|
|
160
172
|
const actionElements = new Map();
|
|
@@ -221,93 +233,16 @@ const announcement = computed(() => {
|
|
|
221
233
|
return messages.join(" ");
|
|
222
234
|
});
|
|
223
235
|
|
|
224
|
-
function itemKey(item = {}) {
|
|
225
|
-
return String(item.clientId || item.id || item.attachmentId || item.fileName || "attachment");
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function itemName(item = {}) {
|
|
229
|
-
return String(item.fileName || item.file?.name || t("jskit.assistant.attachments.unnamed"));
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function itemPhase(item = {}) {
|
|
233
|
-
return String(item.phase || item.status || "queued");
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function itemPercent(item = {}) {
|
|
237
|
-
if (itemPhase(item) === "ready") {
|
|
238
|
-
return 100;
|
|
239
|
-
}
|
|
240
|
-
if (itemPhase(item) === "delivering") {
|
|
241
|
-
return 100;
|
|
242
|
-
}
|
|
243
|
-
if (itemPhase(item) === "queued") {
|
|
244
|
-
return 0;
|
|
245
|
-
}
|
|
246
|
-
const progressValue = item.percent ?? item.progress;
|
|
247
|
-
const directPercent = progressValue === null || progressValue === undefined || progressValue === ""
|
|
248
|
-
? null
|
|
249
|
-
: Number(progressValue);
|
|
250
|
-
if (directPercent !== null && Number.isFinite(directPercent)) {
|
|
251
|
-
return Math.min(100, Math.max(0, Math.round(directPercent)));
|
|
252
|
-
}
|
|
253
|
-
const size = Number(item.size) || 0;
|
|
254
|
-
return size > 0 && Number(item.bytesSent) > 0
|
|
255
|
-
? Math.min(100, Math.max(0, Math.round(((Number(item.bytesSent) || 0) / size) * 100)))
|
|
256
|
-
: null;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function itemProgressVisible(item = {}) {
|
|
260
|
-
return activePhases.has(itemPhase(item));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function itemProgressIndeterminate(item = {}) {
|
|
264
|
-
return itemProgressVisible(item) && itemPercent(item) === null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function itemProgressValue(item = {}) {
|
|
268
|
-
return itemPercent(item) ?? 0;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
236
|
function itemProgressStationary(item = {}) {
|
|
272
237
|
return reducedMotion.value && itemProgressVisible(item) && itemPercent(item) === null;
|
|
273
238
|
}
|
|
274
239
|
|
|
275
|
-
function itemBytesLabel(item = {}) {
|
|
276
|
-
const size = Math.max(0, Number(item.size) || 0);
|
|
277
|
-
const sent = Math.min(Math.max(0, Number(item.bytesSent) || 0), size);
|
|
278
|
-
const percent = itemPercent(item);
|
|
279
|
-
return percent === null
|
|
280
|
-
? t("jskit.assistant.attachments.bytes", { sent: attachmentSizeText(sent), size: attachmentSizeText(size) })
|
|
281
|
-
: t("jskit.assistant.attachments.bytesPercent", { sent: attachmentSizeText(sent), size: attachmentSizeText(size), percent });
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function itemStatusLabel(item = {}) {
|
|
285
|
-
const labels = {
|
|
286
|
-
cancelled: "jskit.assistant.attachments.status.cancelled",
|
|
287
|
-
delivering: "jskit.assistant.attachments.status.delivering",
|
|
288
|
-
failed: item.failureStage === "preparing"
|
|
289
|
-
? "jskit.assistant.attachments.status.preparationFailed"
|
|
290
|
-
: item.failureStage === "handoff"
|
|
291
|
-
? "jskit.assistant.attachments.status.sendingFailed"
|
|
292
|
-
: "jskit.assistant.attachments.status.uploadFailed",
|
|
293
|
-
preparing: "jskit.assistant.attachments.status.preparing",
|
|
294
|
-
queued: "jskit.assistant.attachments.status.queued",
|
|
295
|
-
ready: "jskit.assistant.attachments.status.ready",
|
|
296
|
-
uploading: "jskit.assistant.attachments.status.uploading"
|
|
297
|
-
};
|
|
298
|
-
return t(Object.hasOwn(labels, itemPhase(item)) ? labels[itemPhase(item)] : "jskit.assistant.attachments.status.queued");
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function itemCancelable(item = {}) {
|
|
302
|
-
return ["preparing", "queued", "uploading"].includes(itemPhase(item));
|
|
303
|
-
}
|
|
304
|
-
|
|
305
240
|
function actionKey(item = {}, action = "") {
|
|
306
241
|
return `${itemKey(item)}:${action}`;
|
|
307
242
|
}
|
|
308
243
|
|
|
309
244
|
function actionElement(element = null) {
|
|
310
|
-
return element?.$el ||
|
|
245
|
+
return element?.$el || null;
|
|
311
246
|
}
|
|
312
247
|
|
|
313
248
|
function setActionElement(item = {}, action = "", element = null) {
|
|
@@ -323,7 +258,7 @@ function focusItemAction(item = {}) {
|
|
|
323
258
|
for (const action of ["cancel", "retry", "remove"]) {
|
|
324
259
|
const target = actionElement(actionElements.get(actionKey(item, action)));
|
|
325
260
|
if (target && !target.disabled) {
|
|
326
|
-
target.focus
|
|
261
|
+
target.focus();
|
|
327
262
|
return true;
|
|
328
263
|
}
|
|
329
264
|
}
|
|
@@ -344,51 +279,33 @@ async function handleAction(action, item, index) {
|
|
|
344
279
|
emit("focus-input");
|
|
345
280
|
}
|
|
346
281
|
|
|
347
|
-
function itemIcon(item = {}) {
|
|
348
|
-
if (itemPhase(item) === "ready") {
|
|
349
|
-
return mdiCheckCircleOutline;
|
|
350
|
-
}
|
|
351
|
-
if (itemPhase(item) === "failed") {
|
|
352
|
-
return mdiAlertCircleOutline;
|
|
353
|
-
}
|
|
354
|
-
return mdiFileOutline;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function itemColor(item = {}) {
|
|
358
|
-
if (itemPhase(item) === "ready") {
|
|
359
|
-
return "success";
|
|
360
|
-
}
|
|
361
|
-
if (itemPhase(item) === "failed") {
|
|
362
|
-
return "error";
|
|
363
|
-
}
|
|
364
|
-
return "primary";
|
|
365
|
-
}
|
|
366
|
-
|
|
367
282
|
function updateReducedMotionPreference() {
|
|
368
283
|
reducedMotion.value = reducedMotionMediaQuery?.matches === true;
|
|
369
284
|
}
|
|
370
285
|
|
|
371
|
-
if (
|
|
286
|
+
if (hasWindowFunction("matchMedia")) {
|
|
372
287
|
reducedMotionMediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
373
288
|
updateReducedMotionPreference();
|
|
374
289
|
}
|
|
290
|
+
// readable-check: allow method-probe - platform object: an older browser's media query has addListener() in place of addEventListener()
|
|
291
|
+
const mediaQueryDispatchesEvents = typeof reducedMotionMediaQuery?.addEventListener === "function";
|
|
375
292
|
|
|
376
293
|
onMounted(() => {
|
|
377
294
|
if (!reducedMotionMediaQuery) {
|
|
378
295
|
return;
|
|
379
296
|
}
|
|
380
|
-
if (
|
|
297
|
+
if (mediaQueryDispatchesEvents) {
|
|
381
298
|
reducedMotionMediaQuery.addEventListener("change", updateReducedMotionPreference);
|
|
382
299
|
} else {
|
|
383
|
-
reducedMotionMediaQuery.addListener
|
|
300
|
+
reducedMotionMediaQuery.addListener(updateReducedMotionPreference);
|
|
384
301
|
}
|
|
385
302
|
});
|
|
386
303
|
|
|
387
304
|
onBeforeUnmount(() => {
|
|
388
|
-
if (
|
|
305
|
+
if (mediaQueryDispatchesEvents) {
|
|
389
306
|
reducedMotionMediaQuery.removeEventListener("change", updateReducedMotionPreference);
|
|
390
307
|
} else {
|
|
391
|
-
reducedMotionMediaQuery?.removeListener
|
|
308
|
+
reducedMotionMediaQuery?.removeListener(updateReducedMotionPreference);
|
|
392
309
|
}
|
|
393
310
|
reducedMotionMediaQuery = null;
|
|
394
311
|
});
|
|
@@ -30,7 +30,7 @@ const { t } = useJskitText();
|
|
|
30
30
|
defineProps({ state: { type: Object, required: true } });
|
|
31
31
|
const emit = defineEmits(["submit", "stop"]);
|
|
32
32
|
const sendButton = ref(null);
|
|
33
|
-
defineExpose({ focus() {
|
|
33
|
+
defineExpose({ focus() { sendButton.value?.$el.focus(); } });
|
|
34
34
|
</script>
|
|
35
35
|
<style scoped>
|
|
36
36
|
.assistant-composer-actions, .assistant-composer-actions__tools, .assistant-composer-actions__delivery {
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
</div>
|
|
38
38
|
</div>
|
|
39
39
|
</section>
|
|
40
|
+
<!-- readable-check: allow optional-call - optional operation: an adapter without loadMore, reload, checkDelivery or openLink offers no older history, reload, delivery check or link handling -->
|
|
40
41
|
<AssistantTranscript
|
|
41
42
|
v-bind="conversation" :preview-message="adapter.conversation.previewMessage || null" :working="working" class="assistant-conversation__transcript"
|
|
42
43
|
@load-more="adapter.actions?.loadMore?.($event)" @reload="adapter.actions?.reload?.()"
|
|
@@ -96,9 +97,11 @@
|
|
|
96
97
|
@submit="submit" @tab-to-submit="delivery?.focus()"
|
|
97
98
|
@focus="adapter.suggestions?.focus()" @blur="adapter.suggestions?.blur()"
|
|
98
99
|
@escape="dismissSuggestions"
|
|
99
|
-
@dragenter="
|
|
100
|
-
@
|
|
101
|
-
@
|
|
100
|
+
@dragenter="attachmentsEnabled && adapter.attachments.handleDragEnter($event)"
|
|
101
|
+
@dragover="attachmentsEnabled && adapter.attachments.handleDragOver($event)"
|
|
102
|
+
@dragleave="attachmentsEnabled && adapter.attachments.handleDragLeave($event)"
|
|
103
|
+
@drop="attachmentsEnabled && adapter.attachments.handleDrop($event)"
|
|
104
|
+
@paste="attachmentsEnabled && adapter.attachments.handlePaste($event)"
|
|
102
105
|
>
|
|
103
106
|
<template v-if="attachmentsEnabled" #attachments>
|
|
104
107
|
<input ref="fileInput" hidden multiple type="file" :accept="adapter.attachments.accept" :disabled="!adapter.attachments.canAddFiles" @change="attachSelectedFiles">
|
|
@@ -119,6 +122,7 @@
|
|
|
119
122
|
</template>
|
|
120
123
|
<template #footer>
|
|
121
124
|
<AssistantComposerActions ref="delivery" :state="{ ...adapter.composer, pending, canSend }" @submit="submit" @stop="stop">
|
|
125
|
+
<!-- readable-check: allow optional-call - optional operation: model choices without a provider, model or variant selector, or without reload, ignore that change -->
|
|
122
126
|
<AssistantModelControl
|
|
123
127
|
v-if="adapter.models && adapter.models.enabled !== false" v-model="modelsOpen" v-bind="adapter.models"
|
|
124
128
|
@select-provider="adapter.models.selectProvider?.($event)" @select-model="adapter.models.selectModel?.($event)"
|
|
@@ -163,11 +167,12 @@ import AssistantGoalControl from "./AssistantGoalControl.vue";
|
|
|
163
167
|
import AssistantAttachmentQueue from "./AssistantAttachmentQueue.vue";
|
|
164
168
|
import AssistantQuestionInputs from "./AssistantQuestionInputs.vue";
|
|
165
169
|
import AssistantModelControl from "./AssistantModelControl.vue";
|
|
170
|
+
import { displayedConversation } from "./conversationDisplay.js";
|
|
166
171
|
import { useJskitText } from "../i18n.js";
|
|
167
172
|
|
|
168
173
|
const { t } = useJskitText();
|
|
169
174
|
const props = defineProps({
|
|
170
|
-
adapter: { type: Object, required: true },
|
|
175
|
+
adapter: { type: /** @type {import("vue").PropType<import("../types.js").AssistantConversationAdapter>} */ (Object), required: true },
|
|
171
176
|
avatarSize: { type: String, default: "compact", validator: (value) => ["hidden", "compact", "standard", "large"].includes(value) },
|
|
172
177
|
label: { type: String, default: undefined },
|
|
173
178
|
configuration: { type: Object, default: () => ({}) },
|
|
@@ -247,52 +252,9 @@ const fileInput = ref(null);
|
|
|
247
252
|
const modelsOpen = ref(false);
|
|
248
253
|
const delivery = ref(null);
|
|
249
254
|
const statusId = `assistant-status-${useId()}`;
|
|
250
|
-
const conversation = computed(() =>
|
|
251
|
-
const saved = props.adapter.conversation;
|
|
252
|
-
if (!props.adapter.delivery && !saved.previewMessage && !saved.previewMessages?.length && !saved.interimReply) return saved;
|
|
253
|
-
const { previewMessage, previewMessages, interimReply, ...display } = saved;
|
|
254
|
-
let turns = props.adapter.delivery ? props.adapter.delivery.turns(saved.turns || []) : saved.turns || [];
|
|
255
|
-
if (interimReply?.text && interimReply.turnId) {
|
|
256
|
-
const assistant = { ...interimReply, messageId: interimReply.id, role: "assistant" };
|
|
257
|
-
turns = turns.map(turn => turn.turnId !== interimReply.turnId ? turn : {
|
|
258
|
-
...turn, assistant,
|
|
259
|
-
commentary: (turn.commentary || []).filter(message =>
|
|
260
|
-
(message.outputId || message.messageId) !== (interimReply.outputId || interimReply.id)),
|
|
261
|
-
...(Array.isArray(turn.messages) ? { messages: turn.messages.filter(message =>
|
|
262
|
-
message.role !== "assistant" &&
|
|
263
|
-
(message.outputId || message.messageId) !== (interimReply.outputId || interimReply.id)).concat(assistant) } : {})
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
// This list projects existing capture/pending owners; it never queues delivery.
|
|
267
|
-
const previews = Array.isArray(previewMessages) ? previewMessages : previewMessage ? [previewMessage] : [];
|
|
268
|
-
for (const preview of previews) {
|
|
269
|
-
const messageId = String(preview?.messageId || preview?.id || "").trim();
|
|
270
|
-
const localDelivery = messageId && props.adapter.delivery?.find?.(messageId);
|
|
271
|
-
if ((!localDelivery || localDelivery.status === "failed") && preview?.actions?.send) {
|
|
272
|
-
// Keep one unadmitted bubble and its real status until explicit Edit/discard.
|
|
273
|
-
// A receipt:false user row is not acknowledgement of the pending words.
|
|
274
|
-
turns = turns.map(turn => {
|
|
275
|
-
const failed = turn.optimistic?.id === messageId && turn.optimistic.status === "failed";
|
|
276
|
-
const unadmitted = turn.user?.receipt === false && String(turn.user.messageId || turn.user.id || "") === messageId;
|
|
277
|
-
if (!failed && !unadmitted) return turn;
|
|
278
|
-
return { ...turn, preview: true, previewMessage: preview, user: { ...turn.user, text: String(preview.text || "") } };
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
const alreadyShown = messageId && (localDelivery || turns.some(turn => [
|
|
282
|
-
turn.system, turn.user, turn.assistant, ...(turn.thinking || []), ...(turn.commentary || []), ...(turn.messages || [])
|
|
283
|
-
].some(message => String(message?.messageId || message?.id || "") === messageId &&
|
|
284
|
-
(message.receipt !== false || turn.preview))));
|
|
285
|
-
if (messageId && (preview?.text || preview?.actions?.editing) && !alreadyShown) {
|
|
286
|
-
const user = { messageId, role: "user", text: String(preview.text || "") };
|
|
287
|
-
turns = [...turns, { turnId: messageId, user, messages: [user], preview: true, previewMessage: preview,
|
|
288
|
-
optimistic: { id: messageId, status: "pending" } }];
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
// Transcription and interim replies are display only. The receipt watcher
|
|
292
|
-
// receives canonical turns, never these presentation or delivery overlays.
|
|
293
|
-
return { ...display, turns, welcomeMessage: turns.length ? "" : saved.welcomeMessage };
|
|
294
|
-
});
|
|
255
|
+
const conversation = computed(() => displayedConversation(props.adapter));
|
|
295
256
|
watch([() => props.adapter.delivery, () => props.adapter.conversation.turns], ([controller, turns]) => {
|
|
257
|
+
// readable-check: allow optional-collaborator - optional operation: an adapter without a delivery sends through its own actions
|
|
296
258
|
controller?.reconcile(turns || []);
|
|
297
259
|
}, { immediate: true });
|
|
298
260
|
const pending = computed(() => Boolean(props.adapter.delivery?.state.sending || props.adapter.composer?.pending));
|
|
@@ -318,9 +280,11 @@ const canSend = computed(() => (
|
|
|
318
280
|
async function resend(id) {
|
|
319
281
|
if (props.adapter.actions?.resend) return props.adapter.actions.resend(id);
|
|
320
282
|
const controller = props.adapter.delivery;
|
|
283
|
+
// readable-check: allow optional-collaborator - optional operation: an adapter without a delivery sends through its own actions
|
|
321
284
|
const payload = controller?.find(id)?.payload;
|
|
322
285
|
const attachments = props.adapter.attachments;
|
|
323
286
|
try {
|
|
287
|
+
// readable-check: allow optional-collaborator - optional operation: an adapter without a delivery sends through its own actions
|
|
324
288
|
const response = await controller?.resend(id, { queue: queueWhileSending.value });
|
|
325
289
|
clearAcceptedAttachments(response, payload, attachments);
|
|
326
290
|
return response;
|
|
@@ -343,9 +307,6 @@ function edit(id) {
|
|
|
343
307
|
const draft = props.adapter.delivery?.edit(id, props.adapter.composer?.draft || "");
|
|
344
308
|
if (draft != null) { props.adapter.actions.setDraft(draft); input.value?.focus(); }
|
|
345
309
|
}
|
|
346
|
-
function attachmentEvent(method, event) {
|
|
347
|
-
if (attachmentsEnabled.value) return props.adapter.attachments[method]?.(event);
|
|
348
|
-
}
|
|
349
310
|
function attachSelectedFiles(event) {
|
|
350
311
|
const files = Array.from(event.target.files || []);
|
|
351
312
|
event.target.value = "";
|
|
@@ -397,6 +358,7 @@ function stop() {
|
|
|
397
358
|
}
|
|
398
359
|
function updateConfiguration(value) {
|
|
399
360
|
if (props.configurationMode !== "editable" || pending.value) return;
|
|
361
|
+
// readable-check: allow optional-call - optional operation: an adapter without updateConfiguration keeps its configuration fixed
|
|
400
362
|
return props.adapter.actions?.updateConfiguration?.(value);
|
|
401
363
|
}
|
|
402
364
|
defineExpose({ focus: () => input.value?.focus(), submit, stop });
|
|
@@ -8,17 +8,19 @@ const STATUS_KEYS = Object.freeze({
|
|
|
8
8
|
usageLimited: "jskit.assistant.goal.usageLimited", budgetLimited: "jskit.assistant.goal.budgetLimited", complete: "jskit.assistant.goal.complete"
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
const props = defineProps({
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
state: { type: /** @type {import("vue").PropType<import("../types.js").AssistantGoalState>} */ (Object), required: true }
|
|
13
|
+
});
|
|
12
14
|
const open = ref(false);
|
|
13
15
|
const objective = ref("");
|
|
14
16
|
const tokenBudget = ref("");
|
|
15
17
|
const now = ref(Date.now());
|
|
16
|
-
const goal = computed(() => props.state
|
|
18
|
+
const goal = computed(() => props.state.loading || props.state.available === false ? null : props.state.goal);
|
|
17
19
|
const running = computed(() => goal.value?.status === "active");
|
|
18
|
-
const working = computed(() => running.value && props.state
|
|
20
|
+
const working = computed(() => running.value && props.state.working === true);
|
|
19
21
|
// A Start stays Starting… until the server's goal shows it, even while closed.
|
|
20
|
-
const starting = computed(() => props.state
|
|
21
|
-
const label = computed(() => props.state
|
|
22
|
+
const starting = computed(() => props.state.action === "set");
|
|
23
|
+
const label = computed(() => props.state.loading ? t("jskit.assistant.goal.loading") : props.state.available === false ? t("jskit.assistant.goal.unavailable")
|
|
22
24
|
: starting.value ? t("jskit.assistant.goal.starting")
|
|
23
25
|
: t(Object.hasOwn(STATUS_KEYS, goal.value?.status ?? "") ? STATUS_KEYS[goal.value.status] : "jskit.assistant.goal.set"));
|
|
24
26
|
const nativeElapsed = computed(() => Number.isFinite(goal.value?.elapsedSeconds));
|
|
@@ -43,18 +45,19 @@ const elapsed = computed(() => {
|
|
|
43
45
|
const minutes = Math.floor(total / 60);
|
|
44
46
|
return minutes < 60 ? `${minutes}:${seconds}` : `${Math.floor(minutes / 60)}:${String(minutes % 60).padStart(2, "0")}:${seconds}`;
|
|
45
47
|
});
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
+
// readable-check: allow method-probe - optional operation: a goal state without set() does not allow starting a goal, so Start stays disabled
|
|
49
|
+
const canSet = computed(() => !props.state.loading && props.state.available !== false && typeof props.state.set === "function" && (!goal.value || goal.value.status === "complete"));
|
|
50
|
+
const validBudget = computed(() => props.state.tokenBudgetSupported === false || tokenBudget.value === "" || (Number.isSafeInteger(Number(tokenBudget.value)) && Number(tokenBudget.value) > 0));
|
|
48
51
|
let timer;
|
|
49
|
-
onMounted(() => watch(() => [props.state
|
|
52
|
+
onMounted(() => watch(() => [props.state.enabled, clockRunning.value, goal.value?.elapsedSeconds, goal.value?.sampledAt, createdAt.value], () => {
|
|
50
53
|
clearInterval(timer);
|
|
51
54
|
now.value = Date.now();
|
|
52
|
-
if (props.state
|
|
55
|
+
if (props.state.enabled !== false && clockRunning.value) {
|
|
53
56
|
timer = setInterval(() => { now.value = Date.now(); }, 1000);
|
|
54
57
|
}
|
|
55
58
|
}, { immediate: true, flush: "sync" }));
|
|
56
59
|
onScopeDispose(() => clearInterval(timer));
|
|
57
|
-
watch(() => props.state
|
|
60
|
+
watch(() => props.state.enabled, enabled => { if (enabled === false) open.value = false; });
|
|
58
61
|
async function setGoal() {
|
|
59
62
|
if (!canSet.value || props.state.pending || !objective.value.trim() || !validBudget.value) return;
|
|
60
63
|
const result = await props.state.set({ objective: objective.value.trim(), ...(props.state.tokenBudgetSupported === false || tokenBudget.value === "" ? {} : { tokenBudget: Number(tokenBudget.value) }) });
|
|
@@ -77,7 +77,8 @@
|
|
|
77
77
|
</template>
|
|
78
78
|
|
|
79
79
|
<script setup>
|
|
80
|
-
import { computed,
|
|
80
|
+
import { computed, ref, useId } from "vue";
|
|
81
|
+
import { useTextareaAutoGrow } from "./useTextareaAutoGrow.js";
|
|
81
82
|
const emit = defineEmits(["blur", "escape", "focus", "input-activity", "submit", "tab-to-submit", "update:modelValue", "paste"]);
|
|
82
83
|
const props = defineProps({
|
|
83
84
|
submitOnModifierEnter: { type: Boolean, default: false },
|
|
@@ -155,9 +156,6 @@ const props = defineProps({
|
|
|
155
156
|
|
|
156
157
|
const textareaRef = ref(null);
|
|
157
158
|
const textareaId = `assistant-prompt-${useId()}`;
|
|
158
|
-
let resizeFrame = 0;
|
|
159
|
-
let resizeObserver = null;
|
|
160
|
-
let preserveHeightForNextModelValueChange = false;
|
|
161
159
|
const combinedErrorMessages = computed(() => {
|
|
162
160
|
return Array.isArray(props.errorMessages)
|
|
163
161
|
? props.errorMessages
|
|
@@ -177,55 +175,10 @@ const textareaAriaLabel = computed(() => (
|
|
|
177
175
|
: undefined
|
|
178
176
|
));
|
|
179
177
|
|
|
180
|
-
|
|
181
|
-
if (!props.autoGrow) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
const textarea = textareaRef.value;
|
|
185
|
-
if (!textarea) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (!props.placeholderAffectsHeight && !textarea.value) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
const style = window.getComputedStyle(textarea);
|
|
192
|
-
const minHeight = Number.parseFloat(style.minHeight) || 0;
|
|
193
|
-
const maxHeight = Number.parseFloat(style.maxHeight) || Number.POSITIVE_INFINITY;
|
|
194
|
-
textarea.style.height = "auto";
|
|
195
|
-
const contentHeight = Math.max(textarea.scrollHeight, minHeight);
|
|
196
|
-
const targetHeight = Math.min(contentHeight, maxHeight);
|
|
197
|
-
textarea.style.height = `${targetHeight}px`;
|
|
198
|
-
textarea.style.overflowY = contentHeight > targetHeight + 1 ? "auto" : "hidden";
|
|
199
|
-
}
|
|
178
|
+
const { preserveHeightForNextModelValue, queueResizeTextarea, releasePreservedHeight } = useTextareaAutoGrow(props, textareaRef);
|
|
200
179
|
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (resizeFrame) return;
|
|
206
|
-
resizeFrame = window.requestAnimationFrame(() => {
|
|
207
|
-
resizeFrame = 0;
|
|
208
|
-
resizeTextarea();
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function preserveHeightForNextModelValue() {
|
|
213
|
-
const textarea = textareaRef.value;
|
|
214
|
-
if (!props.autoGrow || !textarea) {
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
if (resizeFrame) {
|
|
218
|
-
window.cancelAnimationFrame(resizeFrame);
|
|
219
|
-
resizeFrame = 0;
|
|
220
|
-
}
|
|
221
|
-
preserveHeightForNextModelValueChange = true;
|
|
222
|
-
textarea.style.height = `${textarea.getBoundingClientRect().height}px`;
|
|
223
|
-
textarea.style.overflowY = "auto";
|
|
224
|
-
return true;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function handleTextareaInput(event = {}) {
|
|
228
|
-
preserveHeightForNextModelValueChange = false;
|
|
180
|
+
function handleTextareaInput(event) {
|
|
181
|
+
releasePreservedHeight();
|
|
229
182
|
emit("input-activity");
|
|
230
183
|
emit("update:modelValue", String(event?.target?.value || ""));
|
|
231
184
|
queueResizeTextarea();
|
|
@@ -289,47 +242,9 @@ function handleTextareaKeydown(event = {}) {
|
|
|
289
242
|
}
|
|
290
243
|
|
|
291
244
|
function focusTextarea(options = { preventScroll: true }) {
|
|
292
|
-
textareaRef.value?.focus
|
|
245
|
+
textareaRef.value?.focus(options);
|
|
293
246
|
}
|
|
294
247
|
|
|
295
|
-
onMounted(() => {
|
|
296
|
-
queueResizeTextarea();
|
|
297
|
-
let lastWidth = 0;
|
|
298
|
-
resizeObserver = new ResizeObserver(([entry]) => {
|
|
299
|
-
// Height changes are our own output. Only width changes need another layout.
|
|
300
|
-
const width = entry.contentRect.width;
|
|
301
|
-
if (width !== lastWidth) {
|
|
302
|
-
lastWidth = width;
|
|
303
|
-
queueResizeTextarea();
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
resizeObserver.observe(textareaRef.value);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
onBeforeUnmount(() => {
|
|
310
|
-
resizeObserver?.disconnect();
|
|
311
|
-
if (resizeFrame && typeof window !== "undefined") {
|
|
312
|
-
window.cancelAnimationFrame(resizeFrame);
|
|
313
|
-
resizeFrame = 0;
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
watch(() => [
|
|
318
|
-
props.autoGrow,
|
|
319
|
-
props.modelValue,
|
|
320
|
-
props.placeholder,
|
|
321
|
-
props.placeholderAffectsHeight,
|
|
322
|
-
props.rows,
|
|
323
|
-
props.density
|
|
324
|
-
], (values, previousValues = []) => {
|
|
325
|
-
const modelValueChanged = values[1] !== previousValues[1];
|
|
326
|
-
if (preserveHeightForNextModelValueChange && modelValueChanged) {
|
|
327
|
-
preserveHeightForNextModelValueChange = false;
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
queueResizeTextarea();
|
|
331
|
-
}, { flush: "post" });
|
|
332
|
-
|
|
333
248
|
defineExpose({ inputElement: textareaRef, focus: focusTextarea, preserveHeightForNextModelValue, queueResizeTextarea });
|
|
334
249
|
</script>
|
|
335
250
|
<style scoped>
|