Skip to content

Commit 70c211d

Browse files
authored
Merge pull request #58 from linux-do/copilot/fix-54
feat: improve JSONL support for content import
2 parents 9e026ef + 0e9e6a6 commit 70c211d

File tree

3 files changed

+116
-17
lines changed

3 files changed

+116
-17
lines changed

frontend/components/common/project/BulkImportSection.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export function BulkImportSection({
109109
className="text-xs cursor-pointer hover:bg-gray-300"
110110
onClick={() => onFileUploadOpenChange(true)}
111111
>
112-
TXT导入
112+
文件导入
113113
</Badge>
114114
<Badge variant="secondary" className="bg-muted">
115115
{isEditMode ? '待添加' : '已添加'}: {items.length}
@@ -120,7 +120,7 @@ export function BulkImportSection({
120120

121121
<div className="space-y-2">
122122
<Textarea
123-
placeholder={`请输入${placeholderPrefix}分发内容,支持以 逗号分隔(中英文逗号均可)或 每行一个内容 的格式批量导入`}
123+
placeholder={`请输入${placeholderPrefix}分发内容,支持以下格式批量导入:\n• JSON 数组格式:[{}, {}, {}]\n• 每行一个内容\n• 逗号分隔(中英文逗号均可)`}
124124
value={bulkContent}
125125
onChange={(e) => setBulkContent(e.target.value)}
126126
className="h-[100px] break-all overflow-x-auto whitespace-pre-wrap"
@@ -199,9 +199,9 @@ export function BulkImportSection({
199199
<Dialog open={fileUploadOpen} onOpenChange={onFileUploadOpenChange}>
200200
<DialogContent className={`${isMobile ? 'max-w-[90vw] max-h-[80vh]' : 'max-w-lg'}`}>
201201
<DialogHeader>
202-
<DialogTitle>文件导入分发内容</DialogTitle>
202+
<DialogTitle>文件导入</DialogTitle>
203203
<DialogDescription className="text-xs">
204-
支持 .txt 格式• 每行一个邀请码 • 空行自动忽略 • 大小限制:5MB
204+
支持 .txt 和 .jsonl 格式 • 每行一个内容 • 空行自动忽略 • 大小限制:5MB
205205
</DialogDescription>
206206
</DialogHeader>
207207

frontend/components/common/project/constants.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,38 @@ export const getTrustLevelGradient = (trustLevel: number): string => {
6060

6161
/**
6262
* 解析导入的内容文本 - 通用工具函数
63+
* 支持多种格式:
64+
* 1. JSON 数组格式:[{}, {}, {}]
65+
* 2. 每行一个内容
66+
* 3. 逗号分隔格式(向后兼容)
6367
*/
6468
export const parseImportContent = (content: string): string[] => {
65-
let parsed = content.split('\n').filter((item) => item.trim());
69+
const trimmedContent = content.trim();
70+
71+
// 尝试解析为 JSON 数组
72+
if (trimmedContent.startsWith('[') && trimmedContent.endsWith(']')) {
73+
try {
74+
const jsonArray = JSON.parse(trimmedContent);
75+
if (Array.isArray(jsonArray)) {
76+
return jsonArray
77+
.map((item) => {
78+
if (typeof item === 'object' && item !== null) {
79+
return JSON.stringify(item);
80+
}
81+
return String(item);
82+
})
83+
.filter((item) => item.trim())
84+
.map((item) => item.substring(0, FORM_LIMITS.CONTENT_ITEM_MAX_LENGTH));
85+
}
86+
} catch {
87+
// JSON 解析失败,继续使用原有逻辑
88+
}
89+
}
90+
91+
// 原有逻辑:按行分割,如果只有一行则按逗号分割
92+
let parsed = trimmedContent.split('\n').filter((item) => item.trim());
6693
if (parsed.length === 1) {
67-
parsed = content
94+
parsed = trimmedContent
6895
.replace(//g, ',')
6996
.split(',')
7097
.filter((item) => item.trim());

frontend/hooks/use-file-upload.ts

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,62 @@ import {useState, useCallback} from 'react';
44
import {toast} from 'sonner';
55
import {handleBulkImportContentWithFilter} from '@/components/common/project';
66

7+
/**
8+
* 解析 JSONL 文件内容
9+
* 支持两种格式:
10+
* 1. JSON 数组格式:[{}, {}, {}]
11+
* 2. 每行一个 JSON 对象
12+
*/
13+
const parseJsonlContent = (content: string): string => {
14+
const trimmedContent = content.trim();
15+
16+
// 尝试解析为 JSON 数组格式
17+
if (trimmedContent.startsWith('[') && trimmedContent.endsWith(']')) {
18+
try {
19+
const jsonArray = JSON.parse(trimmedContent);
20+
if (Array.isArray(jsonArray)) {
21+
return jsonArray
22+
.map((item) => {
23+
if (typeof item === 'object' && item !== null) {
24+
return JSON.stringify(item);
25+
}
26+
return String(item);
27+
})
28+
.filter((item) => item.trim())
29+
.join('\n');
30+
}
31+
} catch {
32+
// JSON 数组解析失败,继续尝试逐行解析
33+
}
34+
}
35+
36+
// 逐行解析 JSON 对象
37+
const lines = trimmedContent
38+
.split(/\r?\n/)
39+
.map((line) => line.trim())
40+
.filter((line) => line.length > 0);
41+
42+
const validJsonLines: string[] = [];
43+
44+
for (const line of lines) {
45+
try {
46+
// 尝试解析为 JSON 对象
47+
const jsonObj = JSON.parse(line);
48+
if (typeof jsonObj === 'object' && jsonObj !== null) {
49+
validJsonLines.push(JSON.stringify(jsonObj));
50+
} else {
51+
// 非对象类型,直接转为字符串
52+
validJsonLines.push(String(jsonObj));
53+
}
54+
} catch {
55+
// JSON 解析失败,作为普通文本处理
56+
validJsonLines.push(line);
57+
}
58+
}
59+
60+
return validJsonLines.join('\n');
61+
};
62+
763
export function useFileUpload() {
864
const [fileUploadOpen, setFileUploadOpen] = useState(false);
965

@@ -16,10 +72,11 @@ export function useFileUpload() {
1672
if (files.length === 0) return;
1773

1874
const file = files[0];
75+
const fileName = file.name.toLowerCase();
1976

2077
// 检查文件类型
21-
if (!file.name.toLowerCase().endsWith('.txt')) {
22-
toast.error('仅支持上传 .txt 格式的文件');
78+
if (!fileName.endsWith('.txt') && !fileName.endsWith('.jsonl')) {
79+
toast.error('仅支持上传 .txt 或 .jsonl 格式的文件');
2380
return;
2481
}
2582

@@ -33,20 +90,35 @@ export function useFileUpload() {
3390
reader.onload = (e) => {
3491
const content = e.target?.result as string;
3592
if (content) {
36-
// 按行分割并过滤空行
37-
const lines = content
38-
.split(/\r?\n/)
39-
.map((line) => line.trim())
40-
.filter((line) => line.length > 0);
41-
42-
if (lines.length === 0) {
43-
toast.error('文件内容为空');
93+
let processedContent = content;
94+
95+
// 根据文件扩展名选择解析方式
96+
if (fileName.endsWith('.jsonl')) {
97+
// JSONL 文件处理
98+
processedContent = parseJsonlContent(content);
99+
} else {
100+
// TXT 文件处理(保持原有逻辑)
101+
const lines = content
102+
.split(/\r?\n/)
103+
.map((line) => line.trim())
104+
.filter((line) => line.length > 0);
105+
106+
if (lines.length === 0) {
107+
toast.error('文件内容为空');
108+
return;
109+
}
110+
111+
processedContent = lines.join('\n');
112+
}
113+
114+
if (!processedContent) {
115+
toast.error('文件内容为空或格式无效');
44116
return;
45117
}
46118

47119
// 执行导入
48120
handleBulkImportContentWithFilter(
49-
lines.join('\n'),
121+
processedContent,
50122
currentItems,
51123
allowDuplicates,
52124
(updatedItems: string[], importedCount: number, skippedInfo?: string) => {

0 commit comments

Comments
 (0)