import { useEffect, useMemo, useRef, useState } from 'react'
import { marked } from 'marked'
import DOMPurify from 'dompurify'
import LensView from './LensView.jsx'
import LensDiff from './Diff.jsx'
import Editor from './Editor.jsx'
import { fmtTok } from './tok'
const GB = 2 ** 30
marked.setOptions({ breaks: true, gfm: true })
// Markdown bubble content: parsed + sanitized (a local model can still emit
// arbitrary HTML — never inject it raw).
function Md({ text }) {
const html = useMemo(() => DOMPurify.sanitize(marked.parse(text || '')), [text])
return
}
// Polluter tokens excluded by default from the Frequencies token cloud (compared
// on the form without edge spaces). Managed at the bottom of the Chat tab.
const DEFAULT_HIDDEN = [
'热门推荐', '阅读全文', '网友评论', '点击查看答案', '查看全文', '最新发布', '展开全文',
'相关问答', '続きを読む', 'Читать', 'Weiterlesen', 'スポンサーリンク',
]
const trimTok = (s) => (s || '').replace(/^\s+|\s+$/g, '')
const LENS_PRESETS = {
'Qwen/Qwen3.5-4B': {
repo_id: 'neuronpedia/jacobian-lens',
revision: 'qwen-n1000',
filename: 'qwen3.5-4b/jlens/Salesforce-wikitext/Qwen3.5-4B_jacobian_lens_n1000.pt',
},
'Qwen/Qwen3-4B': {
repo_id: 'neuronpedia/jacobian-lens',
revision: 'main',
filename: 'qwen3-4b/jlens/Salesforce-wikitext/Qwen3-4B_jacobian_lens.pt',
},
}
async function jsonFetch(url, options) {
const res = await fetch(url, options)
const body = await res.json().catch(() => ({}))
if (!res.ok) throw new Error(body.detail || res.statusText)
return body
}
const SAMPLING_DEFAULT = { temperature: 0.7, top_p: 0.95, top_k: 40, max_tokens: 512, seed: -1 }
// Human-readable name of the loaded lens (local path or Hub file) for "which lens do I have?".
function lensName(meta) {
if (!meta) return ''
if (meta.path) return meta.path.replace(/[/\\]+$/, '').split(/[/\\]/).pop()
if (meta.filename) {
const stem = meta.filename.split('/').pop().replace('_jacobian_lens', '').replace('.pt', '')
return meta.revision ? `${stem} @${meta.revision}` : stem
}
return meta.repo_id || 'lens'
}
// Note about the chat template fetched when loading a base model.
function templateNote(meta) {
const src = meta.chat_template_source
if (!src) return { suffix: '', warn: false } // the model has its own template
if (src === 'generic') {
return { suffix: ' — no chat template found on the Hub: generic User:/Assistant: template (limited results on a base model)', warn: true }
}
return { suffix: ` — chat template fetched from ${src}`, warn: false }
}
function TreeNode({ node, childs, depth, activeIds, onSelect }) {
return (
<>
no known lens for this model — fit one (Fit tab), or
load a lens fitted for a compatible model below
)}
{reg && (reg.other?.length || 0) > 0 && (
lenses fitted for other models ({reg.other.length})
For your own finetune or merge without a matching lens: a lens fitted
on a compatible model of the SAME architecture can work (d_model and
layer count are checked at load). Readouts drift with the distance
between the weights — treat them as approximate.
{reg.other.map(renderHubLens)}
)}
manual load
Load any Jacobian-lens .pt: either a Hugging Face repo + the
file path inside it (mirror the entries above), or the local path of a
lens you fitted (Fit tab writes lenses/<name>/lens.pt).
fit done: {fit.name} ({Math.round((fit.meta?.fit_seconds || 0) / 60)} min) — reload the model then{' '}
)}
{fit?.state === 'error' &&
fit error: {fit.error}
}
{fit?.state === 'stopped' &&
fit stopped (checkpoints kept, restart = resume)
}
{fitContinue && (
resume: {fitN} prompts are added to the {localLenses.find((l) => l.path === fitContinue)?.meta?.n_prompts ?? '?'} existing ones (weighted average = fit over the union); source layers inherited