@jskit-ai/assistant-runtime 0.1.237 → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,138 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 - 2026-10-29
4
+
5
+ - Translation ready: every user-visible text is a message key with its English in `locales/en.json`, so an app translates it by supplying translation files only. With no translations, texts and behaviour are unchanged. See the guide "Translating a JSKIT app".
6
+
7
+ ### New message keys (90)
8
+
9
+ All keys in `locales/en.json` (exported as `./locales/en.json`) are new in this version.
10
+
11
+ <details><summary>Key list</summary>
12
+
13
+ - `jskit.assistant.runtime.authenticationRequired`
14
+ - `jskit.assistant.runtime.workspace.selectionRequired`
15
+ - `jskit.assistant.runtime.route.surfaceHeaderRequired`
16
+ - `jskit.assistant.runtime.route.notFound`
17
+ - `jskit.assistant.runtime.route.surfaceForbidden`
18
+ - `jskit.assistant.runtime.route.assistantNotFound`
19
+ - `jskit.assistant.runtime.connection.unavailable`
20
+ - `jskit.assistant.runtime.connection.selectionNotAllowed`
21
+ - `jskit.assistant.runtime.connection.applicationAi`
22
+ - `jskit.assistant.runtime.connection.applicationDefault`
23
+ - `jskit.assistant.runtime.provider.notConfigured`
24
+ - `jskit.assistant.runtime.conversation.notFound`
25
+ - `jskit.assistant.runtime.conversation.idInvalid`
26
+ - `jskit.assistant.runtime.conversation.createFailed`
27
+ - `jskit.assistant.runtime.conversation.assistantLabel`
28
+ - `jskit.assistant.runtime.conversation.welcomeMessage`
29
+ - `jskit.assistant.runtime.conversation.placeholder`
30
+ - `jskit.assistant.runtime.conversation.startedAtUnknown`
31
+ - `jskit.assistant.runtime.conversation.status.active`
32
+ - `jskit.assistant.runtime.conversation.status.completed`
33
+ - `jskit.assistant.runtime.conversation.status.failed`
34
+ - `jskit.assistant.runtime.conversation.status.aborted`
35
+ - `jskit.assistant.runtime.conversation.status.unknown`
36
+ - `jskit.assistant.runtime.request.messageIdInvalid`
37
+ - `jskit.assistant.runtime.request.inputRequired`
38
+ - `jskit.assistant.runtime.request.messageIdConflict`
39
+ - `jskit.assistant.runtime.request.unconfirmed`
40
+ - `jskit.assistant.runtime.request.failed`
41
+ - `jskit.assistant.runtime.request.cancelled`
42
+ - `jskit.assistant.runtime.request.noOutput`
43
+ - `jskit.assistant.runtime.request.notConfirmed`
44
+ - `jskit.assistant.runtime.attachments.invalidIds`
45
+ - `jskit.assistant.runtime.attachments.notConfigured`
46
+ - `jskit.assistant.runtime.attachments.noLongerAccepted`
47
+ - `jskit.assistant.runtime.attachments.clearFailed`
48
+ - `jskit.assistant.runtime.subscription.idRequired`
49
+ - `jskit.assistant.runtime.subscription.failed`
50
+ - `jskit.assistant.runtime.history.refreshFailed`
51
+ - `jskit.assistant.runtime.history.loadFailed`
52
+ - `jskit.assistant.runtime.history.listFailed`
53
+ - `jskit.assistant.runtime.history.conversationLoadFailed`
54
+ - `jskit.assistant.runtime.accessDenied`
55
+ - `jskit.assistant.runtime.delivery.waitingForStep`
56
+ - `jskit.assistant.runtime.delivery.unconfirmedBeforeClose`
57
+ - `jskit.assistant.runtime.delivery.stillUnknown`
58
+ - `jskit.assistant.runtime.delivery.checkFailed`
59
+ - `jskit.assistant.runtime.delivery.notConfirmed`
60
+ - `jskit.assistant.runtime.goal.budgetsUnsupported`
61
+ - `jskit.assistant.runtime.goal.changeFailed`
62
+ - `jskit.assistant.runtime.goal.unavailable`
63
+ - `jskit.assistant.runtime.goal.resumeMessage`
64
+ - `jskit.assistant.runtime.goal.cancelMessage`
65
+ - `jskit.assistant.runtime.composer.questionsPlaceholder`
66
+ - `jskit.assistant.runtime.composer.choicesPlaceholder`
67
+ - `jskit.assistant.runtime.composer.questionsHint`
68
+ - `jskit.assistant.runtime.tool.progress`
69
+ - `jskit.assistant.runtime.tool.status.pending`
70
+ - `jskit.assistant.runtime.tool.status.done`
71
+ - `jskit.assistant.runtime.tool.status.failed`
72
+ - `jskit.assistant.runtime.tool.status.interrupted`
73
+ - `jskit.assistant.runtime.surface.newConversation`
74
+ - `jskit.assistant.runtime.surface.conversations`
75
+ - `jskit.assistant.runtime.surface.activity`
76
+ - `jskit.assistant.runtime.surface.toolActivity`
77
+ - `jskit.assistant.runtime.surface.conversation`
78
+ - `jskit.assistant.runtime.surface.responseCanceled`
79
+ - `jskit.assistant.runtime.surface.startNewConversation`
80
+ - `jskit.assistant.runtime.surface.refresh`
81
+ - `jskit.assistant.runtime.surface.close`
82
+ - `jskit.assistant.runtime.surface.loadingConversationsLabel`
83
+ - `jskit.assistant.runtime.surface.loadingConversations`
84
+ - `jskit.assistant.runtime.surface.savedConversations`
85
+ - `jskit.assistant.runtime.surface.noSavedConversations`
86
+ - `jskit.assistant.runtime.surface.untitledConversation`
87
+ - `jskit.assistant.runtime.surface.loadOlderConversations`
88
+ - `jskit.assistant.runtime.surface.messageCount`
89
+ - `jskit.assistant.runtime.viewer.you`
90
+ - `jskit.assistant.runtime.settings.title`
91
+ - `jskit.assistant.runtime.settings.noPermission`
92
+ - `jskit.assistant.runtime.settings.save`
93
+ - `jskit.assistant.runtime.settings.systemPrompt`
94
+ - `jskit.assistant.runtime.settings.subtitle`
95
+ - `jskit.assistant.runtime.settings.subtitleWorkspace`
96
+ - `jskit.assistant.runtime.settings.subtitleSurface`
97
+ - `jskit.assistant.runtime.settings.notConfigured`
98
+ - `jskit.assistant.runtime.settings.workspaceUnavailable`
99
+ - `jskit.assistant.runtime.settings.selectWorkspace`
100
+ - `jskit.assistant.runtime.settings.updateFailed`
101
+ - `jskit.errors.assistant_history_inspection_required`
102
+ - `jskit.errors.assistant_subscription_transport_failed`
103
+
104
+ </details>
105
+
106
+ ### Codes (27)
107
+
108
+ Error, field-error and message codes this package sends, with their English. Undotted codes kept their existing values and render from `jskit.errors.<code>`.
109
+
110
+ | Code | English |
111
+ | --- | --- |
112
+ | `assistant.runtime.authenticationRequired` | Authentication required. |
113
+ | `assistant.runtime.workspace.selectionRequired` | Workspace selection required. |
114
+ | `assistant.runtime.route.surfaceHeaderRequired` | Assistant surface header x-jskit-surface is required. |
115
+ | `assistant.runtime.route.notFound` | Assistant route not found. |
116
+ | `assistant.runtime.route.surfaceForbidden` | Assistant route is not available on this surface. |
117
+ | `assistant.runtime.route.assistantNotFound` | Assistant not found. |
118
+ | `assistant.runtime.connection.unavailable` | This AI connection is not available in this assistant. |
119
+ | `assistant.runtime.connection.selectionNotAllowed` | This assistant does not allow selecting an AI connection. |
120
+ | `assistant.runtime.provider.notConfigured` | Assistant provider is not configured. |
121
+ | `assistant.runtime.conversation.notFound` | Conversation not found. |
122
+ | `assistant.runtime.conversation.idInvalid` | conversationId must be a valid record id. |
123
+ | `assistant.runtime.conversation.createFailed` | Assistant failed to create conversation. |
124
+ | `assistant.runtime.request.messageIdInvalid` | messageId is required and must be at most 128 characters. |
125
+ | `assistant.runtime.request.inputRequired` | input is required. |
126
+ | `assistant.runtime.request.messageIdConflict` | This message ID belongs to a different request. |
127
+ | `assistant.runtime.request.unconfirmed` | This message was already submitted. Its completion is not confirmed; check conversation history before sending new work. |
128
+ | `assistant.runtime.request.failed` | Assistant request failed. |
129
+ | `assistant.runtime.request.cancelled` | Assistant request was cancelled. |
130
+ | `assistant.runtime.request.noOutput` | Assistant returned no output. |
131
+ | `assistant.runtime.attachments.invalidIds` | Invalid attachment identifiers. |
132
+ | `assistant.runtime.attachments.notConfigured` | File attachments are not configured for this assistant. |
133
+ | `assistant.runtime.subscription.idRequired` | A conversation subscription id is required. |
134
+ | `assistant.runtime.subscription.failed` | Conversation subscription failed. |
135
+ | `assistant.runtime.history.refreshFailed` | Older conversation history could not be refreshed. Reload chat to try again. |
136
+ | `assistant.runtime.accessDenied` | Access denied. |
137
+ | `assistant_history_inspection_required` | This conversation has ambiguous historical SQL rows. Inspect the reported rows offline before using the common runtime; no history was changed. |
138
+ | `assistant_subscription_transport_failed` | Chat updates could not reconnect. Retrying automatically; Reload chat to try now. |
@@ -0,0 +1,136 @@
1
+ {
2
+ "jskit": {
3
+ "assistant": {
4
+ "runtime": {
5
+ "authenticationRequired": "Authentication required.",
6
+ "workspace": {
7
+ "selectionRequired": "Workspace selection required."
8
+ },
9
+ "route": {
10
+ "surfaceHeaderRequired": "Assistant surface header x-jskit-surface is required.",
11
+ "notFound": "Assistant route not found.",
12
+ "surfaceForbidden": "Assistant route is not available on this surface.",
13
+ "assistantNotFound": "Assistant not found."
14
+ },
15
+ "connection": {
16
+ "unavailable": "This AI connection is not available in this assistant.",
17
+ "selectionNotAllowed": "This assistant does not allow selecting an AI connection.",
18
+ "applicationAi": "Application AI",
19
+ "applicationDefault": "Application default"
20
+ },
21
+ "provider": {
22
+ "notConfigured": "Assistant provider is not configured."
23
+ },
24
+ "conversation": {
25
+ "notFound": "Conversation not found.",
26
+ "idInvalid": "conversationId must be a valid record id.",
27
+ "createFailed": "Assistant failed to create conversation.",
28
+ "assistantLabel": "Assistant",
29
+ "welcomeMessage": "What would you like to do?",
30
+ "placeholder": "Message the assistant…",
31
+ "startedAtUnknown": "unknown",
32
+ "status": {
33
+ "active": "active",
34
+ "completed": "completed",
35
+ "failed": "failed",
36
+ "aborted": "aborted",
37
+ "unknown": "unknown"
38
+ }
39
+ },
40
+ "request": {
41
+ "messageIdInvalid": "messageId is required and must be at most 128 characters.",
42
+ "inputRequired": "input is required.",
43
+ "messageIdConflict": "This message ID belongs to a different request.",
44
+ "unconfirmed": "This message was already submitted. Its completion is not confirmed; check conversation history before sending new work.",
45
+ "failed": "Assistant request failed.",
46
+ "cancelled": "Assistant request was cancelled.",
47
+ "noOutput": "Assistant returned no output.",
48
+ "notConfirmed": "Assistant did not confirm this message."
49
+ },
50
+ "attachments": {
51
+ "invalidIds": "Invalid attachment identifiers.",
52
+ "notConfigured": "File attachments are not configured for this assistant.",
53
+ "noLongerAccepted": "This conversation no longer accepts attachments.",
54
+ "clearFailed": "Accepted attachments could not be cleared from the composer."
55
+ },
56
+ "subscription": {
57
+ "idRequired": "A conversation subscription id is required.",
58
+ "failed": "Conversation subscription failed."
59
+ },
60
+ "history": {
61
+ "refreshFailed": "Older conversation history could not be refreshed. Reload chat to try again.",
62
+ "loadFailed": "Older conversation history could not be loaded.",
63
+ "listFailed": "Unable to load conversation history.",
64
+ "conversationLoadFailed": "Unable to load conversation."
65
+ },
66
+ "accessDenied": "Access denied.",
67
+ "delivery": {
68
+ "waitingForStep": "Waiting for the assistant to finish this step",
69
+ "unconfirmedBeforeClose": "Delivery was not confirmed before this page closed. Check the conversation before retrying.",
70
+ "stillUnknown": "Delivery is still unknown. Retry to check again.",
71
+ "checkFailed": "Delivery could not be checked.",
72
+ "notConfirmed": "Message delivery is not confirmed."
73
+ },
74
+ "goal": {
75
+ "budgetsUnsupported": "This conversation does not support goal token budgets.",
76
+ "changeFailed": "Could not change the goal.",
77
+ "unavailable": "Goal status is unavailable.",
78
+ "resumeMessage": "Resume the goal.",
79
+ "cancelMessage": "Cancel the goal."
80
+ },
81
+ "composer": {
82
+ "questionsPlaceholder": "Optional additional context…",
83
+ "choicesPlaceholder": "Choose an answer above, or type another answer…",
84
+ "questionsHint": "Answer the assistant, then send one combined reply."
85
+ },
86
+ "tool": {
87
+ "progress": "{name} — {status}",
88
+ "status": {
89
+ "pending": "pending",
90
+ "done": "done",
91
+ "failed": "failed",
92
+ "interrupted": "interrupted"
93
+ }
94
+ },
95
+ "surface": {
96
+ "newConversation": "New conversation",
97
+ "conversations": "Conversations",
98
+ "activity": "Activity",
99
+ "toolActivity": "Tool activity",
100
+ "conversation": "Conversation",
101
+ "responseCanceled": "Response canceled. A tool already running may still finish.",
102
+ "startNewConversation": "Start new conversation",
103
+ "refresh": "Refresh",
104
+ "close": "Close",
105
+ "loadingConversationsLabel": "Loading conversations",
106
+ "loadingConversations": "Loading conversations…",
107
+ "savedConversations": "Saved conversations",
108
+ "noSavedConversations": "No saved conversations",
109
+ "untitledConversation": "Untitled conversation",
110
+ "loadOlderConversations": "Load older conversations",
111
+ "messageCount": "{count} messages"
112
+ },
113
+ "viewer": {
114
+ "you": "You"
115
+ },
116
+ "settings": {
117
+ "title": "Assistant settings",
118
+ "noPermission": "You do not have permission to view assistant settings.",
119
+ "save": "Save assistant settings",
120
+ "systemPrompt": "System prompt",
121
+ "subtitle": "Configure assistant settings.",
122
+ "subtitleWorkspace": "Configure the prompt used on the {surface} surface for this workspace.",
123
+ "subtitleSurface": "Configure the prompt used on the {surface} surface.",
124
+ "notConfigured": "Assistant settings are not configured for this surface.",
125
+ "workspaceUnavailable": "Workspace support is not available for this assistant surface.",
126
+ "selectWorkspace": "Select a workspace to configure assistant settings.",
127
+ "updateFailed": "Unable to update assistant settings."
128
+ }
129
+ }
130
+ },
131
+ "errors": {
132
+ "assistant_history_inspection_required": "This conversation has ambiguous historical SQL rows. Inspect the reported rows offline before using the common runtime; no history was changed.",
133
+ "assistant_subscription_transport_failed": "Chat updates could not reconnect. Retrying automatically; Reload chat to try now."
134
+ }
135
+ }
136
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/assistant-runtime",
3
- "version": "0.1.237",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test --test-concurrency=1"
@@ -9,22 +9,23 @@
9
9
  "./client": "./src/client/index.js",
10
10
  "./shared": "./src/shared/index.js",
11
11
  "./server/actionIds": "./src/server/actionIds.js",
12
- "./server": "./src/server/index.js"
12
+ "./server": "./src/server/index.js",
13
+ "./locales/en.json": "./locales/en.json"
13
14
  },
14
15
  "dependencies": {
15
- "@jskit-ai/assistant-core": "0.1.243",
16
+ "@jskit-ai/assistant-core": "0.2.0",
16
17
  "json-rest-schema": "^1.0.17"
17
18
  },
18
19
  "peerDependencies": {
19
- "@jskit-ai/http-runtime": "0.1.245",
20
- "@jskit-ai/http-web": "0.1.95",
21
- "@jskit-ai/kernel": "0.1.247",
22
- "@jskit-ai/realtime": "0.1.247",
23
- "@jskit-ai/shell-web": "0.1.254",
20
+ "@jskit-ai/http-runtime": "0.2.0",
21
+ "@jskit-ai/http-web": "0.2.0",
22
+ "@jskit-ai/kernel": "0.2.0",
23
+ "@jskit-ai/realtime": "0.2.0",
24
+ "@jskit-ai/shell-web": "0.2.0",
24
25
  "@tanstack/vue-query": "^5.90.5",
25
26
  "vue": "^3.5.13",
26
27
  "vuetify": "^4.0.0",
27
- "@jskit-ai/database-runtime": "0.1.247"
28
+ "@jskit-ai/database-runtime": "0.1.249"
28
29
  },
29
30
  "description": "Capability-based assistant runtime with per-surface chat, transcripts, settings, and action tools.",
30
31
  "jskit": {
@@ -2,6 +2,7 @@
2
2
  import { computed, ref, toRef } from "vue";
3
3
  import { AssistantConversationElement } from "@jskit-ai/assistant-core/client/conversation";
4
4
  import { useAssistantConversation } from "../composables/useAssistantConversation.js";
5
+ import { useJskitText } from "../i18n.js";
5
6
 
6
7
  const props = defineProps({
7
8
  conversationId: { type: String, required: true },
@@ -13,9 +14,10 @@ const props = defineProps({
13
14
  api: { type: Object, default: null },
14
15
  active: { type: Boolean, default: true },
15
16
  clearDraftOn: { type: String, default: undefined, validator: value => ["dispatch", "accepted"].includes(value) },
16
- assistantLabel: { type: String, default: "Assistant" },
17
- welcomeMessage: { type: String, default: "What would you like to do?" },
18
- placeholder: { type: String, default: "Message the assistant…" },
17
+ // Unset texts follow the active language; an application's own text is shown as given.
18
+ assistantLabel: { type: String, default: undefined },
19
+ welcomeMessage: { type: String, default: undefined },
20
+ placeholder: { type: String, default: undefined },
19
21
  layout: { type: String, default: "compact", validator: value => ["page", "compact"].includes(value) },
20
22
  data: { type: Object, default: undefined },
21
23
  attachments: { type: Object, default: null },
@@ -26,6 +28,8 @@ const props = defineProps({
26
28
  avatarSize: { type: String, default: "compact", validator: (value) => ["hidden", "compact", "standard", "large"].includes(value) }
27
29
  });
28
30
  const emit = defineEmits(["update:avatarSize"]);
31
+ const { t } = useJskitText();
32
+ const assistantLabelText = computed(() => props.assistantLabel ?? t("jskit.assistant.runtime.conversation.assistantLabel"));
29
33
  const binding = useAssistantConversation({
30
34
  conversationId: toRef(props, "conversationId"), endpoint: toRef(props, "endpoint"),
31
35
  surfaceId: toRef(props, "surfaceId"), hostSurfaceId: toRef(props, "hostSurfaceId"),
@@ -34,8 +38,9 @@ const binding = useAssistantConversation({
34
38
  data: toRef(props, "data"),
35
39
  attachments: toRef(props, "attachments"), suggestions: toRef(props, "suggestions"), models: toRef(props, "models"),
36
40
  questions: toRef(props, "questions"), goal: toRef(props, "goal"),
37
- presentation: computed(() => ({ assistantLabel: props.assistantLabel, welcomeMessage: props.welcomeMessage,
38
- placeholder: props.placeholder, layout: props.layout }))
41
+ presentation: computed(() => ({ assistantLabel: assistantLabelText.value,
42
+ welcomeMessage: props.welcomeMessage ?? t("jskit.assistant.runtime.conversation.welcomeMessage"),
43
+ placeholder: props.placeholder ?? t("jskit.assistant.runtime.conversation.placeholder"), layout: props.layout }))
39
44
  });
40
45
  const conversation = ref(null);
41
46
  defineExpose({ focus: () => conversation.value?.focus(), runtime: binding.runtime });
@@ -43,7 +48,7 @@ defineExpose({ focus: () => conversation.value?.focus(), runtime: binding.runtim
43
48
 
44
49
  <template>
45
50
  <AssistantConversationElement
46
- ref="conversation" :adapter="binding.adapter.value" :label="assistantLabel" :avatar-size="avatarSize"
51
+ ref="conversation" :adapter="binding.adapter.value" :label="assistantLabelText" :avatar-size="avatarSize"
47
52
  class="assistant-client-conversation" @update:avatar-size="emit('update:avatarSize', $event)"
48
53
  >
49
54
  <template v-for="name in Object.keys($slots).filter(name => name !== 'composer-feedback')" #[name]="scope">
@@ -1,16 +1,16 @@
1
1
  <template>
2
2
  <AssistantSettingsFormCard
3
3
  root-class="assistant-settings-client-element"
4
- title="Assistant settings"
4
+ :title="t('jskit.assistant.runtime.settings.title')"
5
5
  :subtitle="subtitle"
6
- no-permission-message="You do not have permission to view assistant settings."
7
- save-label="Save assistant settings"
6
+ :no-permission-message="t('jskit.assistant.runtime.settings.noPermission')"
7
+ :save-label="t('jskit.assistant.runtime.settings.save')"
8
8
  :add-edit="addEdit"
9
9
  :show-form-skeleton="showFormSkeleton"
10
10
  >
11
11
  <v-textarea
12
12
  v-model="form.systemPrompt"
13
- label="System prompt"
13
+ :label="t('jskit.assistant.runtime.settings.systemPrompt')"
14
14
  variant="outlined"
15
15
  density="comfortable"
16
16
  rows="8"
@@ -33,6 +33,7 @@ import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
33
33
  import { useSurfaceRouteContext } from "@jskit-ai/shell-web/client/navigation/useSurfaceRouteContext";
34
34
  import { resolveAssistantSurfaceConfig } from "../../shared/assistantSurfaces.js";
35
35
  import { useWorkspaceWebScopeSupport } from "../support/workspaceScopeSupport.js";
36
+ import { useJskitText } from "../i18n.js";
36
37
 
37
38
  const props = defineProps({
38
39
  targetSurfaceId: {
@@ -49,6 +50,7 @@ const fieldErrors = reactive({
49
50
  systemPrompt: ""
50
51
  });
51
52
 
53
+ const { fieldErrorMessages, serverMessage, t } = useJskitText();
52
54
  const errorRuntime = useShellWebErrorRuntime();
53
55
  const queryClient = useQueryClient();
54
56
  const appConfig = getClientAppConfig();
@@ -93,14 +95,15 @@ const settingsApi = createAssistantApi({
93
95
 
94
96
  const subtitle = computed(() => {
95
97
  if (!assistantSurface.value) {
96
- return "Configure assistant settings.";
98
+ return t("jskit.assistant.runtime.settings.subtitle");
97
99
  }
98
100
 
101
+ const surface = assistantSurface.value.targetSurfaceId;
99
102
  if (assistantSurface.value.configScope === "workspace") {
100
- return `Configure the prompt used on the ${assistantSurface.value.targetSurfaceId} surface for this workspace.`;
103
+ return t("jskit.assistant.runtime.settings.subtitleWorkspace", { surface });
101
104
  }
102
105
 
103
- return `Configure the prompt used on the ${assistantSurface.value.targetSurfaceId} surface.`;
106
+ return t("jskit.assistant.runtime.settings.subtitleSurface", { surface });
104
107
  });
105
108
 
106
109
  function clearFieldErrors() {
@@ -151,18 +154,19 @@ const settingsMutation = useMutation({
151
154
 
152
155
  const loadError = computed(() => {
153
156
  if (!assistantSurface.value) {
154
- return "Assistant settings are not configured for this surface.";
157
+ return t("jskit.assistant.runtime.settings.notConfigured");
155
158
  }
156
159
  if (hasScope.value) {
157
- return normalizeText(settingsQuery.error.value?.message);
160
+ const failure = settingsQuery.error.value;
161
+ return failure?.message ? normalizeText(serverMessage(failure)) : "";
158
162
  }
159
163
 
160
164
  if (assistantSurface.value?.settingsSurfaceRequiresWorkspace) {
161
165
  if (workspaceScopeSupport.available !== true) {
162
- return "Workspace support is not available for this assistant surface.";
166
+ return t("jskit.assistant.runtime.settings.workspaceUnavailable");
163
167
  }
164
168
 
165
- return "Select a workspace to configure assistant settings.";
169
+ return t("jskit.assistant.runtime.settings.selectWorkspace");
166
170
  }
167
171
 
168
172
  return "";
@@ -179,17 +183,18 @@ async function submit() {
179
183
  }
180
184
  });
181
185
  if (!validation.ok) {
182
- fieldErrors.systemPrompt = String(validation.fieldErrors.systemPrompt || "");
186
+ fieldErrors.systemPrompt = String(fieldErrorMessages(validation).systemPrompt || "");
183
187
  return;
184
188
  }
185
189
 
186
190
  try {
187
191
  await settingsMutation.mutateAsync(validation.value);
188
192
  } catch (error) {
189
- const nextFieldErrors = normalizeObject(error?.details?.fieldErrors);
193
+ const nextFieldErrors = fieldErrorMessages({ fieldErrors: normalizeObject(error?.details?.fieldErrors),
194
+ fieldErrorCodes: error?.fieldErrorCodes || error?.details?.fieldErrorCodes });
190
195
  fieldErrors.systemPrompt = String(nextFieldErrors.systemPrompt || "");
191
196
  reportAssistantSettingsError(
192
- normalizeText(error?.message) || "Unable to update assistant settings.",
197
+ (error?.message && normalizeText(serverMessage(error))) || t("jskit.assistant.runtime.settings.updateFailed"),
193
198
  "assistant.settings:save"
194
199
  );
195
200
  }
@@ -2,14 +2,17 @@
2
2
  import { computed, onMounted, onScopeDispose, reactive, ref, toRef, watch, watchPostEffect } from "vue";
3
3
  import { AssistantConversationElement } from "@jskit-ai/assistant-core/client/conversation";
4
4
  import { useAssistantRuntime } from "../composables/useAssistantRuntime.js";
5
- import { mapAssistantConversationTurns } from "../support/assistantRuntimeState.js";
5
+ import { mapAssistantConversationTurns, toolStatusText } from "../support/assistantRuntimeState.js";
6
+ import { DEFAULT_CONVERSATION_TITLE } from "../../shared/assistantRuntimeConfig.js";
7
+ import { useJskitText } from "../i18n.js";
6
8
 
7
9
  const props = defineProps({
8
10
  surfaceId: { type: String, required: true },
9
11
  layout: { type: String, default: "page", validator: (value) => ["page", "compact"].includes(value) },
10
- assistantLabel: { type: String, default: "Assistant" },
11
- welcomeMessage: { type: String, default: "What would you like to do?" },
12
- placeholder: { type: String, default: "Message the assistant…" },
12
+ // Unset texts follow the active language; an application's own text is shown as given.
13
+ assistantLabel: { type: String, default: undefined },
14
+ welcomeMessage: { type: String, default: undefined },
15
+ placeholder: { type: String, default: undefined },
13
16
  showToolActivity: { type: Boolean, default: true },
14
17
  connections: { type: Array, default: () => [] },
15
18
  attachments: { type: Object, default: null },
@@ -19,6 +22,8 @@ const props = defineProps({
19
22
  configurationMode: { type: String, default: "editable", validator: value => ["hidden", "readonly", "editable"].includes(value) }
20
23
  });
21
24
  const integrationId = defineModel("integrationId", { type: String, default: "" });
25
+ const { t } = useJskitText();
26
+ const assistantLabelText = computed(() => props.assistantLabel ?? t("jskit.assistant.runtime.conversation.assistantLabel"));
22
27
  const draftIntegrationId = ref(integrationId.value);
23
28
  watch(integrationId, value => { draftIntegrationId.value = value; });
24
29
  const runtime = useAssistantRuntime({ surfaceId: toRef(props, "surfaceId"), integrationId });
@@ -49,9 +54,10 @@ const adapter = computed(() => ({
49
54
  goal: props.goal,
50
55
  activity: props.activity,
51
56
  models: props.connections.length && props.configurationMode !== "hidden" ? {
52
- providerRows: [{ id: "application", label: "Application AI" }],
57
+ providerRows: [{ id: "application", label: t("jskit.assistant.runtime.connection.applicationAi") }],
53
58
  modelProviderId: "application", modelRows: props.connections, modelId: draftIntegrationId.value,
54
- selectionSummary: props.connections.find(connection => connection.id === integrationId.value)?.label || "Application default",
59
+ selectionSummary: props.connections.find(connection => connection.id === integrationId.value)?.label ||
60
+ t("jskit.assistant.runtime.connection.applicationDefault"),
55
61
  changesDisabled: props.configurationMode !== "editable" || state.isStreaming,
56
62
  canSave: props.configurationMode === "editable" && !state.isStreaming && draftIntegrationId.value !== integrationId.value &&
57
63
  props.connections.some(connection => connection.id === draftIntegrationId.value),
@@ -63,8 +69,9 @@ const adapter = computed(() => ({
63
69
  scrollKey: `${state.scopeKey}:${state.activeConversationId || "new"}`,
64
70
  loading: state.isRestoringConversation,
65
71
  working: state.isStreaming && !runtime.delivery.state.sending,
66
- assistantLabel: props.assistantLabel,
67
- welcomeMessage: state.messages.length || state.isRestoringConversation ? "" : props.welcomeMessage
72
+ assistantLabel: assistantLabelText.value,
73
+ welcomeMessage: state.messages.length || state.isRestoringConversation ? ""
74
+ : props.welcomeMessage ?? t("jskit.assistant.runtime.conversation.welcomeMessage")
68
75
  },
69
76
  composer: {
70
77
  draft: state.input,
@@ -73,7 +80,7 @@ const adapter = computed(() => ({
73
80
  disabled: !state.hasRuntimeScope,
74
81
  canStop: state.isStreaming,
75
82
  stopPending: state.isCanceling,
76
- placeholder: props.placeholder,
83
+ placeholder: props.placeholder ?? t("jskit.assistant.runtime.conversation.placeholder"),
77
84
  rows: 2,
78
85
  density: props.layout === "compact" ? "compact" : "default",
79
86
  submitOnEnter: true,
@@ -97,9 +104,18 @@ function submitMessage({ attachments = [], retryMessageId = "" }) {
97
104
  });
98
105
  }
99
106
 
107
+ const CONVERSATION_STATUS_KEY = "jskit.assistant.runtime.conversation.status.";
108
+
109
+ // The saved default title is stored in English; show it in the active language.
110
+ function conversationTitle(entry, fallbackKey) {
111
+ if (entry?.title === DEFAULT_CONVERSATION_TITLE) return t("jskit.assistant.runtime.surface.newConversation");
112
+ return entry?.title || t(fallbackKey);
113
+ }
114
+
100
115
  function conversationSubtitle(entry) {
101
- return [runtime.meta.normalizeConversationStatus(entry.status), runtime.meta.formatConversationStartedAt(entry.startedAt),
102
- `${Number(entry.messageCount || 0)} messages`].join(" · ");
116
+ return [t(CONVERSATION_STATUS_KEY + runtime.meta.normalizeConversationStatus(entry.status)),
117
+ runtime.meta.formatConversationStartedAt(entry.startedAt),
118
+ t("jskit.assistant.runtime.surface.messageCount", { count: Number(entry.messageCount || 0) })].join(" · ");
103
119
  }
104
120
 
105
121
  async function selectConversation(entry) {
@@ -116,25 +132,25 @@ defineExpose({ focus: () => conversation.value?.focus() });
116
132
  </script>
117
133
 
118
134
  <template>
119
- <section ref="root" class="assistant-surface" :class="`assistant-surface--${layout}`" :aria-label="assistantLabel">
135
+ <section ref="root" class="assistant-surface" :class="`assistant-surface--${layout}`" :aria-label="assistantLabelText">
120
136
  <div class="assistant-surface__toolbar">
121
- <span class="assistant-surface__title">{{ selectedConversation?.title || 'New conversation' }}</span>
122
- <v-btn variant="text" @click="historyOpen = true">Conversations</v-btn>
137
+ <span class="assistant-surface__title">{{ conversationTitle(selectedConversation, "jskit.assistant.runtime.surface.newConversation") }}</span>
138
+ <v-btn variant="text" @click="historyOpen = true">{{ t("jskit.assistant.runtime.surface.conversations") }}</v-btn>
123
139
  <v-menu v-if="showToolActivity && state.pendingToolEvents.length" location="bottom end">
124
140
  <template #activator="{ props: menuProps }">
125
- <v-btn v-bind="menuProps" variant="text">Activity</v-btn>
141
+ <v-btn v-bind="menuProps" variant="text">{{ t("jskit.assistant.runtime.surface.activity") }}</v-btn>
126
142
  </template>
127
- <v-list aria-label="Tool activity" max-height="320" max-width="360" class="assistant-surface__activity">
128
- <v-list-item v-for="event in state.pendingToolEvents" :key="event.id" :title="event.name" :subtitle="event.status" />
143
+ <v-list :aria-label="t('jskit.assistant.runtime.surface.toolActivity')" max-height="320" max-width="360" class="assistant-surface__activity">
144
+ <v-list-item v-for="event in state.pendingToolEvents" :key="event.id" :title="event.name" :subtitle="toolStatusText(event.status)" />
129
145
  </v-list>
130
146
  </v-menu>
131
147
  </div>
132
- <AssistantConversationElement ref="conversation" :adapter="adapter" label="Conversation" class="assistant-surface__conversation">
148
+ <AssistantConversationElement ref="conversation" :adapter="adapter" :label="t('jskit.assistant.runtime.surface.conversation')" class="assistant-surface__conversation">
133
149
  <template v-if="$slots.activity" #activity="scope"><slot name="activity" v-bind="scope" /></template>
134
150
  <template v-if="$slots.attachments" #attachments="scope"><slot name="attachments" v-bind="scope" /></template>
135
151
  <template #message-actions="{ message }">
136
152
  <p v-if="message.status === 'failed' && message.error" class="assistant-surface__message-error" role="status">{{ message.error }}</p>
137
- <p v-if="message.status === 'interrupted'" class="assistant-surface__notice" role="status">Response canceled. A tool already running may still finish.</p>
153
+ <p v-if="message.status === 'interrupted'" class="assistant-surface__notice" role="status">{{ t("jskit.assistant.runtime.surface.responseCanceled") }}</p>
138
154
  </template>
139
155
  <template #composer-feedback>
140
156
  <p v-if="state.error && !state.messages.some((message) => message.error === state.error)" class="assistant-surface__message-error" role="status">{{ state.error }}</p>
@@ -142,28 +158,35 @@ defineExpose({ focus: () => conversation.value?.focus() });
142
158
  <template #composer-tools><slot name="composer-tools" :runtime="runtime" /></template>
143
159
  </AssistantConversationElement>
144
160
 
145
- <v-dialog v-model="historyOpen" max-width="640" scrollable aria-label="Conversations">
161
+ <v-dialog v-model="historyOpen" max-width="640" scrollable :aria-label="t('jskit.assistant.runtime.surface.conversations')">
146
162
  <v-card class="assistant-surface__history">
147
- <v-card-title>Conversations</v-card-title>
163
+ <v-card-title>{{ t("jskit.assistant.runtime.surface.conversations") }}</v-card-title>
148
164
  <v-card-actions class="assistant-surface__history-actions">
149
- <v-btn color="primary" :disabled="!state.canStartNewConversation" @click="startNewConversation">Start new conversation</v-btn>
150
- <v-btn :disabled="state.conversationHistoryLoading" @click="runtime.actions.refreshConversationHistory">Refresh</v-btn>
151
- <v-btn @click="historyOpen = false">Close</v-btn>
165
+ <v-btn color="primary" :disabled="!state.canStartNewConversation" @click="startNewConversation">
166
+ {{ t("jskit.assistant.runtime.surface.startNewConversation") }}
167
+ </v-btn>
168
+ <v-btn :disabled="state.conversationHistoryLoading" @click="runtime.actions.refreshConversationHistory">
169
+ {{ t("jskit.assistant.runtime.surface.refresh") }}
170
+ </v-btn>
171
+ <v-btn @click="historyOpen = false">{{ t("jskit.assistant.runtime.surface.close") }}</v-btn>
152
172
  </v-card-actions>
153
173
  <v-card-text>
154
174
  <p v-if="state.conversationHistoryError" role="alert">{{ state.conversationHistoryError }}</p>
155
- <v-skeleton-loader v-if="state.conversationHistoryLoading && !state.conversationHistory.length" type="list-item-two-line@3" aria-label="Loading conversations" />
156
- <v-list v-else aria-label="Saved conversations">
157
- <v-list-item v-if="!state.conversationHistory.length" title="No saved conversations" />
175
+ <v-skeleton-loader
176
+ v-if="state.conversationHistoryLoading && !state.conversationHistory.length" type="list-item-two-line@3"
177
+ :aria-label="t('jskit.assistant.runtime.surface.loadingConversationsLabel')"
178
+ />
179
+ <v-list v-else :aria-label="t('jskit.assistant.runtime.surface.savedConversations')">
180
+ <v-list-item v-if="!state.conversationHistory.length" :title="t('jskit.assistant.runtime.surface.noSavedConversations')" />
158
181
  <v-list-item
159
182
  v-for="entry in state.conversationHistory" :key="entry.id"
160
183
  :active="String(entry.id) === state.activeConversationId"
161
- :title="entry.title || 'Untitled conversation'" :subtitle="conversationSubtitle(entry)"
184
+ :title="conversationTitle(entry, 'jskit.assistant.runtime.surface.untitledConversation')" :subtitle="conversationSubtitle(entry)"
162
185
  :disabled="state.isStreaming || state.isRestoringConversation" @click="selectConversation(entry)"
163
186
  />
164
187
  </v-list>
165
188
  <v-btn v-if="state.conversationHistoryHasMore" :disabled="state.conversationHistoryLoadingMore" variant="text" @click="runtime.actions.loadMoreConversationHistory">
166
- {{ state.conversationHistoryLoadingMore ? 'Loading conversations…' : 'Load older conversations' }}
189
+ {{ t(state.conversationHistoryLoadingMore ? "jskit.assistant.runtime.surface.loadingConversations" : "jskit.assistant.runtime.surface.loadOlderConversations") }}
167
190
  </v-btn>
168
191
  </v-card-text>
169
192
  </v-card>