Dataset-Combination / script.js
Parham9292's picture
Create script.js
d758cc1 verified
Raw
History Blame Contribute Delete
41.6 kB
"use strict";
/*
============================================================
CONFIG
============================================================
*/
const HF_API =
"https://huggingface.co/api";
let selectedDatasets = new Set();
let localFiles = [];
let generatedTrainBlob = null;
let generatedTrainUrl = null;
let generatedRepoUrl = null;
/*
============================================================
DOM
============================================================
*/
const $ = (id) =>
document.getElementById(id);
const searchInput =
$("searchInput");
const searchButton =
$("searchButton");
const searchStatus =
$("searchStatus");
const datasetResults =
$("datasetResults");
const selectedDatasetsBox =
$("selectedDatasets");
const manualDatasets =
$("manualDatasets");
const fileInput =
$("fileInput");
const fileList =
$("fileList");
const hfToken =
$("hfToken");
const buildButton =
$("buildButton");
const progressSection =
$("progressSection");
const progressBar =
$("progressBar");
const progressPercent =
$("progressPercent");
const progressText =
$("progressText");
const logBox =
$("log");
const resultBox =
$("result");
const resultInfo =
$("resultInfo");
const downloadTrain =
$("downloadTrain");
const repoButton =
$("repoButton");
/*
============================================================
LOGGING
============================================================
*/
function log(message) {
const time =
new Date().toLocaleTimeString();
logBox.textContent +=
`[${time}] ${message}\n`;
logBox.scrollTop =
logBox.scrollHeight;
}
function clearLog() {
logBox.textContent = "";
}
/*
============================================================
PROGRESS
============================================================
*/
function setProgress(
percent,
text
) {
percent =
Math.max(
0,
Math.min(
100,
percent
)
);
progressBar.style.width =
`${percent}%`;
progressPercent.textContent =
`${Math.round(percent)}%`;
progressText.textContent =
text || "";
}
/*
============================================================
HF SEARCH
============================================================
*/
searchButton.addEventListener(
"click",
searchDatasets
);
searchInput.addEventListener(
"keydown",
event => {
if (
event.key === "Enter"
) {
searchDatasets();
}
}
);
async function searchDatasets() {
const query =
searchInput.value.trim();
if (!query) {
searchStatus.textContent =
"عبارت جستجو را وارد کنید.";
return;
}
searchButton.disabled = true;
datasetResults.innerHTML =
"";
searchStatus.textContent =
"در حال جستجو...";
try {
const token =
hfToken.value.trim();
const headers = {};
if (token) {
headers.Authorization =
`Bearer ${token}`;
}
const url =
`${HF_API}/datasets?search=${encodeURIComponent(query)}&limit=30&sort=downloads&direction=-1`;
const response =
await fetch(
url,
{
headers
}
);
if (!response.ok) {
throw new Error(
`HTTP ${response.status}`
);
}
const datasets =
await response.json();
renderDatasetResults(
datasets
);
searchStatus.textContent =
`${datasets.length} دیتاست پیدا شد.`;
} catch (error) {
searchStatus.textContent =
`خطا: ${error.message}`;
} finally {
searchButton.disabled =
false;
}
}
/*
============================================================
RENDER SEARCH RESULTS
============================================================
*/
function renderDatasetResults(
datasets
) {
datasetResults.innerHTML =
"";
for (
const dataset
of datasets
) {
const id =
dataset.id;
if (!id) {
continue;
}
const downloads =
Number(
dataset.downloads || 0
);
const likes =
Number(
dataset.likes || 0
);
const wrapper =
document.createElement(
"label"
);
wrapper.className =
"dataset-item";
const checkbox =
document.createElement(
"input"
);
checkbox.type =
"checkbox";
checkbox.checked =
selectedDatasets.has(
id
);
checkbox.addEventListener(
"change",
() => {
if (
checkbox.checked
) {
selectedDatasets.add(
id
);
} else {
selectedDatasets.delete(
id
);
}
renderSelectedDatasets();
}
);
const info =
document.createElement(
"div"
);
info.className =
"dataset-info";
const name =
document.createElement(
"div"
);
name.className =
"dataset-name";
name.textContent =
id;
const meta =
document.createElement(
"div"
);
meta.className =
"dataset-meta";
meta.textContent =
`${downloads.toLocaleString()} downloads · ${likes.toLocaleString()} likes`;
info.appendChild(
name
);
info.appendChild(
meta
);
wrapper.appendChild(
checkbox
);
wrapper.appendChild(
info
);
datasetResults.appendChild(
wrapper
);
}
}
/*
============================================================
SELECTED DATASETS
============================================================
*/
function renderSelectedDatasets() {
selectedDatasetsBox.innerHTML =
"";
if (
selectedDatasets.size === 0
) {
const empty =
document.createElement(
"span"
);
empty.className =
"muted";
empty.textContent =
"هنوز دیتاستی انتخاب نشده";
selectedDatasetsBox.appendChild(
empty
);
return;
}
for (
const dataset
of selectedDatasets
) {
const chip =
document.createElement(
"span"
);
chip.className =
"chip";
chip.textContent =
dataset;
selectedDatasetsBox.appendChild(
chip
);
}
}
/*
============================================================
LOCAL FILES
============================================================
*/
fileInput.addEventListener(
"change",
() => {
localFiles =
Array.from(
fileInput.files || []
);
renderFiles();
}
);
function renderFiles() {
fileList.innerHTML =
"";
for (
const file
of localFiles
) {
const item =
document.createElement(
"div"
);
item.className =
"file-item";
const size =
formatBytes(
file.size
);
item.textContent =
`${file.name} · ${size}`;
fileList.appendChild(
item
);
}
}
function formatBytes(
bytes
) {
if (
bytes < 1024
) {
return `${bytes} B`;
}
if (
bytes < 1024 * 1024
) {
return (
`${(
bytes /
1024
).toFixed(1)} KB`
);
}
if (
bytes < 1024 * 1024 * 1024
) {
return (
`${(
bytes /
1024 /
1024
).toFixed(1)} MB`
);
}
return (
`${(
bytes /
1024 /
1024 /
1024
).toFixed(2)} GB`
);
}
/*
============================================================
JSON / JSONL PARSER
============================================================
*/
async function parseFile(
file
) {
const text =
await file.text();
const clean =
text
.replace(
/^\uFEFF/,
""
)
.trim();
if (!clean) {
return [];
}
/*
Try normal JSON first.
*/
try {
const obj =
JSON.parse(
clean
);
if (
Array.isArray(obj)
) {
return obj;
}
if (
obj &&
Array.isArray(
obj.data
)
) {
return obj.data;
}
if (
obj &&
typeof obj ===
"object"
) {
return [obj];
}
} catch (_) {
// Continue as JSONL.
}
/*
JSONL
*/
const records = [];
const lines =
clean.split(
/\r?\n/
);
for (
let i = 0;
i < lines.length;
i++
) {
const line =
lines[i].trim();
if (!line) {
continue;
}
try {
const obj =
JSON.parse(
line
);
if (
Array.isArray(obj)
) {
for (
const item
of obj
) {
if (
item &&
typeof item ===
"object"
) {
records.push(
item
);
}
}
} else {
records.push(
obj
);
}
} catch (error) {
throw new Error(
`${file.name}: خطا در خط ${i + 1}`
);
}
}
return records;
}
/*
============================================================
NORMALIZATION
============================================================
*/
function normalizeText(
value
) {
if (
value === null ||
value === undefined
) {
return "";
}
return String(
value
)
.replace(
/ي/g,
"ی"
)
.replace(
/ى/g,
"ی"
)
.replace(
/ك/g,
"ک"
)
.replace(
/\s+/g,
" "
)
.trim();
}
function stringifyValue(
value
) {
if (
typeof value ===
"string"
) {
return value;
}
try {
return JSON.stringify(
value
);
} catch (_) {
return String(
value
);
}
}
function normalizeRecord(
record
) {
if (
!record ||
typeof record !==
"object"
) {
return null;
}
/*
messages
*/
if (
Array.isArray(
record.messages
)
) {
const messages = [];
for (
const message
of record.messages
) {
if (
!message ||
typeof message !==
"object"
) {
continue;
}
const role =
message.role;
const content =
normalizeText(
stringifyValue(
message.content
)
);
if (
role &&
content
) {
messages.push(
{
role:
String(
role
),
content
}
);
}
}
if (
messages.length
) {
return {
messages
};
}
}
/*
instruction / input / output
*/
if (
typeof record.instruction ===
"string" &&
typeof record.output ===
"string"
) {
let instruction =
normalizeText(
record.instruction
);
const input =
normalizeText(
record.input || ""
);
if (input) {
instruction +=
`\n\n${input}`;
}
const output =
normalizeText(
record.output
);
if (
instruction &&
output
) {
return {
messages: [
{
role: "user",
content:
instruction
},
{
role: "assistant",
content:
output
}
]
};
}
}
/*
question / answer
*/
if (
typeof record.question ===
"string" &&
typeof record.answer ===
"string"
) {
const question =
normalizeText(
record.question
);
const answer =
normalizeText(
record.answer
);
if (
question &&
answer
) {
return {
messages: [
{
role: "user",
content:
question
},
{
role: "assistant",
content:
answer
}
]
};
}
}
/*
prompt / response
*/
if (
typeof record.prompt ===
"string" &&
typeof record.response ===
"string"
) {
const prompt =
normalizeText(
record.prompt
);
const response =
normalizeText(
record.response
);
if (
prompt &&
response
) {
return {
messages: [
{
role: "user",
content:
prompt
},
{
role: "assistant",
content:
response
}
]
};
}
}
/*
text
*/
if (
typeof record.text ===
"string"
) {
const text =
normalizeText(
record.text
);
if (text) {
return {
text
};
}
}
/*
content
*/
if (
typeof record.content ===
"string"
) {
const content =
normalizeText(
record.content
);
if (content) {
return {
text: content
};
}
}
/*
Unknown schema:
preserve it.
*/
return record;
}
/*
============================================================
QUALITY
============================================================
*/
function recordText(
record
) {
if (
typeof record.text ===
"string"
) {
return record.text;
}
if (
Array.isArray(
record.messages
)
) {
return record.messages
.map(
message =>
message.content || ""
)
.join(
"\n"
);
}
return JSON.stringify(
record
);
}
function persianRatio(
text
) {
const letters =
Array.from(text)
.filter(
char =>
/\p{L}/u.test(
char
)
);
if (
letters.length === 0
) {
return 0;
}
let persian = 0;
for (
const char
of letters
) {
if (
/[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]/u
.test(char)
) {
persian++;
}
}
return (
persian /
letters.length
);
}
function isSpammy(
text
) {
if (
text.length < 100
) {
return false;
}
const stripped =
text.replace(
/\s/g,
""
);
if (!stripped) {
return true;
}
const counts =
new Map();
for (
const char
of stripped
) {
counts.set(
char,
(
counts.get(
char
) || 0
) + 1
);
}
let maximum = 0;
for (
const count
of counts.values()
) {
maximum =
Math.max(
maximum,
count
);
}
if (
maximum /
stripped.length
>= 0.45
) {
return true;
}
return (
/(.{2,30})\1{5,}/s
.test(text)
);
}
function passesQuality(
record
) {
const minChars =
Number(
$("minChars").value
) || 0;
const maxChars =
Number(
$("maxChars").value
) || 0;
const requiredRatio =
Number(
$("persianRatio").value
) || 0;
const rejectSpam =
$("rejectSpam").checked;
const text =
recordText(
record
).trim();
if (
text.length <
minChars
) {
return false;
}
if (
maxChars > 0 &&
text.length >
maxChars
) {
return false;
}
if (
requiredRatio > 0 &&
persianRatio(text) <
requiredRatio
) {
return false;
}
if (
rejectSpam &&
isSpammy(text)
) {
return false;
}
return true;
}
/*
============================================================
HASH / DEDUP
============================================================
*/
async function hashRecord(
record
) {
const canonical =
JSON.stringify(
record,
Object.keys(record)
.sort()
);
const data =
new TextEncoder()
.encode(
canonical
);
const digest =
await crypto.subtle.digest(
"SHA-256",
data
);
return Array.from(
new Uint8Array(
digest
)
)
.map(
byte =>
byte
.toString(16)
.padStart(
2,
"0"
)
)
.join("");
}
/*
============================================================
DATASET STREAMING
============================================================
*/
async function loadHFDataset(
spec,
token
) {
const parts =
spec.split(":");
const datasetId =
parts[0];
const config =
parts.length >= 2
? parts[1]
: null;
const split =
parts.length >= 3
? parts[2]
: "train";
/*
Resolve dataset files.
*/
const headers = {};
if (token) {
headers.Authorization =
`Bearer ${token}`;
}
/*
Get dataset metadata.
*/
const metadataResponse =
await fetch(
`${HF_API}/datasets/${encodeURIComponent(datasetId)}`,
{
headers
}
);
if (
!metadataResponse.ok
) {
throw new Error(
`${datasetId}: metadata HTTP ${metadataResponse.status}`
);
}
/*
Dataset Server API.
*/
let rowsUrl =
`https://datasets-server.huggingface.co/rows?dataset=${encodeURIComponent(datasetId)}&config=${encodeURIComponent(config || "default")}&split=${encodeURIComponent(split)}&offset=0&length=100`;
const response =
await fetch(
rowsUrl,
{
headers
}
);
if (
!response.ok
) {
throw new Error(
`${datasetId}: Dataset Server HTTP ${response.status}`
);
}
const data =
await response.json();
const rows =
Array.isArray(
data.rows
)
? data.rows
: [];
return rows.map(
row =>
row.row || row
);
}
/*
============================================================
MANUAL DATASET SPECS
============================================================
*/
function getDatasetSpecs() {
const specs =
Array.from(
selectedDatasets
);
const manual =
manualDatasets
.value
.split(/\r?\n/)
.map(
x => x.trim()
)
.filter(Boolean);
for (
const spec
of manual
) {
if (
!specs.includes(
spec
)
) {
specs.push(
spec
);
}
}
return specs;
}
/*
============================================================
WEIGHTS
============================================================
*/
function getWeights() {
const weights = {};
const lines =
$("weights")
.value
.split(/\r?\n/);
for (
const line
of lines
) {
if (
!line.includes("=")
) {
continue;
}
const [
name,
raw
] =
line.split(
"=",
2
);
const value =
Number(
raw.trim()
);
if (
Number.isFinite(value)
) {
weights[
name.trim()
] =
Math.max(
0,
Math.min(
1,
value
)
);
}
}
return weights;
}
/*
============================================================
SHUFFLE
============================================================
*/
function seededRandom(
seed
) {
let value =
Number(seed) || 42;
return function () {
value =
(
value * 1664525
+ 1013904223
) % 4294967296;
return (
value /
4294967296
);
};
}
function shuffleArray(
array,
seed
) {
const random =
seededRandom(
seed
);
for (
let i =
array.length - 1;
i > 0;
i--
) {
const j =
Math.floor(
random()
*
(i + 1)
);
[
array[i],
array[j]
] = [
array[j],
array[i]
];
}
}
/*
============================================================
JSONL
============================================================
*/
function recordsToJSONL(
records
) {
return records
.map(
record =>
JSON.stringify(
record
)
)
.join(
"\n"
)
+ "\n";
}
/*
============================================================
BUILD
============================================================
*/
buildButton.addEventListener(
"click",
buildDataset
);
async function buildDataset() {
const token =
hfToken.value.trim();
const repoName =
$("repoName")
.value
.trim();
if (!token) {
alert(
"Hugging Face Token را وارد کنید."
);
return;
}
if (!repoName) {
alert(
"نام Repository را وارد کنید."
);
return;
}
if (
selectedDatasets.size === 0 &&
localFiles.length === 0 &&
!manualDatasets.value.trim()
) {
alert(
"حداقل یک دیتاست یا فایل اضافه کنید."
);
return;
}
clearLog();
progressSection.classList.remove(
"hidden"
);
resultBox.classList.add(
"hidden"
);
buildButton.disabled =
true;
generatedTrainBlob =
null;
try {
log(
"🚀 شروع پردازش..."
);
const allRecords = [];
const deduplicate =
$("deduplicate").checked;
const maxRecords =
Number(
$("maxRecords").value
) || 0;
const weights =
getWeights();
const specs =
getDatasetSpecs();
const seen =
new Set();
/*
====================================================
LOCAL FILES
====================================================
*/
for (
let i = 0;
i < localFiles.length;
i++
) {
const file =
localFiles[i];
setProgress(
5 +
(
i /
Math.max(
localFiles.length,
1
)
)
* 20,
`خواندن ${file.name}`
);
log(
`📁 ${file.name}`
);
const records =
await parseFile(
file
);
log(
` ${records.length.toLocaleString()} رکورد`
);
for (
const raw
of records
) {
if (
maxRecords > 0 &&
allRecords.length >=
maxRecords
) {
break;
}
const record =
normalizeRecord(
raw
);
if (!record) {
continue;
}
if (
!passesQuality(
record
)
) {
continue;
}
if (
deduplicate
) {
const hash =
await hashRecord(
record
);
if (
seen.has(
hash
)
) {
continue;
}
seen.add(
hash
);
}
allRecords.push(
record
);
}
}
/*
====================================================
HF DATASETS
====================================================
*/
for (
let i = 0;
i < specs.length;
i++
) {
if (
maxRecords > 0 &&
allRecords.length >=
maxRecords
) {
break;
}
const spec =
specs[i];
const datasetId =
spec.split(":")[0];
const weight =
weights[spec] ??
weights[datasetId] ??
1;
if (
weight <= 0
) {
log(
`⏭️ ${spec} — weight=0`
);
continue;
}
setProgress(
25 +
(
i /
Math.max(
specs.length,
1
)
)
* 45,
`دریافت ${spec}`
);
log(
`🔗 ${spec}`
);
try {
const rows =
await loadHFDataset(
spec,
token
);
log(
` دریافت ${rows.length.toLocaleString()} رکورد`
);
const random =
Math.random;
for (
const raw
of rows
) {
if (
maxRecords > 0 &&
allRecords.length >=
maxRecords
) {
break;
}
if (
weight < 1 &&
random() >
weight
) {
continue;
}
const record =
normalizeRecord(
raw
);
if (!record) {
continue;
}
if (
!passesQuality(
record
)
) {
continue;
}
if (
deduplicate
) {
const hash =
await hashRecord(
record
);
if (
seen.has(
hash
)
) {
continue;
}
seen.add(
hash
);
}
allRecords.push(
record
);
}
} catch (error) {
log(
` ❌ ${error.message}`
);
}
}
/*
====================================================
EMPTY
====================================================
*/
if (
allRecords.length === 0
) {
throw new Error(
"هیچ رکورد قابل استفاده‌ای پیدا نشد."
);
}
/*
====================================================
SHUFFLE
====================================================
*/
setProgress(
75,
"Shuffle..."
);
if (
$("shuffle").checked
) {
shuffleArray(
allRecords,
Number(
$("seed").value
) || 42
);
}
/*
====================================================
VALIDATION
====================================================
*/
const validationPercent =
Math.max(
0,
Math.min(
50,
Number(
$("validation").value
) || 0
)
);
const validationCount =
Math.floor(
allRecords.length
*
validationPercent
/
100
);
const validationRecords =
allRecords.splice(
0,
validationCount
);
/*
====================================================
TRAIN JSONL
====================================================
*/
setProgress(
82,
"ساخت train.jsonl..."
);
const trainJSONL =
recordsToJSONL(
allRecords
);
const trainBlob =
new Blob(
[
trainJSONL
],
{
type:
"application/jsonl"
}
);
generatedTrainBlob =
trainBlob;
/*
====================================================
DOWNLOAD URL
====================================================
*/
if (
generatedTrainUrl
) {
URL.revokeObjectURL(
generatedTrainUrl
);
}
generatedTrainUrl =
URL.createObjectURL(
trainBlob
);
downloadTrain.onclick =
() => {
const link =
document.createElement(
"a"
);
link.href =
generatedTrainUrl;
link.download =
"train.jsonl";
link.click();
};
/*
====================================================
UPLOAD
====================================================
*/
setProgress(
88,
"ساخت Repository..."
);
const repo =
normalizeRepoName(
repoName
);
const visibility =
$("visibility").value;
await createRepo(
repo,
visibility,
token
);
log(
`📦 Repository: ${repo}`
);
setProgress(
93,
"آپلود train.jsonl..."
);
await uploadFile(
repo,
"train.jsonl",
trainBlob,
token
);
/*
validation
*/
if (
validationRecords.length
) {
const validationBlob =
new Blob(
[
recordsToJSONL(
validationRecords
)
],
{
type:
"application/jsonl"
}
);
await uploadFile(
repo,
"validation.jsonl",
validationBlob,
token
);
log(
`🧪 validation.jsonl: ${validationRecords.length.toLocaleString()} رکورد`
);
}
/*
====================================================
DONE
====================================================
*/
generatedRepoUrl =
`https://huggingface.co/datasets/${repo}`;
repoButton.onclick =
() => {
window.open(
generatedRepoUrl,
"_blank"
);
};
setProgress(
100,
"تمام شد!"
);
log("");
log(
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
);
log(
"✅ DATASET COMPLETED"
);
log(
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
);
log(
`🚂 Train: ${allRecords.length.toLocaleString()}`
);
log(
`🧪 Validation: ${validationRecords.length.toLocaleString()}`
);
log(
`📦 Size: ${formatBytes(trainBlob.size)}`
);
log(
`🔗 ${generatedRepoUrl}`
);
resultInfo.textContent =
`${allRecords.length.toLocaleString()} رکورد در train.jsonl ساخته شد.`;
resultBox.classList.remove(
"hidden"
);
} catch (error) {
log(
`❌ ${error.message}`
);
alert(
error.message
);
} finally {
buildButton.disabled =
false;
}
}
/*
============================================================
REPOSITORY
============================================================
*/
function normalizeRepoName(
name
) {
name =
name
.replace(
/^https?:\/\/huggingface\.co\/datasets\//,
""
)
.replace(
/^\/+/,
""
)
.trim();
return name;
}
async function createRepo(
repo,
visibility,
token
) {
const response =
await fetch(
`${HF_API}/repos/create`,
{
method:
"POST",
headers: {
Authorization:
`Bearer ${token}`,
"Content-Type":
"application/json"
},
body:
JSON.stringify(
{
name:
repo.split(
"/"
).pop(),
organization:
repo.includes(
"/"
)
? repo.split(
"/"
)[0]
: undefined,
private:
visibility ===
"private",
type:
"dataset"
}
)
}
);
if (
!response.ok &&
response.status !== 409
) {
const text =
await response.text();
throw new Error(
`Repository creation failed: ${response.status} ${text}`
);
}
}
/*
============================================================
UPLOAD FILE
============================================================
*/
async function uploadFile(
repo,
path,
blob,
token
) {
/*
Hugging Face Hub commit API.
*/
const [owner, name] =
repo.split(
"/"
);
if (
!owner ||
!name
) {
throw new Error(
"Repository باید به شکل username/name باشد."
);
}
const form =
new FormData();
form.append(
"file",
blob,
path
);
const response =
await fetch(
`https://huggingface.co/api/datasets/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/upload`,
{
method:
"POST",
headers: {
Authorization:
`Bearer ${token}`
},
body:
form
}
);
if (
!response.ok
) {
const text =
await response.text();
throw new Error(
`Upload failed: ${response.status} ${text}`
);
}
}