Spaces:
Running
Running
| // core/schema.js — lean, dependency-free validators ("zod-lite") for the LibreChat-faithful | |
| // Conversation / Message / Preset shapes. Field names mirror LibreChat's data model (packages/ | |
| // data-schemas) so an exported conversation interoperates; values are validated/normalised, not | |
| // hashed (the κ-object store is what makes them content-addressed). No build step, no dep. | |
| const isStr = (v) => typeof v === "string"; | |
| const isBool = (v) => typeof v === "boolean"; | |
| const isNum = (v) => typeof v === "number" && !Number.isNaN(v); | |
| const arr = (v) => (Array.isArray(v) ? v : []); | |
| const clampNum = (v, lo, hi, d) => (isNum(v) ? Math.min(hi, Math.max(lo, v)) : d); | |
| // A chat message (one node in the parentMessageId tree). | |
| export function normMessage(m = {}) { | |
| return { | |
| messageId: isStr(m.messageId) ? m.messageId : null, | |
| conversationId: isStr(m.conversationId) ? m.conversationId : null, | |
| parentMessageId: isStr(m.parentMessageId) ? m.parentMessageId : null, // tree edge | |
| sender: isStr(m.sender) ? m.sender : (m.isCreatedByUser ? "User" : "Assistant"), | |
| isCreatedByUser: isBool(m.isCreatedByUser) ? m.isCreatedByUser : false, | |
| model: isStr(m.model) ? m.model : null, | |
| text: isStr(m.text) ? m.text : "", | |
| content: arr(m.content), // structured parts (text/tool/image/reasoning) | |
| tokenCount: isNum(m.tokenCount) ? m.tokenCount : null, | |
| error: m.error || null, | |
| unfinished: !!m.unfinished, | |
| feedback: m.feedback && isStr(m.feedback.rating || "") ? { rating: m.feedback.rating, tag: m.feedback.tag || null, text: m.feedback.text || "" } : (m.feedback || null), | |
| files: arr(m.files), | |
| createdAt: isStr(m.createdAt) ? m.createdAt : null, | |
| receiptKappa: isStr(m.receiptKappa) ? m.receiptKappa : null, // links the PROV-O inference receipt | |
| }; | |
| } | |
| // A conversation (spreads the active preset, like LibreChat's conversationPreset). | |
| export function normConversation(c = {}) { | |
| return { | |
| conversationId: isStr(c.conversationId) ? c.conversationId : null, | |
| title: isStr(c.title) ? c.title : "New Chat", | |
| tags: arr(c.tags), | |
| files: arr(c.files), | |
| folder: isStr(c.folder) ? c.folder : null, | |
| archived: !!c.archived, | |
| favorite: !!c.favorite, | |
| isTemporary: !!c.isTemporary, | |
| preset: c.preset ? normPreset(c.preset) : null, | |
| createdAt: isStr(c.createdAt) ? c.createdAt : null, | |
| updatedAt: isStr(c.updatedAt) ? c.updatedAt : null, | |
| }; | |
| } | |
| // A preset / parameter bundle. Bounds mirror common LLM ranges; model is the local κ-object id. | |
| export function normPreset(p = {}) { | |
| return { | |
| presetId: isStr(p.presetId) ? p.presetId : null, | |
| title: isStr(p.title) ? p.title : "Preset", | |
| model: isStr(p.model) ? p.model : null, | |
| temperature: clampNum(p.temperature, 0, 2, 0), | |
| top_p: clampNum(p.top_p, 0, 1, 1), | |
| top_k: clampNum(p.top_k, 0, 200, 40), | |
| maxOutputTokens: clampNum(p.maxOutputTokens, 1, 8192, 1024), | |
| promptPrefix: isStr(p.promptPrefix) ? p.promptPrefix : "", | |
| presence_penalty: clampNum(p.presence_penalty, -2, 2, 0), | |
| frequency_penalty: clampNum(p.frequency_penalty, -2, 2, 0), | |
| stop: arr(p.stop), | |
| iconURL: isStr(p.iconURL) ? p.iconURL : null, | |
| isArchived: !!p.isArchived, | |
| }; | |
| } | |
| // Walk a flat message list into the LibreChat parentMessageId tree → {roots, byId, childrenOf}. | |
| export function buildTree(messages) { | |
| const byId = new Map(); const childrenOf = new Map(); const roots = []; | |
| for (const m of messages) { byId.set(m.messageId, m); childrenOf.set(m.messageId, []); } | |
| for (const m of messages) { | |
| if (m.parentMessageId && childrenOf.has(m.parentMessageId)) childrenOf.get(m.parentMessageId).push(m.messageId); | |
| else roots.push(m.messageId); | |
| } | |
| return { roots, byId, childrenOf }; | |
| } | |
| // The active thread = follow the chosen child at each branch from a root to a leaf. | |
| export function threadOf(tree, { choose = (siblings) => siblings[siblings.length - 1] } = {}) { | |
| const out = []; let cur = tree.roots.length ? choose(tree.roots) : null; | |
| while (cur) { out.push(tree.byId.get(cur)); const kids = tree.childrenOf.get(cur) || []; cur = kids.length ? choose(kids) : null; } | |
| return out; | |
| } | |