@nextclaw/ui 0.10.4 → 0.11.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 +27 -0
- package/dist/assets/{ChannelsList-ohaw9GpD.js → ChannelsList-BqsOYnXz.js} +1 -1
- package/dist/assets/ChatPage-CJBYKR-Y.js +38 -0
- package/dist/assets/{DocBrowser-Cm8LqQ8S.js → DocBrowser-BmL0QXBZ.js} +1 -1
- package/dist/assets/{LogoBadge-GTNIKJO9.js → LogoBadge-C1HiPZPf.js} +1 -1
- package/dist/assets/{MarketplacePage-CfSXSQFi.js → MarketplacePage-BIRP0NRS.js} +1 -1
- package/dist/assets/{McpMarketplacePage-BRJudD8Z.js → McpMarketplacePage-CLHFnNBd.js} +1 -1
- package/dist/assets/{ModelConfig-B-P04UNK.js → ModelConfig-LQSR58tc.js} +1 -1
- package/dist/assets/{ProvidersList-D9IocmDB.js → ProvidersList-CwI-mxah.js} +1 -1
- package/dist/assets/{RemoteAccessPage-58fYdCYK.js → RemoteAccessPage-Cw5BqZb6.js} +1 -1
- package/dist/assets/{RuntimeConfig-C5NlEc57.js → RuntimeConfig-DbowSRAb.js} +1 -1
- package/dist/assets/{SearchConfig-Cw17ED0n.js → SearchConfig-Chzo_JGs.js} +1 -1
- package/dist/assets/{SecretsConfig-DliEIjCS.js → SecretsConfig-CEIbjZYA.js} +2 -2
- package/dist/assets/{SessionsConfig-BRqvuKqq.js → SessionsConfig-BR8GfGWL.js} +1 -1
- package/dist/assets/{chat-message-C6dxqsRj.js → chat-message-CPG7zxRR.js} +1 -1
- package/dist/assets/index-j6A_-1b6.js +8 -0
- package/dist/assets/{label-B3FlNUAA.js → label-GACO2RzW.js} +1 -1
- package/dist/assets/{page-layout-HrT1hpq7.js → page-layout-DjXaK3A3.js} +1 -1
- package/dist/assets/{popover-BEo9XeN-.js → popover-DTaFiTmU.js} +1 -1
- package/dist/assets/{security-config-BRE-9ipr.js → security-config-Dk-yoKvK.js} +1 -1
- package/dist/assets/{skeleton-BeC_fgbu.js → skeleton-Dm2xOBSA.js} +1 -1
- package/dist/assets/{status-dot-D9tZgJWo.js → status-dot-IWEBezqb.js} +1 -1
- package/dist/assets/{switch-Cq7y46-d.js → switch-DCHAJSrA.js} +1 -1
- package/dist/assets/{tabs-custom-xrIDQNF2.js → tabs-custom-DKSbDSB9.js} +1 -1
- package/dist/assets/{useConfirmDialog-DCZbJzHW.js → useConfirmDialog-ByJ8A8n7.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +6 -6
- package/src/api/ncp-attachments.ts +41 -0
- package/src/api/types.ts +13 -0
- package/src/components/chat/adapters/chat-message.adapter.test.ts +66 -0
- package/src/components/chat/adapters/chat-message.adapter.ts +61 -1
- package/src/components/chat/chat-composer-state.test.ts +33 -0
- package/src/components/chat/chat-composer-state.ts +3 -1
- package/src/components/chat/containers/chat-input-bar.container.tsx +9 -8
- package/src/components/chat/ncp/ncp-chat-input.manager.ts +3 -1
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +3 -1
- package/src/components/chat/ncp/ncp-session-adapter.ts +15 -1
- package/src/lib/i18n.chat.ts +7 -7
- package/dist/assets/ChatPage-BqFTFaut.js +0 -38
- package/dist/assets/index-B3caHNCU.js +0 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e}from"./vendor-CNhxtHCf.js";import{as as m,c as s}from"./index-
|
|
1
|
+
import{j as e}from"./vendor-CNhxtHCf.js";import{as as m,c as s}from"./index-j6A_-1b6.js";function c({tabs:a,activeTab:i,onChange:o,className:n}){return e.jsx("div",{className:s("flex items-center gap-6 border-b border-gray-200/60 mb-6",n),children:a.map(t=>{const r=i===t.id;return e.jsxs("button",{onClick:()=>o(t.id),className:s("relative pb-3 text-[14px] font-medium transition-all duration-fast flex items-center gap-1.5",r?"text-gray-900":"text-gray-600 hover:text-gray-900"),children:[t.label,t.count!==void 0&&e.jsx("span",{className:s("text-[11px] font-medium","text-gray-500"),children:m(t.count)}),r&&e.jsx("div",{className:"absolute bottom-0 left-0 right-0 h-[2px] bg-primary rounded-full"})]},t.id)})})}export{c as T};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as a,r as t}from"./vendor-CNhxtHCf.js";import{am as p,an as C,ao as h,ap as x,aq as g,ar as D,B as d,t as i}from"./index-
|
|
1
|
+
import{j as a,r as t}from"./vendor-CNhxtHCf.js";import{am as p,an as C,ao as h,ap as x,aq as g,ar as D,B as d,t as i}from"./index-j6A_-1b6.js";const j=({open:l,onOpenChange:r,title:c,description:o,confirmLabel:s=i("confirm"),cancelLabel:e=i("cancel"),variant:n="default",onConfirm:u,onCancel:f})=>{const m=()=>{u(),r(!1)},v=()=>{f(),r(!1)};return a.jsx(p,{open:l,onOpenChange:r,children:a.jsxs(C,{className:"[&>:last-child]:hidden",onCloseAutoFocus:b=>b.preventDefault(),children:[a.jsxs(h,{children:[a.jsx(x,{children:c}),o?a.jsx(g,{children:o}):null]}),a.jsxs(D,{className:"gap-2 sm:gap-0",children:[a.jsx(d,{type:"button",variant:"outline",onClick:v,children:e}),a.jsx(d,{type:"button",variant:n==="destructive"?"destructive":"default",onClick:m,children:s})]})]})})},L={open:!1,title:"",description:"",confirmLabel:i("confirm"),cancelLabel:i("cancel"),variant:"default",resolve:null};function y(){const[l,r]=t.useState(L),c=t.useCallback(e=>new Promise(n=>{r({open:!0,title:e.title,description:e.description??"",confirmLabel:e.confirmLabel??i("confirm"),cancelLabel:e.cancelLabel??i("cancel"),variant:e.variant??"default",resolve:u=>{n(u),r(f=>({...f,open:!1,resolve:null}))}})}),[]),o=t.useCallback(e=>{r(n=>(!e&&n.resolve&&n.resolve(!1),{...n,open:e,resolve:e?n.resolve:null}))},[]),s=t.useCallback(()=>a.jsx(j,{open:l.open,onOpenChange:o,title:l.title,description:l.description||void 0,confirmLabel:l.confirmLabel,cancelLabel:l.cancelLabel,variant:l.variant,onConfirm:()=>{var e;return(e=l.resolve)==null?void 0:e.call(l,!0)},onCancel:()=>{var e;return(e=l.resolve)==null?void 0:e.call(l,!1)}}),[l,o]);return{confirm:c,ConfirmDialog:s}}export{y as u};
|
package/dist/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-j6A_-1b6.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-CNhxtHCf.js">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-kaPUhd-8.css">
|
|
12
12
|
</head>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"tailwind-merge": "^2.5.4",
|
|
29
29
|
"zod": "^3.23.8",
|
|
30
30
|
"zustand": "^5.0.2",
|
|
31
|
-
"@nextclaw/ncp
|
|
32
|
-
"@nextclaw/ncp": "0.
|
|
33
|
-
"@nextclaw/ncp-http-agent-client": "0.3.
|
|
34
|
-
"@nextclaw/agent-chat-ui": "0.2.
|
|
35
|
-
"@nextclaw/agent-chat": "0.1.
|
|
31
|
+
"@nextclaw/ncp": "0.4.0",
|
|
32
|
+
"@nextclaw/ncp-react": "0.4.0",
|
|
33
|
+
"@nextclaw/ncp-http-agent-client": "0.3.4",
|
|
34
|
+
"@nextclaw/agent-chat-ui": "0.2.5",
|
|
35
|
+
"@nextclaw/agent-chat": "0.1.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@testing-library/react": "^16.3.0",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { NcpDraftAttachment } from "@nextclaw/ncp-react";
|
|
2
|
+
import { API_BASE } from "./api-base";
|
|
3
|
+
import type { ApiResponse, NcpAssetPutView } from "./types";
|
|
4
|
+
|
|
5
|
+
function readErrorMessage(payload: unknown, fallback: string): string {
|
|
6
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
7
|
+
return fallback;
|
|
8
|
+
}
|
|
9
|
+
const error = (payload as { error?: unknown }).error;
|
|
10
|
+
if (!error || typeof error !== "object" || Array.isArray(error)) {
|
|
11
|
+
return fallback;
|
|
12
|
+
}
|
|
13
|
+
const message = (error as { message?: unknown }).message;
|
|
14
|
+
return typeof message === "string" && message.trim().length > 0 ? message : fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function uploadNcpAssets(files: File[]): Promise<NcpDraftAttachment[]> {
|
|
18
|
+
const formData = new FormData();
|
|
19
|
+
for (const file of files) {
|
|
20
|
+
formData.append("files", file);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const response = await fetch(`${API_BASE}/api/ncp/assets`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: formData,
|
|
26
|
+
credentials: "include",
|
|
27
|
+
});
|
|
28
|
+
const payload = (await response.json()) as ApiResponse<NcpAssetPutView>;
|
|
29
|
+
if (!response.ok || !payload.ok) {
|
|
30
|
+
throw new Error(readErrorMessage(payload, "Failed to put assets."));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return payload.data.assets.map((asset) => ({
|
|
34
|
+
id: asset.id,
|
|
35
|
+
name: asset.name,
|
|
36
|
+
mimeType: asset.mimeType,
|
|
37
|
+
sizeBytes: asset.sizeBytes,
|
|
38
|
+
assetUri: asset.assetUri,
|
|
39
|
+
url: asset.url,
|
|
40
|
+
}));
|
|
41
|
+
}
|
package/src/api/types.ts
CHANGED
|
@@ -259,6 +259,19 @@ export type NcpSessionMessagesView = {
|
|
|
259
259
|
total: number;
|
|
260
260
|
};
|
|
261
261
|
|
|
262
|
+
export type NcpAssetView = {
|
|
263
|
+
id: string;
|
|
264
|
+
name: string;
|
|
265
|
+
mimeType: string;
|
|
266
|
+
sizeBytes: number;
|
|
267
|
+
assetUri: string;
|
|
268
|
+
url: string;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export type NcpAssetPutView = {
|
|
272
|
+
assets: NcpAssetView[];
|
|
273
|
+
};
|
|
274
|
+
|
|
262
275
|
export type NcpSessionStatusView = NcpSessionStatus;
|
|
263
276
|
|
|
264
277
|
export type SessionPatchUpdate = {
|
|
@@ -164,3 +164,69 @@ it("maps file parts into previewable attachment view models", () => {
|
|
|
164
164
|
},
|
|
165
165
|
});
|
|
166
166
|
});
|
|
167
|
+
|
|
168
|
+
it("keeps named non-image files as downloadable attachments", () => {
|
|
169
|
+
const adapted = adapt([
|
|
170
|
+
{
|
|
171
|
+
id: "assistant-doc",
|
|
172
|
+
role: "assistant",
|
|
173
|
+
parts: [
|
|
174
|
+
{
|
|
175
|
+
type: "file",
|
|
176
|
+
name: "spec.pdf",
|
|
177
|
+
mimeType: "application/pdf",
|
|
178
|
+
data: "cGRm",
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
] as unknown as ChatMessageSource[]);
|
|
183
|
+
|
|
184
|
+
expect(adapted[0]?.parts[0]).toEqual({
|
|
185
|
+
type: "file",
|
|
186
|
+
file: {
|
|
187
|
+
label: "spec.pdf",
|
|
188
|
+
mimeType: "application/pdf",
|
|
189
|
+
dataUrl: "data:application/pdf;base64,cGRm",
|
|
190
|
+
isImage: false,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("renders asset tool results as previewable files", () => {
|
|
196
|
+
const adapted = adapt([
|
|
197
|
+
{
|
|
198
|
+
id: "assistant-asset",
|
|
199
|
+
role: "assistant",
|
|
200
|
+
parts: [
|
|
201
|
+
{
|
|
202
|
+
type: "tool-invocation",
|
|
203
|
+
toolInvocation: {
|
|
204
|
+
status: ToolInvocationStatus.RESULT,
|
|
205
|
+
toolCallId: "call-asset-1",
|
|
206
|
+
toolName: "asset_put",
|
|
207
|
+
args: { path: "/tmp/output.png" },
|
|
208
|
+
result: {
|
|
209
|
+
ok: true,
|
|
210
|
+
asset: {
|
|
211
|
+
uri: "asset://store/2026/03/27/asset_1",
|
|
212
|
+
name: "output.png",
|
|
213
|
+
mimeType: "image/png",
|
|
214
|
+
url: "/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2F2026%2F03%2F27%2Fasset_1",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
] as unknown as ChatMessageSource[]);
|
|
222
|
+
|
|
223
|
+
expect(adapted[0]?.parts[0]).toEqual({
|
|
224
|
+
type: "file",
|
|
225
|
+
file: {
|
|
226
|
+
label: "output.png",
|
|
227
|
+
mimeType: "image/png",
|
|
228
|
+
dataUrl: "/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2F2026%2F03%2F27%2Fasset_1",
|
|
229
|
+
isImage: true,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -18,6 +18,7 @@ export type ChatMessagePartSource =
|
|
|
18
18
|
type: 'file';
|
|
19
19
|
mimeType: string;
|
|
20
20
|
data: string;
|
|
21
|
+
url?: string;
|
|
21
22
|
name?: string;
|
|
22
23
|
}
|
|
23
24
|
| {
|
|
@@ -75,6 +76,58 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
75
76
|
return typeof value === 'object' && value !== null;
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
function readOptionalString(value: unknown): string | null {
|
|
80
|
+
if (typeof value !== 'string') {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const trimmed = value.trim();
|
|
84
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function extractAssetFileView(
|
|
88
|
+
value: unknown,
|
|
89
|
+
texts: ChatMessageAdapterTexts
|
|
90
|
+
):
|
|
91
|
+
| {
|
|
92
|
+
type: 'file';
|
|
93
|
+
file: {
|
|
94
|
+
label: string;
|
|
95
|
+
mimeType: string;
|
|
96
|
+
dataUrl: string;
|
|
97
|
+
isImage: boolean;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
| null {
|
|
101
|
+
if (!isRecord(value)) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const assetCandidate = isRecord(value.asset)
|
|
105
|
+
? value.asset
|
|
106
|
+
: Array.isArray(value.assets) && value.assets.length > 0 && isRecord(value.assets[0])
|
|
107
|
+
? value.assets[0]
|
|
108
|
+
: null;
|
|
109
|
+
if (!assetCandidate) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const url = readOptionalString(assetCandidate.url);
|
|
113
|
+
const mimeType = readOptionalString(assetCandidate.mimeType) ?? 'application/octet-stream';
|
|
114
|
+
if (!url) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const label =
|
|
118
|
+
readOptionalString(assetCandidate.name) ??
|
|
119
|
+
(mimeType.startsWith('image/') ? texts.imageAttachmentLabel : texts.fileAttachmentLabel);
|
|
120
|
+
return {
|
|
121
|
+
type: 'file',
|
|
122
|
+
file: {
|
|
123
|
+
label,
|
|
124
|
+
mimeType,
|
|
125
|
+
dataUrl: url,
|
|
126
|
+
isImage: mimeType.startsWith('image/')
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
78
131
|
function isTextPart(part: ChatMessagePartSource): part is Extract<ChatMessagePartSource, { type: 'text' }> {
|
|
79
132
|
return part.type === 'text' && typeof part.text === 'string';
|
|
80
133
|
}
|
|
@@ -212,13 +265,20 @@ export function adaptChatMessages(params: {
|
|
|
212
265
|
? params.texts.imageAttachmentLabel
|
|
213
266
|
: params.texts.fileAttachmentLabel,
|
|
214
267
|
mimeType: part.mimeType,
|
|
215
|
-
dataUrl:
|
|
268
|
+
dataUrl:
|
|
269
|
+
typeof part.url === 'string' && part.url.trim().length > 0
|
|
270
|
+
? part.url.trim()
|
|
271
|
+
: `data:${part.mimeType};base64,${part.data}`,
|
|
216
272
|
isImage
|
|
217
273
|
}
|
|
218
274
|
};
|
|
219
275
|
}
|
|
220
276
|
if (isToolInvocationPart(part)) {
|
|
221
277
|
const invocation = part.toolInvocation;
|
|
278
|
+
const assetFileView = extractAssetFileView(invocation.result, params.texts);
|
|
279
|
+
if (assetFileView) {
|
|
280
|
+
return assetFileView;
|
|
281
|
+
}
|
|
222
282
|
const detail = summarizeToolArgs(invocation.parsedArgs ?? invocation.args);
|
|
223
283
|
const rawResult =
|
|
224
284
|
typeof invocation.error === 'string' && invocation.error.trim()
|
|
@@ -71,4 +71,37 @@ describe('deriveNcpMessagePartsFromComposer', () => {
|
|
|
71
71
|
}
|
|
72
72
|
]);
|
|
73
73
|
});
|
|
74
|
+
|
|
75
|
+
it('preserves uploaded attachment references when the attachment has a server uri', () => {
|
|
76
|
+
const parts = deriveNcpMessagePartsFromComposer(
|
|
77
|
+
[
|
|
78
|
+
createChatComposerTokenNode({
|
|
79
|
+
tokenKind: 'file',
|
|
80
|
+
tokenKey: 'config',
|
|
81
|
+
label: 'config.json'
|
|
82
|
+
})
|
|
83
|
+
],
|
|
84
|
+
[
|
|
85
|
+
{
|
|
86
|
+
id: 'config',
|
|
87
|
+
name: 'config.json',
|
|
88
|
+
mimeType: 'application/json',
|
|
89
|
+
sizeBytes: 18,
|
|
90
|
+
assetUri: 'asset://store/2026/03/26/asset_123',
|
|
91
|
+
url: '/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2F2026%2F03%2F26%2Fasset_123'
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(parts).toEqual([
|
|
97
|
+
{
|
|
98
|
+
type: 'file',
|
|
99
|
+
name: 'config.json',
|
|
100
|
+
mimeType: 'application/json',
|
|
101
|
+
assetUri: 'asset://store/2026/03/26/asset_123',
|
|
102
|
+
url: '/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2F2026%2F03%2F26%2Fasset_123',
|
|
103
|
+
sizeBytes: 18
|
|
104
|
+
}
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
74
107
|
});
|
|
@@ -108,7 +108,9 @@ export function deriveNcpMessagePartsFromComposer(
|
|
|
108
108
|
type: 'file',
|
|
109
109
|
name: attachment.name,
|
|
110
110
|
mimeType: attachment.mimeType,
|
|
111
|
-
|
|
111
|
+
...(attachment.assetUri ? { assetUri: attachment.assetUri } : {}),
|
|
112
|
+
...(attachment.url ? { url: attachment.url } : {}),
|
|
113
|
+
...(attachment.contentBase64 ? { contentBase64: attachment.contentBase64 } : {}),
|
|
112
114
|
sizeBytes: attachment.sizeBytes
|
|
113
115
|
});
|
|
114
116
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { ChatInputBar, type ChatInputBarHandle } from '@nextclaw/agent-chat-ui';
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
readFilesAsNcpDraftAttachments
|
|
4
|
+
DEFAULT_NCP_ATTACHMENT_MAX_BYTES,
|
|
5
|
+
uploadFilesAsNcpDraftAttachments
|
|
7
6
|
} from '@nextclaw/ncp-react';
|
|
7
|
+
import { uploadNcpAssets } from '@/api/ncp-attachments';
|
|
8
8
|
import {
|
|
9
9
|
buildChatSlashItems,
|
|
10
10
|
buildModelStateHint,
|
|
@@ -133,23 +133,25 @@ export function ChatInputBarContainer() {
|
|
|
133
133
|
|
|
134
134
|
const showAttachmentError = useCallback((reason: 'unsupported-type' | 'too-large' | 'read-failed') => {
|
|
135
135
|
if (reason === 'unsupported-type') {
|
|
136
|
-
toast.error(t('
|
|
136
|
+
toast.error(t('chatInputAttachmentUnsupported'));
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
if (reason === 'too-large') {
|
|
140
140
|
toast.error(
|
|
141
|
-
t('
|
|
141
|
+
t('chatInputAttachmentTooLarge').replace('{maxMb}', String(DEFAULT_NCP_ATTACHMENT_MAX_BYTES / (1024 * 1024)))
|
|
142
142
|
);
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
|
-
toast.error(t('
|
|
145
|
+
toast.error(t('chatInputAttachmentReadFailed'));
|
|
146
146
|
}, []);
|
|
147
147
|
|
|
148
148
|
const handleFilesAdd = useCallback(async (files: File[]) => {
|
|
149
149
|
if (!attachmentSupported || files.length === 0) {
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
|
-
const result = await
|
|
152
|
+
const result = await uploadFilesAsNcpDraftAttachments(files, {
|
|
153
|
+
uploadBatch: uploadNcpAssets,
|
|
154
|
+
});
|
|
153
155
|
if (result.attachments.length > 0) {
|
|
154
156
|
const insertedAttachments = presenter.chatInputManager.addAttachments?.(result.attachments) ?? [];
|
|
155
157
|
if (insertedAttachments.length > 0) {
|
|
@@ -275,7 +277,6 @@ export function ChatInputBarContainer() {
|
|
|
275
277
|
<input
|
|
276
278
|
ref={fileInputRef}
|
|
277
279
|
type="file"
|
|
278
|
-
accept={DEFAULT_NCP_IMAGE_ATTACHMENT_ACCEPT}
|
|
279
280
|
multiple
|
|
280
281
|
className="hidden"
|
|
281
282
|
onChange={async (event) => {
|
|
@@ -27,10 +27,12 @@ export class NcpChatInputManager {
|
|
|
27
27
|
|
|
28
28
|
private buildAttachmentSignature = (attachment: NcpDraftAttachment): string =>
|
|
29
29
|
[
|
|
30
|
+
attachment.assetUri ?? '',
|
|
31
|
+
attachment.url ?? '',
|
|
30
32
|
attachment.name,
|
|
31
33
|
attachment.mimeType,
|
|
32
34
|
String(attachment.sizeBytes),
|
|
33
|
-
attachment.contentBase64,
|
|
35
|
+
attachment.contentBase64 ?? '',
|
|
34
36
|
].join(':');
|
|
35
37
|
|
|
36
38
|
constructor(
|
|
@@ -121,8 +121,22 @@ function toUiParts(parts: NcpMessagePart[]): UIMessage['parts'] {
|
|
|
121
121
|
if (part.type === 'file' && part.contentBase64) {
|
|
122
122
|
uiParts.push({
|
|
123
123
|
type: 'file',
|
|
124
|
+
...(part.name ? { name: part.name } : {}),
|
|
124
125
|
mimeType: part.mimeType ?? 'application/octet-stream',
|
|
125
|
-
data: part.contentBase64
|
|
126
|
+
data: part.contentBase64,
|
|
127
|
+
...(part.url ? { url: part.url } : {}),
|
|
128
|
+
...(typeof part.sizeBytes === 'number' ? { sizeBytes: part.sizeBytes } : {})
|
|
129
|
+
});
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (part.type === 'file' && part.url) {
|
|
133
|
+
uiParts.push({
|
|
134
|
+
type: 'file',
|
|
135
|
+
...(part.name ? { name: part.name } : {}),
|
|
136
|
+
mimeType: part.mimeType ?? 'application/octet-stream',
|
|
137
|
+
data: '',
|
|
138
|
+
url: part.url,
|
|
139
|
+
...(typeof part.sizeBytes === 'number' ? { sizeBytes: part.sizeBytes } : {})
|
|
126
140
|
});
|
|
127
141
|
continue;
|
|
128
142
|
}
|
package/src/lib/i18n.chat.ts
CHANGED
|
@@ -104,14 +104,14 @@ export const CHAT_LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
104
104
|
chatSkillsPickerOfficial: { zh: '官方', en: 'Official' },
|
|
105
105
|
chatSkillsPickerManage: { zh: '管理技能', en: 'Manage Skills' },
|
|
106
106
|
chatInputAttach: { zh: '添加附件', en: 'Attach file' },
|
|
107
|
-
|
|
108
|
-
zh: '
|
|
109
|
-
en: '
|
|
107
|
+
chatInputAttachmentUnsupported: {
|
|
108
|
+
zh: '当前上传流程不支持该文件类型。',
|
|
109
|
+
en: 'This file type is not supported in the current upload flow.'
|
|
110
110
|
},
|
|
111
|
-
|
|
112
|
-
zh: '
|
|
113
|
-
en: '
|
|
111
|
+
chatInputAttachmentTooLarge: {
|
|
112
|
+
zh: '文件不能超过 {maxMb} MB。',
|
|
113
|
+
en: 'Files must be {maxMb} MB or smaller.'
|
|
114
114
|
},
|
|
115
|
-
|
|
115
|
+
chatInputAttachmentReadFailed: { zh: '读取文件失败,请重试。', en: 'Failed to read the file. Please try again.' },
|
|
116
116
|
chatInputAttachComingSoon: { zh: '即将支持', en: 'Coming soon' }
|
|
117
117
|
};
|