nolo-cli 0.1.30 → 0.1.31

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 (308) hide show
  1. package/agent-runtime/agentRecordConfig.ts +0 -2
  2. package/agent-runtime/hostAdapter.ts +0 -1
  3. package/agent-runtime/hostRuntimeCapabilities.ts +7 -0
  4. package/agent-runtime/hostRuntimeConfig.ts +86 -0
  5. package/agent-runtime/index.ts +13 -0
  6. package/agent-runtime/localToolPolicy.ts +13 -6
  7. package/agent-runtime/localWorkspaceTools.ts +333 -30
  8. package/agent-runtime/platformChatProvider.ts +4 -1
  9. package/agent-runtime/runtimeToolPolicy.ts +32 -47
  10. package/agent-runtime/shellCommandPolicy.ts +117 -0
  11. package/agent-runtime/toolGate.ts +38 -0
  12. package/agentRecordCommands.ts +1 -1
  13. package/agentRecordHelpers.ts +35 -20
  14. package/agentRunParsing.ts +18 -5
  15. package/agentRuntimeLocal.ts +10 -0
  16. package/ai/agent/agentRunDialogLineage.ts +56 -0
  17. package/ai/agent/agentSlice.ts +45 -10
  18. package/ai/agent/avatarUtils.ts +1 -1
  19. package/ai/agent/buildSystemPrompt.ts +1 -2
  20. package/ai/agent/cliExecutor.ts +330 -19
  21. package/ai/agent/createAgentSchema.ts +22 -0
  22. package/ai/agent/hooks/fetchPublicAgents.ts +14 -39
  23. package/ai/agent/hooks/usePublicAgents.ts +90 -192
  24. package/ai/agent/publicAgentCatalog.ts +359 -0
  25. package/ai/agent/publicAgentIdentity.ts +76 -0
  26. package/ai/agent/server/fetchPublicAgents.ts +21 -46
  27. package/ai/agent/serverOrigin.ts +7 -0
  28. package/ai/agent/streamAgentChatTurn.ts +23 -21
  29. package/ai/agent/streamAgentChatTurnUtils.ts +292 -6
  30. package/ai/agent/types.ts +2 -0
  31. package/ai/agent/web/agentDisplayUtils.ts +171 -0
  32. package/ai/agent/web/useAgentModelAvatarComponent.ts +32 -0
  33. package/ai/ai.locale.ts +8 -0
  34. package/ai/chat/sendOpenAICompletionsRequest.native.ts +15 -0
  35. package/ai/chat/sendOpenAICompletionsRequest.ts +93 -1
  36. package/ai/chat/sendOpenAIResponseRequest.ts +86 -9
  37. package/ai/chat/updateTotalUsage.ts +7 -0
  38. package/ai/llm/deepinfra.ts +30 -15
  39. package/ai/llm/mistral.ts +3 -4
  40. package/ai/llm/providers.ts +2 -0
  41. package/ai/llm/types.ts +15 -0
  42. package/ai/memory/capture.ts +3 -3
  43. package/ai/memory/consolidate.ts +2 -2
  44. package/ai/memory/delete.ts +4 -3
  45. package/ai/memory/query.ts +3 -3
  46. package/ai/memory/remember.ts +2 -2
  47. package/ai/memory/store.ts +3 -3
  48. package/ai/memory/storeShared.ts +3 -2
  49. package/ai/policy/commercialMultiplier.ts +16 -0
  50. package/ai/token/billingAdminAuditEvent.ts +68 -0
  51. package/ai/token/billingAdminAuditEventWriter.ts +41 -0
  52. package/ai/token/billingAnomaly.ts +90 -0
  53. package/ai/token/billingAnomalyLifecycle.ts +54 -0
  54. package/ai/token/billingAnomalyLifecycleWriter.ts +44 -0
  55. package/ai/token/billingAnomalyWriter.ts +41 -0
  56. package/ai/token/calculatePrice.ts +3 -2
  57. package/ai/token/deepinfraReconciliation.ts +153 -0
  58. package/ai/token/deepinfraRequestIdQuery.ts +92 -0
  59. package/ai/token/externalToolCost.ts +54 -41
  60. package/ai/token/missingUsageEstimate.ts +12 -3
  61. package/ai/token/normalizeUsage.ts +45 -5
  62. package/ai/token/openaiReconciliation.ts +188 -0
  63. package/ai/token/providerBillableEvent.ts +84 -0
  64. package/ai/token/providerBillableEventWriter.ts +36 -0
  65. package/ai/token/providerBillingAnomalyDrilldown.ts +308 -0
  66. package/ai/token/providerBillingDrilldown.ts +361 -0
  67. package/ai/token/providerBillingHealthReport.ts +366 -0
  68. package/ai/token/providerCall.ts +144 -0
  69. package/ai/token/providerCallSweeper.ts +168 -0
  70. package/ai/token/providerCallWriter.ts +33 -0
  71. package/ai/token/providerCredential.ts +89 -0
  72. package/ai/token/providerCredentialAnomaly.ts +154 -0
  73. package/ai/token/providerCredentialLifecycle.ts +59 -0
  74. package/ai/token/providerCredentialLifecycleWriter.ts +44 -0
  75. package/ai/token/providerCredentialRegistry.ts +83 -0
  76. package/ai/token/providerCredentialRegistryCommand.ts +97 -0
  77. package/ai/token/providerCredentialRegistryList.ts +124 -0
  78. package/ai/token/providerCredentialRegistryResolver.ts +150 -0
  79. package/ai/token/providerCredentialRegistryWriter.ts +41 -0
  80. package/ai/token/providerCredentialRotationCommand.ts +102 -0
  81. package/ai/token/providerCredentialRuntime.ts +56 -0
  82. package/ai/token/providerDispatchIntent.ts +67 -0
  83. package/ai/token/providerDispatchIntentWriter.ts +36 -0
  84. package/ai/token/providerGateway.ts +260 -0
  85. package/ai/token/providerReconciliation.ts +148 -0
  86. package/ai/token/providerReconciliationLocal.ts +97 -0
  87. package/ai/token/providerReconciliationRunner.ts +165 -0
  88. package/ai/token/providerReconciliationWriter.ts +41 -0
  89. package/ai/token/ratingResult.ts +202 -0
  90. package/ai/token/ratingResultWriter.ts +33 -0
  91. package/ai/token/serverDialogProjection.ts +5 -3
  92. package/ai/token/serverTokenWriter.ts +11 -3
  93. package/ai/token/types.ts +10 -0
  94. package/ai/tools/agent/callAgentTool.ts +3 -4
  95. package/ai/tools/agent/presets/appBuilderPreset.ts +5 -7
  96. package/ai/tools/agent/runStreamingAgentTool.ts +1 -2
  97. package/ai/tools/agent/streamParallelAgentsTool.ts +2 -3
  98. package/ai/tools/readXPostTool.ts +1 -5
  99. package/app/admin/adminPages.ts +9 -0
  100. package/app/i18n/i18n.config.ts +5 -0
  101. package/app/i18n/translations/pricing.locale.ts +397 -0
  102. package/app/utils/desktopAgentRuntimeStatusClient.ts +5 -4
  103. package/app/utils/desktopAgentRuntimeTurnClient.ts +4 -1
  104. package/app/utils/imageUtils.ts +64 -34
  105. package/auth/adminPermissions.ts +18 -0
  106. package/auth/authSlice.ts +26 -12
  107. package/auth/hooks/useFetchUsers.ts +3 -0
  108. package/auth/routes.ts +46 -0
  109. package/auth/server/accessActivity.ts +4 -3
  110. package/auth/server/adminPermissions.ts +43 -0
  111. package/auth/server/adminUsageReport.ts +375 -0
  112. package/auth/server/billingAnomalies.ts +235 -0
  113. package/auth/server/billingUsageReport.ts +41 -2
  114. package/auth/server/creatorEarningCreditConversion.ts +10 -2
  115. package/auth/server/deduct.ts +14 -7
  116. package/auth/server/delete.ts +10 -2
  117. package/auth/server/disable.ts +4 -3
  118. package/auth/server/emailAutomation.ts +7 -5
  119. package/auth/server/emailDbConfig.ts +4 -2
  120. package/auth/server/emailDelivery.ts +24 -20
  121. package/auth/server/emailDeliveryGuards.ts +10 -7
  122. package/auth/server/emailPreferences.ts +6 -4
  123. package/auth/server/emailReport.ts +6 -4
  124. package/auth/server/emailRuntimeConfig.ts +3 -3
  125. package/auth/server/emailScenarioNotifications.ts +9 -8
  126. package/auth/server/enable.ts +4 -3
  127. package/auth/server/getRechargeHistory.ts +3 -2
  128. package/auth/server/getUser.ts +3 -2
  129. package/auth/server/gptProAccess.ts +44 -0
  130. package/auth/server/inviteReward.ts +10 -7
  131. package/auth/server/listusers.ts +6 -4
  132. package/auth/server/login.ts +2 -2
  133. package/auth/server/productEntitlements.ts +7 -4
  134. package/auth/server/productPurchase.ts +10 -2
  135. package/auth/server/providerBillingDrilldown.ts +47 -0
  136. package/auth/server/providerBillingHealth.ts +71 -0
  137. package/auth/server/providerCredentials.ts +136 -0
  138. package/auth/server/recharge.ts +14 -8
  139. package/auth/server/revokeSessions.ts +4 -3
  140. package/auth/server/route.ts +77 -0
  141. package/auth/server/sendEmail.ts +5 -4
  142. package/auth/server/signup.ts +10 -3
  143. package/auth/server/token.ts +53 -55
  144. package/auth/server/tokenUsageBilling.ts +18 -7
  145. package/auth/server/usageManagementAccess.ts +43 -0
  146. package/auth/server/usageReport.ts +59 -0
  147. package/auth/types.ts +12 -0
  148. package/auth/utils.ts +24 -22
  149. package/authCommands.ts +36 -2
  150. package/buildPublish.ts +3 -1
  151. package/buildPublishArtifactAssembly.ts +62 -1
  152. package/buildPublishImportAnalysis.ts +21 -16
  153. package/chat/dialog/dialogLaunchScope.ts +32 -0
  154. package/chat/dialog/objectAssistantRegistry.ts +2 -2
  155. package/chat/dialog/useCreateDialog.ts +1 -1
  156. package/chat/hooks/useChatInput.ts +41 -10
  157. package/chat/messages/messageSlice.ts +5 -1
  158. package/chat/messages/pendingAttachmentParts.ts +28 -2
  159. package/chat/messages/sendFirstMessage.ts +76 -79
  160. package/chat/messages/types.ts +9 -0
  161. package/chat/messages/web/streamingMarkdownModel.ts +41 -0
  162. package/chat/messages/web/useStreamingReveal.ts +91 -0
  163. package/chat/web/messageInputAgentUi.ts +94 -6
  164. package/cliEnvHelpers.ts +8 -0
  165. package/cliRuntimeDoctorReport.ts +193 -0
  166. package/client/agentConfigResolver.test.ts +0 -2
  167. package/client/agentRun.test.ts +171 -10
  168. package/client/agentRun.ts +44 -637
  169. package/client/agentRunInput.ts +10 -0
  170. package/client/agentRunLocalExecutor.ts +179 -0
  171. package/client/agentRunServerTransport.ts +394 -0
  172. package/client/agentRunTaskRunRecorder.ts +176 -0
  173. package/client/localRuntimeAdapter.test.ts +236 -9
  174. package/client/localRuntimeAdapter.ts +221 -23
  175. package/client/localToolPolicy.test.ts +28 -4
  176. package/connector-experimental/daemonLauncher.ts +38 -4
  177. package/connectorRunArtifact.ts +34 -10
  178. package/connectorWebSocketTarget.ts +9 -5
  179. package/core/builtinAgents.ts +155 -11
  180. package/create/space/contentLabels.ts +1 -1
  181. package/create/space/contentTypeMeta.ts +3 -2
  182. package/create/space/sidebarVisibleTypes.ts +27 -0
  183. package/create/space/space.locale.ts +76 -4
  184. package/database/actions/agentReadResolution.ts +2 -8
  185. package/database/actions/read.ts +11 -9
  186. package/database/actions/readAndWait.ts +10 -5
  187. package/database/server/agentDelegation.ts +3 -2
  188. package/database/server/authorityStoreConformance.ts +209 -0
  189. package/database/server/authorityStoreTypes.ts +31 -0
  190. package/database/server/coreDataProxy.ts +1 -1
  191. package/database/server/dataHandlers.ts +13 -5
  192. package/database/server/db.ts +15 -34
  193. package/database/server/delete.ts +88 -71
  194. package/database/server/emailRepository.ts +11 -9
  195. package/database/server/fileService.ts +2 -1
  196. package/database/server/legacyServerDb.ts +58 -0
  197. package/database/server/levelAuthorityStore.ts +75 -0
  198. package/database/server/memoryAuthorityStore.ts +133 -0
  199. package/database/server/patch.ts +13 -12
  200. package/database/server/query.ts +3 -2
  201. package/database/server/read.ts +33 -30
  202. package/database/server/resourceAccess.ts +7 -5
  203. package/database/server/serverStoreFactory.ts +124 -0
  204. package/database/server/spaceMemberAuthority.ts +3 -2
  205. package/database/server/storeBatchCompat.ts +37 -0
  206. package/database/server/testAuthorityStore.ts +76 -0
  207. package/database/server/write.ts +29 -27
  208. package/integrations/anthropic/anthropicModels.ts +12 -12
  209. package/integrations/deepseek/models.ts +67 -67
  210. package/integrations/google/models.ts +111 -53
  211. package/integrations/openai/models.ts +22 -22
  212. package/integrations/xai/models.ts +1 -1
  213. package/localRuntimeDb.ts +39 -2
  214. package/machineCommandPrompts.ts +2 -0
  215. package/machineCommands.ts +193 -63
  216. package/machineWsRunDispatch.ts +80 -15
  217. package/machineWsSession.ts +39 -2
  218. package/noloReadBridgePrompt.ts +16 -0
  219. package/package.json +99 -6
  220. package/render/layout/mainLayoutViewMode.ts +25 -0
  221. package/render/page/server/createPage.ts +3 -1
  222. package/render/page/server/handleCreatePage.ts +2 -11
  223. package/runtimeDoctorCommands.ts +15 -66
  224. package/server/api/methods.ts +34 -12
  225. package/server/api/tableHandlers.ts +6 -3
  226. package/server/authenticateToken.ts +47 -2
  227. package/server/billingLedgerRoutes.ts +15 -0
  228. package/server/buildManifest.ts +54 -0
  229. package/server/coreReadiness.ts +34 -25
  230. package/server/coreRuntime.ts +15 -10
  231. package/server/handlers/activityStatsHandler.ts +5 -2
  232. package/server/handlers/agentEmailRegistrationE2EHandler.ts +25 -23
  233. package/server/handlers/agentRun/agentLookup.ts +1 -7
  234. package/server/handlers/agentRun/autonomousRuntime.ts +11 -10
  235. package/server/handlers/agentRun/builtinAgents.ts +12 -88
  236. package/server/handlers/agentRun/continueDialogContext.ts +15 -3
  237. package/server/handlers/agentRun/{inheritedDialogPrompt.ts → conversationReference.ts} +3 -3
  238. package/server/handlers/agentRun/dialogLookup.ts +4 -3
  239. package/server/handlers/agentRun/index.ts +864 -404
  240. package/server/handlers/agentRun/localRuntimeBootstrap.ts +17 -1
  241. package/server/handlers/agentRun/loop.ts +920 -508
  242. package/server/handlers/agentRun/machineConnector.ts +56 -8
  243. package/server/handlers/agentRun/machineRunAudit.ts +3 -2
  244. package/server/handlers/agentRun/modelUsageToolHandlers.ts +13 -10
  245. package/server/handlers/agentRun/requestContext.ts +12 -8
  246. package/server/handlers/agentRun/runtimeExecutionContext.ts +15 -3
  247. package/server/handlers/agentRun/runtimePreparation.ts +26 -24
  248. package/server/handlers/agentRun/runtimeRouting.ts +25 -40
  249. package/server/handlers/agentRun/runtimeSystemMessages.ts +65 -0
  250. package/server/handlers/agentRun/saveDialog.ts +97 -87
  251. package/server/handlers/agentRun/taskRunToolHandler.ts +9 -7
  252. package/server/handlers/agentRun/toolExecutor.ts +42 -78
  253. package/server/handlers/agentRun/types.ts +2 -1
  254. package/server/handlers/appDeployStatusStore.ts +17 -10
  255. package/server/handlers/appDomainHandler.ts +21 -15
  256. package/server/handlers/appLegacyInfraCleanup.ts +5 -2
  257. package/server/handlers/appSandboxHandler.ts +29 -8
  258. package/server/handlers/appSpaceAccess.ts +15 -8
  259. package/server/handlers/chatHandler.ts +22 -2
  260. package/server/handlers/chatUpstreamRetry.ts +225 -2
  261. package/server/handlers/connector/connectorConnectionRegistry.ts +55 -7
  262. package/server/handlers/connector/connectorWebSocket.ts +14 -10
  263. package/server/handlers/desktopAgentRuntimeAdapter.ts +4 -4
  264. package/server/handlers/desktopAgentRuntimeHostCapabilities.ts +25 -0
  265. package/server/handlers/desktopAgentRuntimeReadiness.ts +62 -0
  266. package/server/handlers/desktopAgentRuntimeStatusHandler.ts +38 -30
  267. package/server/handlers/desktopAgentRuntimeTurnHandler.ts +7 -3
  268. package/server/handlers/desktopAgentRuntimeTurnService.ts +39 -45
  269. package/server/handlers/desktopLlamaRuntimeHandler.ts +85 -51
  270. package/server/handlers/desktopUpdaterHandler.ts +132 -24
  271. package/server/handlers/favoriteHandlers.ts +8 -2
  272. package/server/handlers/getDialogBillingHandler.ts +4 -3
  273. package/server/handlers/getTransactionsHandler.ts +5 -2
  274. package/server/handlers/machines/machineBinding.ts +1 -0
  275. package/server/handlers/machines/machineHandlers.ts +0 -14
  276. package/server/handlers/machines/machineInstallAssets.ts +4 -2
  277. package/server/handlers/notificationEmitter.ts +5 -3
  278. package/server/handlers/notificationsHandler.ts +5 -2
  279. package/server/handlers/providerCallsRecentHandler.ts +234 -0
  280. package/server/handlers/providerReconciliationRunHandler.ts +180 -0
  281. package/server/handlers/providerRequestIdsHandler.ts +105 -0
  282. package/server/handlers/tableAccess.ts +6 -2
  283. package/server/handlers/tableToolHandlers.ts +28 -14
  284. package/server/handlers/userSecretsHandler.ts +17 -6
  285. package/server/handlers/versionHandler.ts +9 -4
  286. package/server/handlers/versionStore.ts +18 -10
  287. package/server/html/ssrPreload.ts +5 -3
  288. package/server/payments/crypto/cryptoUsdcBasePaymentProvider.ts +11 -8
  289. package/server/payments/crypto/cryptoUsdcBaseScanner.ts +12 -7
  290. package/server/payments/crypto/cryptoUsdtTronPaymentProvider.ts +11 -8
  291. package/server/payments/crypto/cryptoUsdtTronScanner.ts +9 -7
  292. package/server/payments/waffo/waffoPaymentProvider.ts +12 -9
  293. package/server/runtimeRole.ts +15 -0
  294. package/server/scheduler/scheduledTaskRuntime.ts +4 -2
  295. package/server/scheduler/schedulerService.ts +2 -2
  296. package/server/taskRunBridgeRoutes.ts +8 -5
  297. package/share/server/fetchCommunityShares.ts +1 -1
  298. package/share/server/helpers.ts +19 -17
  299. package/share/server/importShare.ts +9 -13
  300. package/share/server/listCommunityShares.ts +4 -4
  301. package/share/server/purchaseDoc.ts +8 -16
  302. package/share/server/readCommunityShareCover.ts +3 -2
  303. package/share/server/readSharedTable.ts +14 -13
  304. package/share/server/tableReplication.ts +42 -39
  305. package/spaceAttachCommands.ts +135 -0
  306. package/spaceInternalCommandEntries.ts +2 -0
  307. package/ai/agent/runtimeServerBase.ts +0 -37
  308. package/server/handlers/desktopAgentRuntimeHostFacts.ts +0 -40
@@ -120,8 +120,8 @@ export const buildBuiltinObjectAssistantAgent = (
120
120
  model: FIREWORKS_KIMI_LATEST_MODEL,
121
121
  apiSource: "platform" as const,
122
122
  useServerProxy: true,
123
- inputPrice: 7.6,
124
- outputPrice: 32,
123
+ inputPrice: 0.95 * 8,
124
+ outputPrice: 4 * 8,
125
125
  createdAt: now,
126
126
  updatedAt: now,
127
127
  dialogCount: 0,
@@ -42,7 +42,7 @@ export const useCreateDialog = (): UseCreateDialogResult => {
42
42
  });
43
43
  setIsSuccess(true);
44
44
  } catch (error) {
45
- // 错误处理
45
+ throw error;
46
46
  } finally {
47
47
  setIsLoading(false);
48
48
  }
@@ -1,4 +1,4 @@
1
- import { useState, useCallback } from "react";
1
+ import { useState, useCallback, useEffect, useRef } from "react";
2
2
  import { nanoid } from "nanoid";
3
3
  import { readFileAsDataURL } from "../../app/utils/fileReaders";
4
4
 
@@ -25,6 +25,29 @@ export const useChatInput = (options: UseChatInputOptions = {}) => {
25
25
  const [text, setText] = useState(initialText);
26
26
  const [imageFiles, setImageFiles] = useState<Map<string, File>>(new Map());
27
27
  const [imgPreviews, setImgPreviews] = useState<PendingImagePreview[]>([]);
28
+ const objectUrlByIdRef = useRef<Map<string, string>>(new Map());
29
+
30
+ const revokePreviewUrl = useCallback((id: string) => {
31
+ const url = objectUrlByIdRef.current.get(id);
32
+ if (!url) return;
33
+
34
+ if (typeof URL !== "undefined" && typeof URL.revokeObjectURL === "function") {
35
+ URL.revokeObjectURL(url);
36
+ }
37
+ objectUrlByIdRef.current.delete(id);
38
+ }, []);
39
+
40
+ const revokeAllPreviewUrls = useCallback(() => {
41
+ for (const id of objectUrlByIdRef.current.keys()) {
42
+ revokePreviewUrl(id);
43
+ }
44
+ }, [revokePreviewUrl]);
45
+
46
+ useEffect(() => {
47
+ return () => {
48
+ revokeAllPreviewUrls();
49
+ };
50
+ }, [revokeAllPreviewUrls]);
28
51
 
29
52
  const handleTextChange = useCallback(
30
53
  (newText: string) => {
@@ -54,13 +77,19 @@ export const useChatInput = (options: UseChatInputOptions = {}) => {
54
77
  allowedFiles.forEach((file) => {
55
78
  const id = nanoid();
56
79
 
57
- readFileAsDataURL(file)
58
- .then((url) => {
59
- setImgPreviews((prev) => [...prev, { id, url }]);
60
- })
61
- .catch((err) => {
62
- console.warn("[useChatInput] readFileAsDataURL failed:", err);
63
- });
80
+ if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
81
+ const url = URL.createObjectURL(file);
82
+ objectUrlByIdRef.current.set(id, url);
83
+ setImgPreviews((prev) => [...prev, { id, url }]);
84
+ } else {
85
+ readFileAsDataURL(file)
86
+ .then((url) => {
87
+ setImgPreviews((prev) => [...prev, { id, url }]);
88
+ })
89
+ .catch((err) => {
90
+ console.warn("[useChatInput] readFileAsDataURL failed:", err);
91
+ });
92
+ }
64
93
 
65
94
  setImageFiles((prev) => {
66
95
  const next = new Map(prev);
@@ -73,19 +102,21 @@ export const useChatInput = (options: UseChatInputOptions = {}) => {
73
102
  );
74
103
 
75
104
  const removeImage = useCallback((id: string) => {
105
+ revokePreviewUrl(id);
76
106
  setImgPreviews((prev) => prev.filter((img) => img.id !== id));
77
107
  setImageFiles((prev) => {
78
108
  const next = new Map(prev);
79
109
  next.delete(id);
80
110
  return next;
81
111
  });
82
- }, []);
112
+ }, [revokePreviewUrl]);
83
113
 
84
114
  const clear = useCallback(() => {
115
+ revokeAllPreviewUrls();
85
116
  setText("");
86
117
  setImgPreviews([]);
87
118
  setImageFiles(new Map());
88
- }, []);
119
+ }, [revokeAllPreviewUrls]);
89
120
 
90
121
  return {
91
122
  text,
@@ -788,6 +788,8 @@ export const messageSlice = createSliceWithThunks({
788
788
  ? agentConfig.name.trim()
789
789
  : "";
790
790
  const agentName = rawName || undefined;
791
+ const { imageGenerationState: _transientImageGenerationState, ...persistedMessageMetadata } =
792
+ payload.messageMetadata ?? {};
791
793
 
792
794
  const finalMessage: Message = {
793
795
  id: messageId,
@@ -799,7 +801,7 @@ export const messageSlice = createSliceWithThunks({
799
801
  cybotKey: agentConfig.dbKey,
800
802
  usage: finalUsageData,
801
803
  isStreaming: false,
802
- ...(payload.messageMetadata ?? {}),
804
+ ...persistedMessageMetadata,
803
805
  ...(agentName ? { agentName } : {}),
804
806
  ...(toolCalls && toolCalls.length > 0
805
807
  ? { tool_calls: toolCalls }
@@ -916,6 +918,7 @@ export const messageSlice = createSliceWithThunks({
916
918
  id: action.payload.id,
917
919
  changes: {
918
920
  isStreaming: false,
921
+ imageGenerationState: undefined,
919
922
  content: action.payload.content,
920
923
  thinkContent: action.payload.thinkContent,
921
924
  usage: action.payload.usage,
@@ -980,6 +983,7 @@ export const messageSlice = createSliceWithThunks({
980
983
  id: messageId,
981
984
  changes: {
982
985
  isStreaming: false,
986
+ imageGenerationState: undefined,
983
987
  content: appendSaveFailureToContent(
984
988
  dialogState.msgs.entities[messageId]?.content
985
989
  ),
@@ -75,7 +75,9 @@ export const pendingAttachmentsToMessageParts = (
75
75
  files: PendingAttachmentLike[],
76
76
  args: { currentServer?: string | null }
77
77
  ): PendingAttachmentMessagePart[] =>
78
- files.flatMap((file) => pendingAttachmentToMessageParts(file, args));
78
+ deduplicatePendingAttachments(files).flatMap((file) =>
79
+ pendingAttachmentToMessageParts(file, args)
80
+ );
79
81
 
80
82
  export const resolvePendingAttachmentToMessageParts = async (
81
83
  file: PendingAttachmentLike,
@@ -111,7 +113,31 @@ export const resolvePendingAttachmentsToMessageParts = async (
111
113
  }
112
114
  ): Promise<PendingAttachmentMessagePart[]> => {
113
115
  const nested = await Promise.all(
114
- files.map((file) => resolvePendingAttachmentToMessageParts(file, args))
116
+ deduplicatePendingAttachments(files).map((file) =>
117
+ resolvePendingAttachmentToMessageParts(file, args)
118
+ )
115
119
  );
116
120
  return nested.flat();
117
121
  };
122
+
123
+ const pendingAttachmentIdentity = (file: PendingAttachmentLike): string => {
124
+ return (
125
+ normalizeText(file.pageKey) ||
126
+ normalizeText(file.sourceDialogKey) ||
127
+ normalizeText(file.dialogKey)
128
+ );
129
+ };
130
+
131
+ export const deduplicatePendingAttachments = (
132
+ files: PendingAttachmentLike[]
133
+ ): PendingAttachmentLike[] => {
134
+ const seen = new Set<string>();
135
+ const result: PendingAttachmentLike[] = [];
136
+ for (const file of files) {
137
+ const identity = pendingAttachmentIdentity(file);
138
+ if (identity && seen.has(identity)) continue;
139
+ if (identity) seen.add(identity);
140
+ result.push(file);
141
+ }
142
+ return result;
143
+ };
@@ -4,7 +4,7 @@ import type { AgentRuntimeOptions } from "../../ai/agent/types";
4
4
  import { handleSendMessage } from "../dialog/dialogSlice";
5
5
  import { upload } from "../../database/dbSlice";
6
6
  import { getRuntimeServerContext } from "../../database/runtimeServerContext";
7
- import { waitForFileReady, compressImage, dataURLtoFile } from "../../app/utils/imageUtils";
7
+ import { waitForFileReady, compressImageFile } from "../../app/utils/imageUtils";
8
8
  import { readFileAsDataURL } from "../../app/utils/fileReaders";
9
9
  import type { RootState } from "../../app/store";
10
10
  import { buildMessageFileContentUrl, isLocalFileContentUrl } from "./fileUrl";
@@ -41,6 +41,36 @@ const logQuickChatPerfStage = (
41
41
  });
42
42
  };
43
43
 
44
+ const IMAGE_PREP_CONCURRENCY = 2;
45
+
46
+ const mapWithConcurrency = async <T, R>(
47
+ items: T[],
48
+ concurrency: number,
49
+ mapper: (item: T, index: number) => Promise<R>
50
+ ): Promise<R[]> => {
51
+ if (items.length === 0) return [];
52
+
53
+ const results = new Array<R>(items.length);
54
+ let nextIndex = 0;
55
+ const workerCount = Math.min(concurrency, items.length);
56
+
57
+ const workers = Array.from({ length: workerCount }, async () => {
58
+ while (true) {
59
+ const currentIndex = nextIndex++;
60
+ if (currentIndex >= items.length) return;
61
+ results[currentIndex] = await mapper(items[currentIndex], currentIndex);
62
+ }
63
+ });
64
+
65
+ await Promise.all(workers);
66
+ return results;
67
+ };
68
+
69
+ const buildInlineImagePart = async (file: File): Promise<MessagePart> => ({
70
+ type: "image_url",
71
+ image_url: { url: await readFileAsDataURL(file) },
72
+ });
73
+
44
74
  /**
45
75
  * 发送一条首条消息:
46
76
  * - 支持文字 + 图片 + 文档混合
@@ -84,86 +114,53 @@ export const sendFirstMessage =
84
114
  // 当前文档逻辑是通过 “pendingFiles + addReferenceKeysAction + createPage”等实现的,
85
115
  // 如需首页入口支持文档首条消息,请把那部分逻辑抽成复用函数后在这里调用。
86
116
 
87
- // 2. 处理图片 files:基本复制 MessageInputContainer 中 compress+upload 的逻辑
88
- const uploadedImgs: MessagePart[] = [];
89
-
90
- for (const file of imageFiles) {
91
- // 2.1 先得到本地 dataURL,用于压缩与 fallback
92
- const dataUrl = await readFileAsDataURL(file).catch(() => null);
93
- if (!dataUrl) continue;
94
-
95
- let compressedDataUrl = dataUrl;
96
- let uploadFile: File | null = file;
97
-
98
- try {
99
- compressedDataUrl = await compressImage(dataUrl);
100
- const filename = file.name || `chat-image-${Date.now()}.png`;
101
- const maybeFile = dataURLtoFile(compressedDataUrl, filename);
102
- if (maybeFile) {
103
- uploadFile = maybeFile;
117
+ // 2. 处理图片 files:优先直传压缩后的 File,只有 fallback 时才转 data URL
118
+ const uploadedImgs = await mapWithConcurrency(
119
+ imageFiles,
120
+ IMAGE_PREP_CONCURRENCY,
121
+ async (file): Promise<MessagePart> => {
122
+ const uploadFile = await compressImageFile(file);
123
+
124
+ try {
125
+ const metadata = await dispatch(
126
+ upload({
127
+ file: uploadFile,
128
+ customKey: `chat-image-${Date.now()}`,
129
+ }) as any
130
+ ).unwrap();
131
+ const fileId = metadata?.id as string | undefined;
132
+
133
+ if (!fileId || !currentServer) {
134
+ return buildInlineImagePart(uploadFile);
135
+ }
136
+
137
+ const imageUrl = buildMessageFileContentUrl(currentServer, fileId);
138
+ if (!imageUrl) {
139
+ return buildInlineImagePart(uploadFile);
140
+ }
141
+
142
+ if (isLocalFileContentUrl(imageUrl)) {
143
+ return buildInlineImagePart(uploadFile);
144
+ }
145
+
146
+ const ready = await waitForFileReady(imageUrl);
147
+ if (!ready) {
148
+ return buildInlineImagePart(uploadFile);
149
+ }
150
+
151
+ return {
152
+ type: "image_url",
153
+ image_url: { url: imageUrl },
154
+ };
155
+ } catch (error) {
156
+ console.warn(
157
+ "[sendFirstMessage] image upload failed, falling back to inline data URL",
158
+ error
159
+ );
160
+ return buildInlineImagePart(uploadFile);
104
161
  }
105
- } catch {
106
- // 压缩失败就用原图
107
162
  }
108
-
109
- if (!uploadFile) {
110
- uploadedImgs.push({
111
- type: "image_url",
112
- image_url: { url: compressedDataUrl },
113
- });
114
- continue;
115
- }
116
-
117
- const metadata = await dispatch(
118
- upload({
119
- file: uploadFile,
120
- customKey: `chat-image-${Date.now()}`,
121
- }) as any
122
- ).unwrap();
123
- const fileId = metadata?.id as string | undefined;
124
-
125
- if (!fileId || !currentServer) {
126
- uploadedImgs.push({
127
- type: "image_url",
128
- image_url: { url: compressedDataUrl },
129
- });
130
- continue;
131
- }
132
-
133
- const imageUrl = buildMessageFileContentUrl(currentServer, fileId);
134
- if (!imageUrl) {
135
- uploadedImgs.push({
136
- type: "image_url",
137
- image_url: { url: compressedDataUrl },
138
- });
139
- continue;
140
- }
141
-
142
- const isLocalhost = isLocalFileContentUrl(imageUrl);
143
-
144
- // 如果是 localhost,AI 无法访问,改用 base64
145
- if (isLocalhost) {
146
- uploadedImgs.push({
147
- type: "image_url",
148
- image_url: { url: compressedDataUrl },
149
- });
150
- continue;
151
- }
152
-
153
- const ready = await waitForFileReady(imageUrl);
154
- if (!ready) {
155
- uploadedImgs.push({
156
- type: "image_url",
157
- image_url: { url: compressedDataUrl },
158
- });
159
- continue;
160
- }
161
-
162
- uploadedImgs.push({
163
- type: "image_url",
164
- image_url: { url: imageUrl },
165
- });
166
- }
163
+ );
167
164
 
168
165
  const finalParts: MessagePart[] = [
169
166
  ...parts,
@@ -46,6 +46,14 @@ export interface CompletionUsage {
46
46
 
47
47
  export type Content = string | MessageContentPart[];
48
48
 
49
+ export interface ImageGenerationState {
50
+ kind: "image_generation";
51
+ stage: "submitted" | "generating" | "saving";
52
+ startedAt: number;
53
+ waitHint?: string;
54
+ profileLabel?: string;
55
+ }
56
+
49
57
  // ========== 消息级 UI 选项(Agent 菜单 / 快速回复 等) ==========
50
58
 
51
59
  export type UiOptionSource = "agent_menu" | "llm" | "system" | "tool";
@@ -142,6 +150,7 @@ export interface Message {
142
150
  agentName?: string;
143
151
 
144
152
  isStreaming?: boolean;
153
+ imageGenerationState?: ImageGenerationState;
145
154
  userId?: string;
146
155
  usage?: any;
147
156
 
@@ -0,0 +1,41 @@
1
+ import { markdownToSlate } from "../../../create/editor/transforms/markdownToSlate";
2
+
3
+ type StreamingMarkdownModel =
4
+ | { kind: "plain-text"; content: string }
5
+ | { kind: "structured"; nodes: any[] };
6
+
7
+ function isPlainTextLeaf(node: any): boolean {
8
+ return (
9
+ typeof node?.text === "string" &&
10
+ Object.keys(node).every((key) => key === "text")
11
+ );
12
+ }
13
+
14
+ function isPlainTextParagraph(node: any): boolean {
15
+ return (
16
+ node?.type === "paragraph" &&
17
+ Array.isArray(node.children) &&
18
+ node.children.every(isPlainTextLeaf)
19
+ );
20
+ }
21
+
22
+ export function buildStreamingMarkdownModel(content: string): StreamingMarkdownModel {
23
+ if (!content) {
24
+ return { kind: "plain-text", content };
25
+ }
26
+
27
+ try {
28
+ const nodes = markdownToSlate(content) || [];
29
+ if (nodes.length === 1 && isPlainTextParagraph(nodes[0])) {
30
+ return { kind: "plain-text", content };
31
+ }
32
+
33
+ if (nodes.length > 0) {
34
+ return { kind: "structured", nodes };
35
+ }
36
+ } catch {
37
+ // Fall through to plain-text rendering when markdown parsing fails.
38
+ }
39
+
40
+ return { kind: "plain-text", content };
41
+ }
@@ -0,0 +1,91 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+
3
+ const STREAMING_SOFT_REVEAL_THRESHOLD = 8;
4
+ const STREAMING_SOFT_REVEAL_LEAD_MIN = 12;
5
+ const STREAMING_SOFT_REVEAL_LEAD_MAX = 24;
6
+ const STREAMING_SOFT_REVEAL_LEAD_RATIO = 0.4;
7
+ const STREAMING_SOFT_REVEAL_SETTLE_DELAY_MS = 36;
8
+
9
+ export function splitVisibleCharacters(content: string): string[] {
10
+ const Segmenter = Intl.Segmenter;
11
+ if (typeof Segmenter === "function") {
12
+ const segmenter = new Segmenter(undefined, { granularity: "grapheme" });
13
+ return Array.from(segmenter.segment(content), (part) => part.segment);
14
+ }
15
+ return Array.from(content);
16
+ }
17
+
18
+ function getSoftRevealCharacterCount(currentCount: number, targetCount: number): number {
19
+ const appendedCount = targetCount - currentCount;
20
+ if (appendedCount <= STREAMING_SOFT_REVEAL_THRESHOLD) {
21
+ return targetCount;
22
+ }
23
+
24
+ const leadCount = Math.min(
25
+ STREAMING_SOFT_REVEAL_LEAD_MAX,
26
+ Math.max(
27
+ STREAMING_SOFT_REVEAL_LEAD_MIN,
28
+ Math.ceil(appendedCount * STREAMING_SOFT_REVEAL_LEAD_RATIO)
29
+ )
30
+ );
31
+
32
+ return Math.min(targetCount, currentCount + leadCount);
33
+ }
34
+
35
+ export function useStreamingReveal(content: string): string {
36
+ const [visibleContent, setVisibleContent] = useState(content);
37
+ const visibleContentRef = useRef(content);
38
+ const revealTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
39
+
40
+ useEffect(() => {
41
+ visibleContentRef.current = visibleContent;
42
+ }, [visibleContent]);
43
+
44
+ useEffect(() => {
45
+ if (revealTimerRef.current) {
46
+ clearTimeout(revealTimerRef.current);
47
+ revealTimerRef.current = null;
48
+ }
49
+
50
+ const currentVisible = visibleContentRef.current;
51
+ if (currentVisible === content) return;
52
+
53
+ if (currentVisible && !content.startsWith(currentVisible)) {
54
+ visibleContentRef.current = content;
55
+ setVisibleContent(content);
56
+ return;
57
+ }
58
+
59
+ const targetCharacters = splitVisibleCharacters(content);
60
+ const visibleCharacters = splitVisibleCharacters(currentVisible);
61
+ const nextCount = getSoftRevealCharacterCount(
62
+ visibleCharacters.length,
63
+ targetCharacters.length
64
+ );
65
+
66
+ if (nextCount >= targetCharacters.length) {
67
+ visibleContentRef.current = content;
68
+ setVisibleContent(content);
69
+ return;
70
+ }
71
+
72
+ const partialContent = targetCharacters.slice(0, nextCount).join("");
73
+ visibleContentRef.current = partialContent;
74
+ setVisibleContent(partialContent);
75
+
76
+ revealTimerRef.current = setTimeout(() => {
77
+ revealTimerRef.current = null;
78
+ visibleContentRef.current = content;
79
+ setVisibleContent(content);
80
+ }, STREAMING_SOFT_REVEAL_SETTLE_DELAY_MS);
81
+
82
+ return () => {
83
+ if (revealTimerRef.current) {
84
+ clearTimeout(revealTimerRef.current);
85
+ revealTimerRef.current = null;
86
+ }
87
+ };
88
+ }, [content]);
89
+
90
+ return visibleContent;
91
+ }
@@ -21,12 +21,36 @@ export interface ImageUiConfig {
21
21
  supportedAspectRatios: string[];
22
22
  supportedImageSizes: ("1K" | "2K" | "4K")[];
23
23
  pricePerImage?: number;
24
+ waitHint?: string;
25
+ defaultImageProfileKey?: ImageProfileOption["key"];
26
+ imageProfiles?: ImageProfileOption[];
24
27
  pricingModel?: Pick<
25
28
  Model,
26
29
  "pricePerImage" | "imageTokenPricePerMillion" | "imageOutputTokenEstimateBySize"
27
30
  >;
28
31
  }
29
32
 
33
+ export interface ImageProfileOption {
34
+ key: "speed" | "quality";
35
+ label: string;
36
+ imageModelOverride: string;
37
+ waitHint?: string;
38
+ pricePerImage?: number;
39
+ pricingModel?: Pick<
40
+ Model,
41
+ "pricePerImage" | "imageTokenPricePerMillion" | "imageOutputTokenEstimateBySize"
42
+ >;
43
+ }
44
+
45
+ const formatImageWaitHint = (
46
+ range?: Model["imageGenerationWaitTimeSeconds"]
47
+ ) => {
48
+ if (!range || typeof range.min !== "number" || typeof range.max !== "number") {
49
+ return undefined;
50
+ }
51
+ return `通常需要 ${range.min}-${range.max} 秒`;
52
+ };
53
+
30
54
  export function normalizeFavoriteAgentSummary(
31
55
  agentKey: string,
32
56
  agent: Pick<Agent, "name"> | null | undefined
@@ -95,6 +119,65 @@ function resolveAgentModelIdentity(agent: Partial<Agent>) {
95
119
  return { providerKey, modelConfig };
96
120
  }
97
121
 
122
+ function toPricingModel(
123
+ modelConfig: Model | null | undefined
124
+ ): ImageUiConfig["pricingModel"] | undefined {
125
+ if (!modelConfig) return undefined;
126
+ return {
127
+ pricePerImage: modelConfig.pricePerImage,
128
+ imageTokenPricePerMillion: modelConfig.imageTokenPricePerMillion,
129
+ imageOutputTokenEstimateBySize:
130
+ modelConfig.imageOutputTokenEstimateBySize,
131
+ };
132
+ }
133
+
134
+ function resolveImageProfiles(args: {
135
+ providerKey: string;
136
+ modelConfig: Model | null;
137
+ }): {
138
+ imageProfiles: ImageProfileOption[];
139
+ defaultImageProfileKey?: ImageProfileOption["key"];
140
+ } {
141
+ const { providerKey, modelConfig } = args;
142
+ if (!modelConfig?.imageGenerationProfiles?.length) {
143
+ return { imageProfiles: [] };
144
+ }
145
+
146
+ const imageProfiles = modelConfig.imageGenerationProfiles.flatMap((profile) => {
147
+ let profileModelConfig: Model | null = null;
148
+ try {
149
+ profileModelConfig = getModelConfig(
150
+ providerKey as Provider,
151
+ profile.imageModel
152
+ );
153
+ } catch {
154
+ profileModelConfig = null;
155
+ }
156
+
157
+ const pricingModel = toPricingModel(profileModelConfig);
158
+ return [
159
+ {
160
+ key: profile.key,
161
+ label: profile.label,
162
+ imageModelOverride: profile.imageModel,
163
+ waitHint:
164
+ formatImageWaitHint(profile.waitTimeSeconds) ??
165
+ formatImageWaitHint(profileModelConfig?.imageGenerationWaitTimeSeconds),
166
+ pricingModel,
167
+ pricePerImage:
168
+ getApproxPricePerImage(pricingModel, undefined) ?? undefined,
169
+ },
170
+ ];
171
+ });
172
+
173
+ return {
174
+ imageProfiles,
175
+ defaultImageProfileKey: modelConfig.imageGenerationProfiles.find(
176
+ (profile) => profile.imageModel === modelConfig.name
177
+ )?.key,
178
+ };
179
+ }
180
+
98
181
  export function resolveMessageInputAgentUi(input: {
99
182
  agent: Partial<Agent>;
100
183
  userId: string | null;
@@ -148,6 +231,9 @@ export function resolveMessageInputAgentUi(input: {
148
231
  supportsImageConfig: false,
149
232
  supportedAspectRatios: [],
150
233
  supportedImageSizes: [],
234
+ waitHint: undefined,
235
+ defaultImageProfileKey: undefined,
236
+ imageProfiles: [],
151
237
  pricingModel: undefined,
152
238
  },
153
239
  };
@@ -166,12 +252,11 @@ export function resolveMessageInputAgentUi(input: {
166
252
  (modelConfig.supportedImageSizes as ("1K" | "2K" | "4K")[]) ??
167
253
  (imageMode === "continuous" ? ["1K", "2K"] : ["1K", "2K", "4K"]);
168
254
 
169
- const pricingModel = {
170
- pricePerImage: modelConfig.pricePerImage,
171
- imageTokenPricePerMillion: modelConfig.imageTokenPricePerMillion,
172
- imageOutputTokenEstimateBySize:
173
- modelConfig.imageOutputTokenEstimateBySize,
174
- };
255
+ const pricingModel = toPricingModel(modelConfig);
256
+ const { imageProfiles, defaultImageProfileKey } = resolveImageProfiles({
257
+ providerKey,
258
+ modelConfig,
259
+ });
175
260
 
176
261
  return {
177
262
  switchModelQueryUserId,
@@ -181,6 +266,9 @@ export function resolveMessageInputAgentUi(input: {
181
266
  supportsImageConfig,
182
267
  supportedAspectRatios,
183
268
  supportedImageSizes,
269
+ waitHint: formatImageWaitHint(modelConfig.imageGenerationWaitTimeSeconds),
270
+ defaultImageProfileKey,
271
+ imageProfiles,
184
272
  pricePerImage:
185
273
  getApproxPricePerImage(pricingModel, undefined) ?? undefined,
186
274
  pricingModel,
package/cliEnvHelpers.ts CHANGED
@@ -32,6 +32,14 @@ export function readCliOptionValue(args: string[], flag: string): string {
32
32
  return "";
33
33
  }
34
34
 
35
+ export function hasExplicitServerOverride(args: string[]) {
36
+ return Boolean(
37
+ readCliOptionValue(args, "--server-url") ||
38
+ readCliOptionValue(args, "--server") ||
39
+ readCliOptionValue(args, "--base-url")
40
+ );
41
+ }
42
+
35
43
  export function resolveServerUrlFromEnv(env: EnvLike) {
36
44
  return trimTrailingSlashes(
37
45
  firstDefinedEnvValue(env, ["NOLO_SERVER_URL", "NOLO_SERVER", "BASE_URL"]) ||