Files
2025-12-02 12:39:54 -07:00

187 lines
5.9 KiB
JavaScript

import fse from 'fs-extra'
/**
* Downloads the most recent ModelZoo file for the model type from the community repo
* And parses the model Specifications into JS
* It is expected that some Specifications will not parse properly (because they aren't models)
* Also, this is potentially dangerious because it uses eval
* @param {"Model" | "ControlNet" | "LoRA" | "TextualInversion"} name
* @returns
*/
export async function compileOfficialModels(name) {
const res = await fetch(
`https://api.github.com/repos/drawthingsai/draw-things-community/contents/Libraries/ModelZoo/Sources/${name}Zoo.swift`
)
const src = Buffer.from((await res.json()).content, "base64").toString(
"utf-8"
)
const specs = extractSpecifications(src)
const parsed = await specs.map(s => toJS(s)).filter(s => !!s)
// const extract = specs.map(s => extractData(s)).filter(s => !!s).map(s => ({ ...s, official: true }))
// return JSON.stringify(parsed, null, 2)
// await fse.writeJSON(`./web/models/official.json`, extract, { spaces: 2 })
return parsed
}
/**
* Since the swift objects are mostly compatible with JS code, we just touch up or remove
* any of the parts that aren't compatible (i'm not familiar with swift but they seem to be
* enums or function calls).
*
* uses eval()
* @param {string} spec
* @returns
*/
function toJS(spec) {
const json = spec
.replace(/^\s+/gm, "")
.replace(/^Specification\($/gm, "{")
.replace(/^\)$/gm, "}")
.replace(/^version: \.(\w+)(,?)$/gm, '"version": "$1"$2')
// .replace(/^objective: \.(\w+)\((\w+): (\w+)\)(,?)$/gm, (m, p1, p2, p3, p4) => {
// return `"objective": { "${p1}": { "${p2}": ${p3} } }${p4}`
// })
.replace(/^modifier: \.(\w+)(,?)$/gm, '"modifier": "$1"$2')
.replace(/^type: \.(\w+)(,?)$/gm, '"type": "$1"$2')
.replace(/^alternativeDecoderVersion: \.(\w+)(,?)$/gm, '"alternativeDecoderVersion": "$1"$2')
.replace(/^mmdit.*$/gm, "")
.replace(/^conditioning.*$/gm, "")
.replace(/^objective.*$/gm, "")
.replace(/^noiseDiscretization.*$/gm, "")
.replace(/^weight:.*$/gm, "")
.replace(/: nil/gm, ": null")
try {
const obj = eval(`Object(${json})`)
return obj
// return JSON.parse(json)
} catch (e) {
if (e?.message?.includes("Unexpected identifier 'specification'")) return null
console.log(e)
return null
}
}
let specI = 0
function extractSpecifications(input) {
const text = input.replace(/^\s+/gm, "").replace(/\n/g, " ")
const brackets = { "(": ")", "[": "]", "{": "}" }
const specs = []
let i = 0
while (true) {
const start = text.indexOf("Specification(", i)
if (start === -1) break
let bstack = []
let inQuote = false
let escaped = false
let j = text.indexOf("(", start)
// j++ // skip first '('
let output = 'Specification'
const startIdx = j
for (; j < text.length; j++) {
const ch = text[j]
output += ch
if (inQuote) {
if (escaped) escaped = false
else if (ch === "\\") escaped = true
else if (ch === '"') inQuote = false
} else {
if (ch === '"') inQuote = true
else if (brackets[ch]) bstack.push(brackets[ch])
else if (ch === bstack.at(-1)) {
bstack.pop()
if (bstack.length === 0) break
}
else if (ch === ',' && bstack.length === 1) output += '\n'
if (ch === "(" && bstack.length === 1) output += "\n"
}
}
const block = output.slice(0, -1) + "\n)"
specs.push(block)
i = j + 1
}
return specs
}
function extractData(spec) {
const data = {
name: extractValue(spec, "name"),
file: extractValue(spec, "file"),
version: versionMap(extractDotValue(spec, "version")),
modifier: extractDotValue(spec, "modifier") ?? undefined
}
if (data.name && data.file && data.version) return data
console.error("couldn't extract data from", spec)
return undefined
}
/** @param spec {string} */
function extractValue(spec, key) {
if (key === "name")
return spec.match(/\bname:\s+?"([^"]+)"/)?.[1]
if (key === "file")
return spec.match(/\bfile:\s+?"([^"]+)"/)?.[1]
}
/** @param spec {string} */
function extractDotValue(spec, key) {
if (key === "version")
return spec.match(/\bversion:\s+?\.(\w+)/)?.[1]
if (key === "modifier")
return spec.match(/\bmodifier:\s+?\.(\w+)/)?.[1]
}
export function versionMap(version) {
switch (version) {
case "v1":
return "v1"
case "v2":
return "v2"
case "kandinsky21":
return "kandinsky2.1"
case "sdxlBase":
return "sdxl_base_v0.9"
case "sdxlRefiner":
return "sdxl_refiner_v0.9"
case "ssd1b":
return "ssd_1b"
case "svdI2v":
return "svd_i2v"
case "wurstchenStageC":
return "wurstchen_v3.0_stage_c"
case "wurstchenStageB":
return "wurstchen_v3.0_stage_b"
case "sd3":
return "sd3"
case "pixart":
return "pixart"
case "auraflow":
return "auraflow"
case "flux1":
return "flux1"
case "sd3Large":
return "sd3_large"
case "hunyuanVideo":
return "hunyuan_video"
case "wan21_1_3b":
return "wan_v2.1_1.3b"
case "wan21_14b":
return "wan_v2.1_14b"
case "hiDreamI1":
return "hidream_i1"
case "qwenImage":
return "qwen_image"
case "wan22_5b":
return "wan_v2.2_5b"
default:
return version
}
}