@jingyi0605/codingns4dsh 0.2.1-beta.10 → 0.2.1-beta.12

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.
Files changed (248) hide show
  1. package/README.en.md +7 -5
  2. package/README.md +12 -6
  3. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +1 -1
  4. package/data/build/dist/bootstrap/index.d.ts +1 -1
  5. package/data/build/dist/client/bundle.js +9713 -7350
  6. package/data/build/dist/client/bundle.js.map +1 -1
  7. package/data/build/dist/client/cli-slots.d.ts +13 -3
  8. package/data/build/dist/client/cli-slots.d.ts.map +1 -1
  9. package/data/build/dist/client/cli-slots.js +93 -18
  10. package/data/build/dist/client/cli-slots.js.map +1 -1
  11. package/data/build/dist/client/client.assistant-configuration-view.js +4 -3
  12. package/data/build/dist/client/client.assistant-configuration-view.js.map +1 -1
  13. package/data/build/dist/client/client.moss-voice-output.js +415 -0
  14. package/data/build/dist/client/client.moss-voice-output.js.map +1 -0
  15. package/data/build/dist/client/client.native-workspace-store.js +4 -4
  16. package/data/build/dist/client/client.native-workspace-store.js.map +1 -1
  17. package/data/build/dist/client/client.primitives-adapter.js.map +1 -1
  18. package/data/build/dist/client/client.react-dom.js +0 -404
  19. package/data/build/dist/client/client.react-dom.js.map +1 -1
  20. package/data/build/dist/client/client.session-translation.js +105 -2
  21. package/data/build/dist/client/client.session-translation.js.map +1 -1
  22. package/data/build/dist/client/client.transport.js +103 -11
  23. package/data/build/dist/client/client.transport.js.map +1 -1
  24. package/data/build/dist/client/conversation-translation-settings.d.ts +10 -0
  25. package/data/build/dist/client/conversation-translation-settings.d.ts.map +1 -0
  26. package/data/build/dist/client/conversation-translation-settings.js +52 -0
  27. package/data/build/dist/client/conversation-translation-settings.js.map +1 -0
  28. package/data/build/dist/client/desktop-assistant.js +569 -450
  29. package/data/build/dist/client/features/cli-adapters.d.ts.map +1 -1
  30. package/data/build/dist/client/features/cli-adapters.js +3 -1
  31. package/data/build/dist/client/features/cli-adapters.js.map +1 -1
  32. package/data/build/dist/client/features/session-translation.d.ts.map +1 -1
  33. package/data/build/dist/client/features/session-translation.js +49 -48
  34. package/data/build/dist/client/features/session-translation.js.map +1 -1
  35. package/data/build/dist/client/locales/panels.d.ts.map +1 -1
  36. package/data/build/dist/client/locales/panels.js +103 -11
  37. package/data/build/dist/client/locales/panels.js.map +1 -1
  38. package/data/build/dist/client/native-model-translation-menu.d.ts +6 -0
  39. package/data/build/dist/client/native-model-translation-menu.d.ts.map +1 -0
  40. package/data/build/dist/client/native-model-translation-menu.js +57 -0
  41. package/data/build/dist/client/native-model-translation-menu.js.map +1 -0
  42. package/data/build/dist/client/native-translation-default.d.ts +6 -0
  43. package/data/build/dist/client/native-translation-default.d.ts.map +1 -0
  44. package/data/build/dist/client/native-translation-default.js +30 -0
  45. package/data/build/dist/client/native-translation-default.js.map +1 -0
  46. package/data/build/dist/client/session-translation-chat.d.ts +23 -2
  47. package/data/build/dist/client/session-translation-chat.d.ts.map +1 -1
  48. package/data/build/dist/client/session-translation-chat.js +238 -72
  49. package/data/build/dist/client/session-translation-chat.js.map +1 -1
  50. package/data/build/dist/client/session-translation-interactions.d.ts +31 -0
  51. package/data/build/dist/client/session-translation-interactions.d.ts.map +1 -0
  52. package/data/build/dist/client/session-translation-interactions.js +158 -0
  53. package/data/build/dist/client/session-translation-interactions.js.map +1 -0
  54. package/data/build/dist/client/session-translation-model-pool.d.ts +16 -0
  55. package/data/build/dist/client/session-translation-model-pool.d.ts.map +1 -0
  56. package/data/build/dist/client/session-translation-model-pool.js +246 -0
  57. package/data/build/dist/client/session-translation-model-pool.js.map +1 -0
  58. package/data/build/dist/client/session-translation-pending-dom.d.ts +8 -0
  59. package/data/build/dist/client/session-translation-pending-dom.d.ts.map +1 -0
  60. package/data/build/dist/client/session-translation-pending-dom.js +58 -0
  61. package/data/build/dist/client/session-translation-pending-dom.js.map +1 -0
  62. package/data/build/dist/client/session-translation-retry.d.ts +16 -0
  63. package/data/build/dist/client/session-translation-retry.d.ts.map +1 -0
  64. package/data/build/dist/client/session-translation-retry.js +66 -0
  65. package/data/build/dist/client/session-translation-retry.js.map +1 -0
  66. package/data/build/dist/client/session-translation-rules.d.ts +13 -0
  67. package/data/build/dist/client/session-translation-rules.d.ts.map +1 -0
  68. package/data/build/dist/client/session-translation-rules.js +58 -0
  69. package/data/build/dist/client/session-translation-rules.js.map +1 -0
  70. package/data/build/dist/client/style.css +276 -276
  71. package/data/build/dist/dsh-capabilities/client/interaction-presentation-adapter.d.ts +30 -0
  72. package/data/build/dist/dsh-capabilities/client/interaction-presentation-adapter.d.ts.map +1 -0
  73. package/data/build/dist/dsh-capabilities/client/interaction-presentation-adapter.js +93 -0
  74. package/data/build/dist/dsh-capabilities/client/interaction-presentation-adapter.js.map +1 -0
  75. package/data/build/dist/dsh-capabilities/client/interaction-text-dom.d.ts +12 -0
  76. package/data/build/dist/dsh-capabilities/client/interaction-text-dom.d.ts.map +1 -0
  77. package/data/build/dist/dsh-capabilities/client/interaction-text-dom.js +181 -0
  78. package/data/build/dist/dsh-capabilities/client/interaction-text-dom.js.map +1 -0
  79. package/data/build/dist/dsh-capabilities/client/message-presentation-adapter.d.ts +19 -0
  80. package/data/build/dist/dsh-capabilities/client/message-presentation-adapter.d.ts.map +1 -0
  81. package/data/build/dist/dsh-capabilities/client/message-presentation-adapter.js +39 -0
  82. package/data/build/dist/dsh-capabilities/client/message-presentation-adapter.js.map +1 -0
  83. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +1 -0
  84. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts.map +1 -1
  85. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +1 -0
  86. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js.map +1 -1
  87. package/data/build/dist/dsh-capabilities/client/question-history-adapter.d.ts +20 -0
  88. package/data/build/dist/dsh-capabilities/client/question-history-adapter.d.ts.map +1 -0
  89. package/data/build/dist/dsh-capabilities/client/question-history-adapter.js +65 -0
  90. package/data/build/dist/dsh-capabilities/client/question-history-adapter.js.map +1 -0
  91. package/data/build/dist/dsh-capabilities/client/slot-presentation-mirror.d.ts +32 -0
  92. package/data/build/dist/dsh-capabilities/client/slot-presentation-mirror.d.ts.map +1 -0
  93. package/data/build/dist/dsh-capabilities/client/slot-presentation-mirror.js +90 -0
  94. package/data/build/dist/dsh-capabilities/client/slot-presentation-mirror.js.map +1 -0
  95. package/data/build/dist/dsh-capabilities/host/connection-peer-adapter.d.ts +10 -0
  96. package/data/build/dist/dsh-capabilities/host/connection-peer-adapter.d.ts.map +1 -0
  97. package/data/build/dist/dsh-capabilities/host/connection-peer-adapter.js +34 -0
  98. package/data/build/dist/dsh-capabilities/host/connection-peer-adapter.js.map +1 -0
  99. package/data/build/dist/dsh-capabilities/host/native-dispatch-adapter.d.ts +21 -0
  100. package/data/build/dist/dsh-capabilities/host/native-dispatch-adapter.d.ts.map +1 -0
  101. package/data/build/dist/dsh-capabilities/host/native-dispatch-adapter.js +11 -0
  102. package/data/build/dist/dsh-capabilities/host/native-dispatch-adapter.js.map +1 -0
  103. package/data/build/dist/dsh-capabilities/host/session-activation-adapter.d.ts +17 -0
  104. package/data/build/dist/dsh-capabilities/host/session-activation-adapter.d.ts.map +1 -0
  105. package/data/build/dist/dsh-capabilities/host/session-activation-adapter.js +37 -0
  106. package/data/build/dist/dsh-capabilities/host/session-activation-adapter.js.map +1 -0
  107. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts +17 -0
  108. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts.map +1 -0
  109. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js +45 -0
  110. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js.map +1 -0
  111. package/data/build/dist/dsh-capabilities/host/session-prompt-adapter.d.ts +28 -0
  112. package/data/build/dist/dsh-capabilities/host/session-prompt-adapter.d.ts.map +1 -0
  113. package/data/build/dist/dsh-capabilities/host/session-prompt-adapter.js +31 -0
  114. package/data/build/dist/dsh-capabilities/host/session-prompt-adapter.js.map +1 -0
  115. package/data/build/dist/dsh-capabilities/matrix.d.ts.map +1 -1
  116. package/data/build/dist/dsh-capabilities/matrix.js +12 -3
  117. package/data/build/dist/dsh-capabilities/matrix.js.map +1 -1
  118. package/data/build/dist/dsh-capabilities/routes.d.ts.map +1 -1
  119. package/data/build/dist/dsh-capabilities/routes.js +26 -3
  120. package/data/build/dist/dsh-capabilities/routes.js.map +1 -1
  121. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  122. package/data/build/dist/dsh-capabilities/types.d.ts.map +1 -1
  123. package/data/build/dist/dsh-capabilities/types.js.map +1 -1
  124. package/data/build/dist/host/cli-adapters/antigravity-driver.js +1 -1
  125. package/data/build/dist/host/cli-adapters/antigravity-driver.js.map +1 -1
  126. package/data/build/dist/host/cli-adapters/attachment-utils.d.ts +4 -3
  127. package/data/build/dist/host/cli-adapters/attachment-utils.d.ts.map +1 -1
  128. package/data/build/dist/host/cli-adapters/attachment-utils.js +9 -8
  129. package/data/build/dist/host/cli-adapters/attachment-utils.js.map +1 -1
  130. package/data/build/dist/host/cli-adapters/claude-driver.js +1 -1
  131. package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
  132. package/data/build/dist/host/cli-adapters/command-code-driver.js +1 -1
  133. package/data/build/dist/host/cli-adapters/command-code-driver.js.map +1 -1
  134. package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
  135. package/data/build/dist/host/cli-adapters/driver.d.ts.map +1 -1
  136. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +2 -0
  137. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts.map +1 -1
  138. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +28 -11
  139. package/data/build/dist/host/cli-adapters/dsh-message-projector.js.map +1 -1
  140. package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
  141. package/data/build/dist/host/cli-adapters/feature.js +369 -70
  142. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  143. package/data/build/dist/host/cli-adapters/gemini-driver.js +1 -1
  144. package/data/build/dist/host/cli-adapters/gemini-driver.js.map +1 -1
  145. package/data/build/dist/host/cli-adapters/grok-driver.d.ts +1 -0
  146. package/data/build/dist/host/cli-adapters/grok-driver.d.ts.map +1 -1
  147. package/data/build/dist/host/cli-adapters/grok-driver.js +128 -40
  148. package/data/build/dist/host/cli-adapters/grok-driver.js.map +1 -1
  149. package/data/build/dist/host/cli-adapters/kimi-driver.js +2 -2
  150. package/data/build/dist/host/cli-adapters/kimi-driver.js.map +1 -1
  151. package/data/build/dist/host/cli-adapters/mcode-driver.js +1 -1
  152. package/data/build/dist/host/cli-adapters/mcode-driver.js.map +1 -1
  153. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.js +2 -2
  154. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.js.map +1 -1
  155. package/data/build/dist/host/cli-adapters/native-team-subagent.d.ts.map +1 -1
  156. package/data/build/dist/host/cli-adapters/native-team-subagent.js +2 -1
  157. package/data/build/dist/host/cli-adapters/native-team-subagent.js.map +1 -1
  158. package/data/build/dist/host/cli-adapters/pi-driver.js +1 -1
  159. package/data/build/dist/host/cli-adapters/pi-driver.js.map +1 -1
  160. package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
  161. package/data/build/dist/host/cli-adapters/registry.js +25 -16
  162. package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
  163. package/data/build/dist/host/cli-adapters/zcode-driver.js +1 -1
  164. package/data/build/dist/host/cli-adapters/zcode-driver.js.map +1 -1
  165. package/data/build/dist/host/features/global-voice-rpc.d.ts.map +1 -1
  166. package/data/build/dist/host/features/global-voice-rpc.js +2 -3
  167. package/data/build/dist/host/features/global-voice-rpc.js.map +1 -1
  168. package/data/build/dist/host/features/interaction-translation.d.ts +9 -0
  169. package/data/build/dist/host/features/interaction-translation.d.ts.map +1 -0
  170. package/data/build/dist/host/features/interaction-translation.js +168 -0
  171. package/data/build/dist/host/features/interaction-translation.js.map +1 -0
  172. package/data/build/dist/host/features/peer-host.js +6 -6
  173. package/data/build/dist/host/features/peer-host.js.map +1 -1
  174. package/data/build/dist/host/features/session-prompt-translation.d.ts +10 -0
  175. package/data/build/dist/host/features/session-prompt-translation.d.ts.map +1 -0
  176. package/data/build/dist/host/features/session-prompt-translation.js +197 -0
  177. package/data/build/dist/host/features/session-prompt-translation.js.map +1 -0
  178. package/data/build/dist/host/features/session-translation-protection.d.ts +16 -0
  179. package/data/build/dist/host/features/session-translation-protection.d.ts.map +1 -0
  180. package/data/build/dist/host/features/session-translation-protection.js +183 -0
  181. package/data/build/dist/host/features/session-translation-protection.js.map +1 -0
  182. package/data/build/dist/host/features/session-translation-recovery.d.ts +20 -0
  183. package/data/build/dist/host/features/session-translation-recovery.d.ts.map +1 -0
  184. package/data/build/dist/host/features/session-translation-recovery.js +122 -0
  185. package/data/build/dist/host/features/session-translation-recovery.js.map +1 -0
  186. package/data/build/dist/host/features/session-translation-runtime.d.ts +16 -0
  187. package/data/build/dist/host/features/session-translation-runtime.d.ts.map +1 -0
  188. package/data/build/dist/host/features/session-translation-runtime.js +85 -0
  189. package/data/build/dist/host/features/session-translation-runtime.js.map +1 -0
  190. package/data/build/dist/host/features/session-translation-service.d.ts +14 -4
  191. package/data/build/dist/host/features/session-translation-service.d.ts.map +1 -1
  192. package/data/build/dist/host/features/session-translation-service.js +217 -211
  193. package/data/build/dist/host/features/session-translation-service.js.map +1 -1
  194. package/data/build/dist/host/features/session-translation.d.ts.map +1 -1
  195. package/data/build/dist/host/features/session-translation.js +5 -2
  196. package/data/build/dist/host/features/session-translation.js.map +1 -1
  197. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.d.ts +2 -2
  198. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.d.ts.map +1 -1
  199. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.js +10 -9
  200. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.js.map +1 -1
  201. package/data/build/dist/host/modules/peer-host/peer-host-native-streams.d.ts +3 -3
  202. package/data/build/dist/host/modules/peer-host/peer-host-native-streams.d.ts.map +1 -1
  203. package/data/build/dist/host/modules/peer-host/peer-host-native-streams.js +26 -14
  204. package/data/build/dist/host/modules/peer-host/peer-host-native-streams.js.map +1 -1
  205. package/data/build/dist/host/native-session-bridge.d.ts +2 -1
  206. package/data/build/dist/host/native-session-bridge.d.ts.map +1 -1
  207. package/data/build/dist/host/native-session-bridge.js +7 -15
  208. package/data/build/dist/host/native-session-bridge.js.map +1 -1
  209. package/data/build/dist/host/rpc.d.ts.map +1 -1
  210. package/data/build/dist/host/rpc.js +14 -8
  211. package/data/build/dist/host/rpc.js.map +1 -1
  212. package/data/build/dist/host/settings.d.ts.map +1 -1
  213. package/data/build/dist/host/settings.js +10 -2
  214. package/data/build/dist/host/settings.js.map +1 -1
  215. package/data/build/dist/shared/attachment-prompts.d.ts +11 -0
  216. package/data/build/dist/shared/attachment-prompts.d.ts.map +1 -0
  217. package/data/build/dist/shared/attachment-prompts.js +17 -0
  218. package/data/build/dist/shared/attachment-prompts.js.map +1 -0
  219. package/data/build/dist/shared/contracts/cli-adapter.d.ts +8 -1
  220. package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
  221. package/data/build/dist/shared/contracts/errors.d.ts +2 -2
  222. package/data/build/dist/shared/contracts/interaction-presentation.d.ts +32 -0
  223. package/data/build/dist/shared/contracts/interaction-presentation.d.ts.map +1 -0
  224. package/data/build/dist/shared/contracts/interaction-presentation.js +2 -0
  225. package/data/build/dist/shared/contracts/interaction-presentation.js.map +1 -0
  226. package/data/build/dist/shared/contracts/interaction-translation.d.ts +53 -0
  227. package/data/build/dist/shared/contracts/interaction-translation.d.ts.map +1 -0
  228. package/data/build/dist/shared/contracts/interaction-translation.js +44 -0
  229. package/data/build/dist/shared/contracts/interaction-translation.js.map +1 -0
  230. package/data/build/dist/shared/contracts/session-translation.d.ts +75 -7
  231. package/data/build/dist/shared/contracts/session-translation.d.ts.map +1 -1
  232. package/data/build/dist/shared/contracts/session-translation.js +132 -2
  233. package/data/build/dist/shared/contracts/session-translation.js.map +1 -1
  234. package/data/build/dist/shared/contracts/version.d.ts +4 -4
  235. package/data/build/dist/shared/contracts/version.js +3 -3
  236. package/data/build/dist/shared/index.d.ts +1 -1
  237. package/data/build/dist/shared/index.d.ts.map +1 -1
  238. package/data/build/dist/shared/index.js +1 -1
  239. package/data/build/dist/shared/index.js.map +1 -1
  240. package/data/build/dist/shared/translation-indicator.d.ts +26 -0
  241. package/data/build/dist/shared/translation-indicator.d.ts.map +1 -0
  242. package/data/build/dist/shared/translation-indicator.js +29 -0
  243. package/data/build/dist/shared/translation-indicator.js.map +1 -0
  244. package/package.json +35 -34
  245. package/profile/README.md +2 -2
  246. package/profile/package.json +3 -3
  247. package/profile/version.json +3 -3
  248. package/version.json +3 -3
@@ -1,14 +1,32 @@
1
- import { createElement, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
1
+ import { createElement, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
- import { DisclosureRow, FileTypeIcon, IconCheckOutlineRegular, IconCopyOutlineRegular, IconThinkOutlineRegular, JsonBlock, MarkdownText, TextShimmer, fileExtension, fileSizeText, projectUserText, writeClipboard, } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { DisclosureRow, FileTypeIcon, IconCheckOutlineRegular, IconCopyOutlineRegular, IconThinkOutlineRegular, JsonBlock, MarkdownText, TextShimmer, fileExtension, fileSizeText, writeClipboard, } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import * as messagePrimitives from '@deepseek-ai/dsh-client-ui-primitives';
5
+ import { createDshCapabilityRegistry } from '../dsh-capabilities/routes.js';
4
6
  import { useDismissOnOutsidePointer } from './popup-dismiss.js';
5
7
  import { dshThemeColor } from './theme.js';
8
+ import { TRANSLATION_GLYPH_PATHS, TRANSLATION_GLYPH_SIZE, TRANSLATION_PENDING_BADGE_STYLE, TRANSLATION_STATUS_BADGE_STYLE } from '../shared/translation-indicator.js';
6
9
  import { uiFontSize } from './font-scale.js';
10
+ import { useCliSessionTranslationEnabled } from './cli-slots.js';
11
+ import { watchPendingTranslationTargets } from './session-translation-pending-dom.js';
12
+ import { callCliRpc } from './cli-catalog.js';
13
+ import { startSerialPolling } from './serial-polling.js';
14
+ import { clearOutputTranslationRetries, useOutputTranslation } from './session-translation-retry.js';
15
+ import { readSessionTranslationMetrics } from '../shared/contracts/session-translation.js';
7
16
  /**
8
17
  * 把翻译状态和查看原文的入口放回对应消息块,不占用 AI 回复底部的统一操作栏。
9
18
  */
10
- function TranslationAction(props) {
11
- const [open, setOpen] = useState(false);
19
+ export function TranslationAction(props) {
20
+ const [open, setOpen] = useState(props.inputFailure !== undefined);
21
+ const output = useOutputTranslation({ ...props, record: props.inputFailure?.record ?? props.record });
22
+ const [deciding, setDeciding] = useState(false);
23
+ const decidingRef = useRef(false);
24
+ const [decisionError, setDecisionError] = useState(false);
25
+ useEffect(() => {
26
+ if (props.inputFailure !== undefined)
27
+ setOpen(true);
28
+ setDecisionError(false);
29
+ }, [props.inputFailure?.id]);
12
30
  const rootRef = useRef(null);
13
31
  const buttonRef = useRef(null);
14
32
  const popupRef = useRef(null);
@@ -54,20 +72,45 @@ function TranslationAction(props) {
54
72
  window.removeEventListener('scroll', updatePosition, true);
55
73
  };
56
74
  }, [open, props.popupAlign]);
57
- const record = props.record;
58
- const failed = record.failed === true;
59
- const alreadyTargetLanguage = record.alreadyTargetLanguage === true;
60
- const statusText = failed
61
- ? props.translationT('translation.statusFailed')
62
- : alreadyTargetLanguage
63
- ? props.translationT('translation.statusNotNeeded')
64
- : props.translationT('translation.statusTranslated');
65
- const model = formatModel(record.model, props.translationT);
75
+ const record = output.record;
76
+ const inputFailure = props.inputFailure !== undefined;
77
+ const detailsTitle = props.translationT(inputFailure ? 'translation.inputFailedTitle' : 'translation.detailsTitle');
78
+ const pending = record === undefined || output.pending;
79
+ const failed = record?.failed === true;
80
+ const alreadyTargetLanguage = record?.alreadyTargetLanguage === true;
81
+ const statusText = pending
82
+ ? props.translationT('translation.statusTranslating')
83
+ : failed
84
+ ? props.translationT('translation.statusFailed')
85
+ : alreadyTargetLanguage
86
+ ? props.translationT('translation.statusNotNeeded')
87
+ : props.translationT('translation.statusTranslated');
88
+ const statusColor = pending ? dshThemeColor.accent : failed ? dshThemeColor.error : alreadyTargetLanguage ? dshThemeColor.accent : dshThemeColor.success;
66
89
  const detailsId = `codingns-translation-details-${safeDomId(props.messageId)}`;
90
+ const decide = async (choice) => {
91
+ if (props.inputFailure === undefined || decidingRef.current)
92
+ return;
93
+ decidingRef.current = true;
94
+ setDeciding(true);
95
+ setDecisionError(false);
96
+ try {
97
+ await callCliRpc(props.services.rpc, 'translation/resolve', {
98
+ sessionId: props.inputFailure.sessionId, id: props.inputFailure.id, choice,
99
+ }, AbortSignal.timeout(15_000));
100
+ props.onDecision?.();
101
+ }
102
+ catch {
103
+ setDecisionError(true);
104
+ }
105
+ finally {
106
+ decidingRef.current = false;
107
+ setDeciding(false);
108
+ }
109
+ };
67
110
  const details = open && popupPosition !== null ? createElement('div', {
68
111
  ref: popupRef,
69
112
  role: 'dialog',
70
- 'aria-label': props.translationT('translation.detailsTitle'),
113
+ 'aria-label': detailsTitle,
71
114
  id: detailsId,
72
115
  style: {
73
116
  position: 'fixed',
@@ -88,7 +131,17 @@ function TranslationAction(props) {
88
131
  fontSize: uiFontSize(12),
89
132
  lineHeight: 1.45,
90
133
  },
91
- }, createElement('div', { style: { display: 'flex', justifyContent: 'space-between', gap: 10, fontWeight: 600 } }, createElement('span', undefined, props.translationT('translation.detailsTitle')), createElement('span', { style: { color: failed ? dshThemeColor.error : alreadyTargetLanguage ? dshThemeColor.accent : dshThemeColor.success, fontWeight: 400 } }, statusText)), record.sourceLanguage === undefined ? null : createElement('div', { style: { marginTop: 8, color: dshThemeColor.labelSecondary } }, `${props.translationT('translation.detectedLanguage')}: ${props.translationT(`translation.language.${record.sourceLanguage}`)}`), createElement(DetailBlock, { label: props.translationT('translation.originalText'), value: record.original }), createElement(DetailBlock, { label: props.translationT('translation.translatedText'), value: record.translated }), createElement('div', { style: { marginTop: 8, color: dshThemeColor.labelSecondary } }, `${props.translationT('translation.modelUsed')}: ${alreadyTargetLanguage ? props.translationT('translation.modelNotUsed') : model}`)) : null;
134
+ }, createElement('div', { style: { display: 'flex', justifyContent: 'space-between', gap: 10, fontWeight: 600 } }, createElement('span', undefined, detailsTitle), inputFailure ? null : createElement('span', { role: 'status', 'aria-live': 'polite', style: { color: statusColor, fontWeight: 400 } }, statusText)), inputFailure || record?.sourceLanguage === undefined ? null : createElement('div', { style: { marginTop: 8, color: dshThemeColor.labelSecondary } }, `${props.translationT('translation.detectedLanguage')}: ${props.translationT(`translation.language.${record.sourceLanguage}`)}`), inputFailure ? null : createElement(DetailBlock, { kind: 'original', translationT: props.translationT, value: record?.original ?? props.original ?? '' }), pending ? createElement('progress', { 'aria-label': statusText, style: { display: 'block', width: '100%', height: 4, marginTop: 8, accentColor: dshThemeColor.accent } }) : null, pending
135
+ ? createElement('div', { role: 'status', 'aria-live': 'polite', style: { marginTop: 8, color: dshThemeColor.labelSecondary } }, props.translationT('translation.progressHelp'))
136
+ : props.inputFailure !== undefined ? null : createElement(DetailBlock, { kind: props.messageRole === 'user' ? 'sent' : 'translated', translationT: props.translationT, value: record.translated }), pending || inputFailure ? null : createElement('div', { style: { marginTop: 8, color: dshThemeColor.labelSecondary } }, [
137
+ `${props.translationT('translation.modelUsed')}: ${alreadyTargetLanguage ? props.translationT('translation.modelNotUsed') : formatModel(record.model, props.translationT)}`,
138
+ ...(record.durationMs === undefined ? [] : [`${(record.durationMs / 1000).toFixed(2)} s`]),
139
+ ...(record.totalTokens === undefined ? [] : [`${record.totalTokens} tok`]),
140
+ ].join(' · ')), props.inputFailure === undefined ? null : createElement('p', { role: 'alert' }, props.translationT('translation.inputFailedHelp')), decisionError ? createElement('p', { role: 'alert', style: { color: dshThemeColor.error } }, props.translationT('translation.decisionFailed')) : null, props.inputFailure !== undefined ? createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 10 } }, ...['retry', 'send', 'cancel'].map(choice => createElement('button', {
141
+ key: choice, type: 'button', disabled: deciding, onClick: () => { void decide(choice); }, style: recoveryButtonStyle,
142
+ }, props.translationT(choice === 'retry' ? 'translation.retry' : choice === 'send' ? 'translation.sendDirectly' : 'translation.cancelSending')))) : output.retry === undefined ? null : createElement('button', {
143
+ type: 'button', disabled: pending, onClick: () => { void output.retry?.(); }, style: { ...recoveryButtonStyle, marginTop: 10 },
144
+ }, props.translationT('translation.retry'))) : null;
92
145
  const renderedDetails = details === null
93
146
  ? null
94
147
  : typeof document === 'undefined' ? details : createPortal(details, document.body);
@@ -113,38 +166,78 @@ function TranslationAction(props) {
113
166
  style: translationButtonStyle,
114
167
  }, createElement(TranslationGlyph), createElement('span', {
115
168
  'aria-hidden': true,
169
+ 'data-codingns-translation-pending': pending || undefined,
116
170
  style: {
117
- position: 'absolute',
118
- top: 1,
119
- right: 1,
120
- display: 'grid',
121
- placeItems: 'center',
122
- width: 10,
123
- height: 10,
124
- zIndex: 1,
125
- boxSizing: 'border-box',
126
- border: '1.5px solid var(--dsw-alias-bg-base, Canvas)',
127
- borderRadius: '50%',
128
- color: '#fff',
129
- background: failed ? dshThemeColor.error : alreadyTargetLanguage ? dshThemeColor.accent : dshThemeColor.success,
130
- fontSize: 6,
131
- fontWeight: 700,
132
- lineHeight: 1,
171
+ ...TRANSLATION_STATUS_BADGE_STYLE,
172
+ ...(pending ? TRANSLATION_PENDING_BADGE_STYLE : { background: statusColor }),
133
173
  },
134
- }, failed ? '!' : '✓')), renderedDetails);
174
+ }, pending ? null : failed ? '!' : '✓')), renderedDetails);
175
+ }
176
+ const recoveryButtonStyle = {
177
+ padding: '5px 10px', borderRadius: 6, border: `1px solid ${dshThemeColor.border}`,
178
+ background: dshThemeColor.surfaceSubtle, color: dshThemeColor.labelPrimary, cursor: 'pointer', font: 'inherit',
179
+ };
180
+ function PendingTranslationActions(props) {
181
+ const anchorRef = useRef(null);
182
+ const [targets, setTargets] = useState([]);
183
+ const [failures, setFailures] = useState([]);
184
+ const pollingRef = useRef();
185
+ const enabled = useCliSessionTranslationEnabled(props.sessionId, props.services.rpc);
186
+ const store = props.services.settings;
187
+ const settings = useSyncExternalStore(store.subscribe.bind(store), store.getSnapshot.bind(store), store.getSnapshot.bind(store));
188
+ const subagent = props.useSession?.((state) => state.subagent);
189
+ const eligible = enabled && settings.value?.sessionTranslation?.inputEnabled !== false && subagent == null && !props.sessionId?.startsWith('codingns-assistant-');
190
+ useEffect(() => {
191
+ setFailures([]);
192
+ if (!eligible || props.sessionId === undefined)
193
+ return;
194
+ const polling = startSerialPolling(async (signal) => {
195
+ const next = await callCliRpc(props.services.rpc, 'translation/pending', { sessionId: props.sessionId }, signal);
196
+ if (!signal.aborted && Array.isArray(next))
197
+ setFailures(current => JSON.stringify(current) === JSON.stringify(next) ? current : next);
198
+ }, 750);
199
+ pollingRef.current = polling;
200
+ return () => { polling.dispose(); pollingRef.current = undefined; };
201
+ }, [eligible, props.sessionId, props.services.rpc]);
202
+ useLayoutEffect(() => {
203
+ setTargets([]);
204
+ if (!eligible || anchorRef.current === null)
205
+ return undefined;
206
+ return watchPendingTranslationTargets(anchorRef.current, setTargets);
207
+ }, [eligible, props.sessionId]);
208
+ const remaining = [...failures];
209
+ const actions = targets.map(target => {
210
+ const index = remaining.findIndex(failure => normalizePendingText(failure.record.original) === normalizePendingText(target.original));
211
+ const failure = index < 0 ? undefined : remaining.splice(index, 1)[0];
212
+ return createPortal(createElement(PendingTranslationAction, {
213
+ services: props.services,
214
+ translationT: props.translationT,
215
+ original: target.original,
216
+ inputFailure: failure,
217
+ onDecision: () => { void pollingRef.current?.refresh({ afterPending: true }); },
218
+ }), target.container, String(target.key));
219
+ });
220
+ return createElement('span', { ref: anchorRef, style: { display: 'contents' } }, ...actions, ...remaining.map(failure => createElement('span', { key: failure.id, style: { display: 'inline-flex', alignItems: 'center', gap: 8, maxWidth: '100%' } }, createElement('span', { style: { maxWidth: 240, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, failure.record.original), createElement(PendingTranslationAction, { services: props.services, translationT: props.translationT,
221
+ original: failure.record.original, inputFailure: failure,
222
+ onDecision: () => { void pollingRef.current?.refresh({ afterPending: true }); } }))));
223
+ }
224
+ function normalizePendingText(value) { return value.replace(/\s+/gu, ' ').trim(); }
225
+ function PendingTranslationAction(props) {
226
+ const id = useId();
227
+ return createElement(TranslationAction, { ...props, messageId: `pending-${id}`, messageRole: 'user', popupAlign: 'end' });
135
228
  }
136
229
  function TranslationGlyph() {
137
230
  return createElement('svg', {
138
231
  viewBox: '0 0 24 24',
139
- width: 16,
140
- height: 16,
232
+ width: TRANSLATION_GLYPH_SIZE,
233
+ height: TRANSLATION_GLYPH_SIZE,
141
234
  fill: 'none',
142
235
  stroke: 'currentColor',
143
236
  strokeWidth: 1.5,
144
237
  strokeLinecap: 'round',
145
238
  strokeLinejoin: 'round',
146
239
  'aria-hidden': true,
147
- }, createElement('path', { d: 'M3.5 5.5h10M8.5 3.5v2M12.5 5.5c0 4.4-3.3 7.2-8 9' }), createElement('path', { d: 'M5.5 8.5c1.4 2 3.4 3.7 6 4.8M14.5 19.5l3-8 3 8M15.7 17h3.6' }));
240
+ }, ...TRANSLATION_GLYPH_PATHS.map(d => createElement('path', { key: d, d })));
148
241
  }
149
242
  const messageActionButtonStyle = {
150
243
  position: 'relative',
@@ -164,8 +257,36 @@ const translationButtonStyle = {
164
257
  ...messageActionButtonStyle,
165
258
  transition: 'background-color 100ms ease, color 100ms ease',
166
259
  };
167
- function DetailBlock({ label, value }) {
168
- return createElement('div', { style: { marginTop: 8 } }, createElement('div', { style: { marginBottom: 3, color: dshThemeColor.labelSecondary, fontWeight: 600 } }, label), createElement('pre', { style: { margin: 0, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', fontFamily: 'inherit' } }, value));
260
+ const translationDetailTypes = {
261
+ original: { labelKey: 'translation.originalText', color: dshThemeColor.labelSecondary },
262
+ translated: { labelKey: 'translation.translatedText', color: dshThemeColor.accent },
263
+ sent: { labelKey: 'translation.actualSentText', color: dshThemeColor.success },
264
+ };
265
+ function DetailBlock({ kind, translationT, value }) {
266
+ const { labelKey, color } = translationDetailTypes[kind];
267
+ return createElement('div', { style: { marginTop: 8 } }, createElement('div', {
268
+ style: {
269
+ display: 'inline-block',
270
+ maxWidth: '100%',
271
+ boxSizing: 'border-box',
272
+ marginBottom: 6,
273
+ padding: '2px 8px',
274
+ border: `1px solid color-mix(in srgb, ${color} 30%, transparent)`,
275
+ borderRadius: 'var(--dsw-radius-sm, 6px)',
276
+ color,
277
+ background: `color-mix(in srgb, ${color} 12%, transparent)`,
278
+ fontWeight: 600,
279
+ overflowWrap: 'anywhere',
280
+ },
281
+ }, translationT(labelKey)), createElement('pre', {
282
+ style: {
283
+ margin: 0,
284
+ color: `color-mix(in srgb, ${color} 45%, ${dshThemeColor.labelPrimary})`,
285
+ whiteSpace: 'pre-wrap',
286
+ overflowWrap: 'anywhere',
287
+ fontFamily: 'inherit',
288
+ },
289
+ }, value));
169
290
  }
170
291
  /** 按原生 user 节点的结构渲染用户消息;翻译入口只在确实有记录时追加。 */
171
292
  function UserMessageNodeView(props) {
@@ -185,7 +306,12 @@ function UserMessageNodeView(props) {
185
306
  return createElement('div', {
186
307
  'data-chat-flow-kind': 'user',
187
308
  style: { display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 6 },
188
- }, createElement('div', {
309
+ }, props.presentation.kind === 'factory' ? props.presentation.userContent(props, {
310
+ text: parts.text,
311
+ attachments: parts.attachments.map(item => item.type === 'image' ? { type: 'image', image: { attachment: item.attachment } } : item),
312
+ extraBlocks: parts.rest,
313
+ referenceLabels: labels,
314
+ }, { sessionLabels: labels, skillNames }) : createElement('div', {
189
315
  style: {
190
316
  display: 'flex',
191
317
  flexDirection: 'column',
@@ -198,7 +324,7 @@ function UserMessageNodeView(props) {
198
324
  'data-message-attachments': true,
199
325
  style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'flex-end', gap: 8, maxWidth: '100%' },
200
326
  }, ...parts.attachments.map((attachment, index) => attachment.type === 'image'
201
- ? createElement('div', { key: `image-${index}` }, props.renderMessageImages({ images: [{ attachment: attachment.attachment }], align: 'end', compact: compactImages }))
327
+ ? createElement('div', { key: `image-${index}` }, props.presentation.images(props, { images: [{ attachment: attachment.attachment }], align: 'end', compact: compactImages }))
202
328
  : createFileAttachment(attachment.file, index))) : null, showBubble ? createElement('div', {
203
329
  style: {
204
330
  boxSizing: 'border-box',
@@ -212,7 +338,7 @@ function UserMessageNodeView(props) {
212
338
  whiteSpace: 'pre-wrap',
213
339
  wordBreak: 'break-word',
214
340
  },
215
- }, projectUserText(parts.text, labels, skillNames, 'skill', typeof props.openFile === 'function' && typeof props.openSkill === 'function'
341
+ }, props.presentation.projectText(parts.text, labels, skillNames, 'skill', typeof props.openFile === 'function' && typeof props.openSkill === 'function'
216
342
  ? { openFile: props.openFile, openSkill: props.openSkill }
217
343
  : undefined), ...parts.rest.map((block, index) => createElement(JsonBlock, {
218
344
  key: `extra-${index}`,
@@ -238,6 +364,7 @@ function UserMessageNodeView(props) {
238
364
  style: { paddingRight: 12, color: dshThemeColor.labelTertiary, fontSize: 'var(--dsh-content-font-size-secondary, 13px)', whiteSpace: 'nowrap' },
239
365
  }, formatMessageTime(data.time)) : null, translationRecord === undefined ? null : createElement(TranslationAction, {
240
366
  messageId,
367
+ messageRole: 'user',
241
368
  services: props.services,
242
369
  translationT: props.translationT,
243
370
  popupAlign: 'end',
@@ -321,7 +448,7 @@ function formatMessageTime(time) {
321
448
  : { ...(date.getFullYear() === now.getFullYear() ? {} : { year: 'numeric' }), month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' };
322
449
  return new Intl.DateTimeFormat(undefined, options).format(date);
323
450
  }
324
- /** DSH assistant-step renderer: keep each translated text/reasoning block's source action adjacent to that block. */
451
+ /** DSH assistant-step renderer: keep each translation action adjacent to its source block. */
325
452
  function AssistantTranslationNodeView(props) {
326
453
  const data = props.node.data;
327
454
  const blocks = Array.isArray(data.blocks) ? data.blocks : [];
@@ -359,7 +486,8 @@ function AssistantTranslationNodeView(props) {
359
486
  && turnProcess.open !== true;
360
487
  const rendered = [];
361
488
  const last = blocks.length - 1;
362
- const messageId = typeof finalNode?.messageId === 'string' ? finalNode.messageId : `turn-${data.turn}-step-${data.step}`;
489
+ // Keep retries attached when a running step receives its final history node.
490
+ const messageId = `turn-${data.turn}-step-${data.step}`;
363
491
  if (!renderable)
364
492
  return null;
365
493
  for (let index = 0; index < blocks.length; index += 1) {
@@ -375,17 +503,14 @@ function AssistantTranslationNodeView(props) {
375
503
  const translationRecord = readRecord(block) ?? (hasTranslation && eventSnapshot !== undefined
376
504
  ? findTranslationRecordForText(eventSnapshot, block.text, 'assistant', data.turn, data.step)
377
505
  : undefined);
378
- rendered.push(createElement('div', {
506
+ rendered.push(createElement(AssistantTextContent, {
379
507
  key: `text-${index}`,
380
- 'data-codingns-translation-inline': true,
381
- style: { minWidth: 0 },
382
- }, createElement(MarkdownText, { text: block.text, streaming, labels: markdownLabels, fileMentions: mentions, pathImages }), translationRecord === undefined ? null : createElement(TranslationAction, {
383
508
  messageId: `${messageId}-text-${index}`,
509
+ sessionId: props.sessionId,
384
510
  services: props.services,
385
511
  translationT: props.translationT,
386
- record: translationRecord,
387
- inline: true,
388
- })));
512
+ text: block.text, streaming, markdownLabels, mentions, pathImages, translationRecord,
513
+ }));
389
514
  continue;
390
515
  }
391
516
  if (block.kind === 'reasoning') {
@@ -395,6 +520,7 @@ function AssistantTranslationNodeView(props) {
395
520
  rendered.push(createElement(AssistantReasoningBlock, {
396
521
  key: `reasoning-${index}`,
397
522
  messageId: `${messageId}-reasoning-${index}`,
523
+ sessionId: props.sessionId,
398
524
  services: props.services,
399
525
  translationT: props.translationT,
400
526
  chatT,
@@ -420,7 +546,7 @@ function AssistantTranslationNodeView(props) {
420
546
  imageGroup.push(next);
421
547
  index += 1;
422
548
  }
423
- rendered.push(createElement('div', { key: `images-${index}`, style: { alignSelf: 'flex-start' } }, props.renderMessageImages({ images: imageGroup.map((image) => ({ attachment: image.attachment })), align: 'start' })));
549
+ rendered.push(createElement('div', { key: `images-${index}`, style: { alignSelf: 'flex-start' } }, props.presentation.images(props, { images: imageGroup.map((image) => ({ attachment: image.attachment })), align: 'start' })));
424
550
  continue;
425
551
  }
426
552
  if (block.kind === 'other') {
@@ -439,10 +565,18 @@ function AssistantTranslationNodeView(props) {
439
565
  style: { alignSelf: 'flex-start', padding: '0 6px', borderRadius: 'var(--dsw-radius-sm, 4px)', color: dshThemeColor.labelTertiary, background: dshThemeColor.surfaceSubtle, fontSize: 11, lineHeight: '18px' },
440
566
  }, chatT('message.stopped')) : null);
441
567
  }
568
+ function AssistantTextContent(props) {
569
+ const { record } = useOutputTranslation({ ...props, outputKind: 'assistant', record: props.translationRecord });
570
+ return createElement('div', { 'data-codingns-translation-inline': true, style: { minWidth: 0 } }, createElement(MarkdownText, { text: record?.translated ?? props.text, streaming: props.streaming, labels: props.markdownLabels, fileMentions: props.mentions, pathImages: props.pathImages }), record === undefined ? null : createElement(TranslationAction, { messageId: props.messageId, sessionId: props.sessionId,
571
+ messageRole: 'assistant', outputKind: 'assistant', services: props.services, translationT: props.translationT,
572
+ record: props.translationRecord, inline: true }));
573
+ }
442
574
  function AssistantReasoningBlock(props) {
443
575
  const [expanded, setExpanded] = useState(false);
444
576
  const groupRef = props.useGroupAction?.(props.hidden, props.reveal);
445
- const summary = props.text.split('\n').map((line) => line.replace(/[`*_#>]/gu, '').trim()).find(Boolean) ?? '';
577
+ const { record } = useOutputTranslation({ ...props, outputKind: 'reasoning', record: props.translationRecord });
578
+ const displayText = record?.translated ?? props.text;
579
+ const summary = displayText.split('\n').map((line) => line.replace(/[`*_#>]/gu, '').trim()).find(Boolean) ?? '';
446
580
  return createElement('div', {
447
581
  ref: groupRef,
448
582
  hidden: props.hidden,
@@ -460,6 +594,7 @@ function AssistantReasoningBlock(props) {
460
594
  }, createElement(TextShimmer, { active: props.running, children: summary })),
461
595
  }, expanded ? createElement(AssistantReasoningContent, {
462
596
  messageId: props.messageId,
597
+ sessionId: props.sessionId,
463
598
  services: props.services,
464
599
  translationT: props.translationT,
465
600
  text: props.text,
@@ -483,15 +618,20 @@ function AssistantReasoningBlock(props) {
483
618
  * 工厂存在时才允许表现层覆盖。
484
619
  */
485
620
  export function AssistantReasoningContent(props) {
486
- const markdown = createElement(MarkdownText, { text: props.text, streaming: props.running, labels: props.markdownLabels, fileMentions: props.mentions, pathImages: props.pathImages, variant: 'compact' });
621
+ const { record } = useOutputTranslation({ ...props, outputKind: 'reasoning', record: props.translationRecord });
622
+ const displayText = record?.translated ?? props.text;
623
+ const markdown = createElement(MarkdownText, { text: displayText, streaming: props.running, labels: props.markdownLabels, fileMentions: props.mentions, pathImages: props.pathImages, variant: 'compact' });
487
624
  const content = props.renderFactorySlot === undefined
488
625
  ? markdown
489
- : props.renderFactorySlot('conversation.chat.reasoning.content', { text: props.text, running: props.running }, { fallback: markdown });
626
+ : props.renderFactorySlot('conversation.chat.reasoning.content', { text: displayText, running: props.running }, { fallback: markdown });
490
627
  return createElement('div', {
491
628
  'data-codingns-translation-inline': true,
492
629
  style: { paddingTop: 8 },
493
630
  }, content, props.translationRecord === undefined ? null : createElement(TranslationAction, {
494
631
  messageId: props.messageId,
632
+ sessionId: props.sessionId,
633
+ outputKind: 'reasoning',
634
+ messageRole: 'assistant',
495
635
  services: props.services,
496
636
  translationT: props.translationT,
497
637
  record: props.translationRecord,
@@ -528,29 +668,49 @@ function resolveMarkdownImagePath(value) {
528
668
  /**
529
669
  * 注册两个有实际翻译记录的消息渲染器;DSH 的用户气泡内容仍由统一主题令牌控制。
530
670
  */
531
- export function registerSessionTranslationChatUi(services) {
671
+ export function registerSessionTranslationChatUi(services, facts = { primitives: messagePrimitives }) {
672
+ const resolution = createDshCapabilityRegistry(services.dshVersion, 'client', services, facts).getProfile(services).capabilities.get('conversation.message-presentation');
532
673
  const slots = services.slots;
533
674
  if (slots?.inject === undefined || slots.register === undefined)
534
675
  return () => undefined;
535
676
  const removeStyles = installSessionTranslationChatStyles();
536
677
  const translationT = services.locale.bind('codingns');
537
- const removeUserRenderer = slots.inject('conversation.chat.node', () => slots.register({
538
- name: 'conversation.chat.node',
539
- key: 'user',
540
- priority: -100,
541
- locale: 'chat',
678
+ const removePendingActions = slots.inject('conversation.input.left', () => slots.register({
679
+ name: 'conversation.input.left',
680
+ id: 'codingns-translation-pending',
542
681
  inject: (sessionId) => ({ services, sessionId, translationT }),
543
- }, UserMessageNodeView));
544
- const removeAssistantRenderer = slots.inject('conversation.chat.node', () => slots.register({
545
- name: 'conversation.chat.node',
546
- key: 'assistant-step',
547
- priority: -100,
548
- locale: 'chat',
549
- inject: (sessionId) => ({ services, sessionId, translationT }),
550
- }, AssistantTranslationNodeView));
682
+ }, PendingTranslationActions));
683
+ if (resolution?.status !== 'ready')
684
+ return () => { clearOutputTranslationRetries(services); removePendingActions(); removeStyles(); };
685
+ const presentation = resolution.value;
686
+ const removeRenderers = presentation.mount(() => {
687
+ const removeUserRenderer = slots.inject('conversation.chat.node', () => slots.register({
688
+ name: 'conversation.chat.node',
689
+ key: 'user',
690
+ priority: -100,
691
+ locale: 'chat',
692
+ inject: (sessionId) => ({ services, sessionId, translationT }),
693
+ }, props => UserMessageNodeView({ ...props, presentation })));
694
+ let removeAssistantRenderer;
695
+ try {
696
+ removeAssistantRenderer = slots.inject('conversation.chat.node', () => slots.register({
697
+ name: 'conversation.chat.node',
698
+ key: 'assistant-step',
699
+ priority: -100,
700
+ locale: 'chat',
701
+ inject: (sessionId) => ({ services, sessionId, translationT }),
702
+ }, props => AssistantTranslationNodeView({ ...props, presentation })));
703
+ }
704
+ catch (error) {
705
+ removeUserRenderer();
706
+ throw error;
707
+ }
708
+ return () => { removeAssistantRenderer(); removeUserRenderer(); };
709
+ });
551
710
  return () => {
552
- removeAssistantRenderer();
553
- removeUserRenderer();
711
+ clearOutputTranslationRetries(services);
712
+ removePendingActions();
713
+ removeRenderers();
554
714
  removeStyles();
555
715
  };
556
716
  }
@@ -564,6 +724,9 @@ function installSessionTranslationChatStyles() {
564
724
  style.dataset.plugin = 'codingns4dsh';
565
725
  style.dataset.pluginCss = styleId;
566
726
  style.textContent = `
727
+ @keyframes codingns-translation-spin{to{transform:rotate(360deg)}}
728
+ [data-codingns-translation-pending]{animation:codingns-translation-spin .8s linear infinite}
729
+ @media(prefers-reduced-motion:reduce){[data-codingns-translation-pending]{animation-duration:1.6s}}
567
730
  [data-codingns-translation-inline] > div:not([data-codingns-translation-action]){display:contents}
568
731
  [data-codingns-translation-inline] > div:not([data-codingns-translation-action]) > p:last-child{display:inline;margin:0}
569
732
  `;
@@ -594,7 +757,7 @@ function findTranslationRecordForText(snapshot, text, outputKind, turn, step) {
594
757
  collectRecordCandidates(event.data, candidates, new Set());
595
758
  for (const candidate of candidates) {
596
759
  const record = readRecord(candidate);
597
- if (record === undefined || record.translated !== text)
760
+ if (record === undefined || record.original !== text && record.translated !== text)
598
761
  continue;
599
762
  if (outputKind !== undefined && record.outputKind !== undefined && record.outputKind !== outputKind)
600
763
  continue;
@@ -647,6 +810,7 @@ function readRecord(value) {
647
810
  ...(candidate.alreadyTargetLanguage === true ? { alreadyTargetLanguage: true } : {}),
648
811
  ...(model === undefined ? {} : { model }),
649
812
  ...(candidate.failed === true ? { failed: true } : {}),
813
+ ...readSessionTranslationMetrics(candidate),
650
814
  };
651
815
  }
652
816
  function readModel(value) {
@@ -663,8 +827,8 @@ function readInputRecord(value) {
663
827
  return undefined;
664
828
  if (typeof candidate.original !== 'string' || typeof candidate.translated !== 'string')
665
829
  return undefined;
666
- const targetLanguage = candidate.targetLanguage;
667
- if (targetLanguage !== 'en' && targetLanguage !== 'fr' && targetLanguage !== 'de' && targetLanguage !== 'ja')
830
+ const targetLanguage = readLanguage(candidate.targetLanguage);
831
+ if (targetLanguage === undefined)
668
832
  return undefined;
669
833
  const model = readModel(candidate.model);
670
834
  return {
@@ -675,6 +839,7 @@ function readInputRecord(value) {
675
839
  ...(candidate.alreadyTargetLanguage === true ? { alreadyTargetLanguage: true } : {}),
676
840
  ...(model === undefined ? {} : { model }),
677
841
  ...(candidate.failed === true ? { failed: true } : {}),
842
+ ...readSessionTranslationMetrics(candidate),
678
843
  };
679
844
  }
680
845
  function inputRecordForAction(record) {
@@ -687,6 +852,7 @@ function inputRecordForAction(record) {
687
852
  ...(record.alreadyTargetLanguage === true ? { alreadyTargetLanguage: true } : {}),
688
853
  ...(record.model === undefined ? {} : { model: record.model }),
689
854
  ...(record.failed === true ? { failed: true } : {}),
855
+ ...readSessionTranslationMetrics(record),
690
856
  };
691
857
  }
692
858
  function readLanguage(value) {