@@ -1576,18 +1576,32 @@
< option value = "150" > 150< / option >
< option value = "150" > 150< / option >
< option value = "200" > 200< / option >
< option value = "200" > 200< / option >
< / select > < / div >
< / select > < / div >
< / select >
< / div >
< / div >
< div class = "settings-row" >
< div class = "settings-field full" > < label > 头部包裹词( 应对NoAss配置) < / label > < input type = "text"
id = "trigger-wrapper-head" placeholder = "添加到开头" > < / div >
< / div >
< div class = "settings-row" >
< div class = "settings-field full" > < label > 尾部包裹词( 应对NoAss配置) < / label > < input type = "text"
id = "trigger-wrapper-tail" placeholder = "添加到结尾" > < / div >
< / div >
< / div >
< div class = "settings-row" >
< div class = "settings-row" >
< div class = "settings-field-inline" > < input type = "checkbox" id = "trigger-enabled" > < label
< div class = "settings-field-inline" > < input type = "checkbox" id = "trigger-enabled" > < label
for = "trigger-enabled" > 启用自动总结< / label > < / div >
for = "trigger-enabled" > 启用自动总结< / label > < / div >
< div class = "settings-field-inline" > < input type = "checkbox" id = "trigger-stream" checked > < label
< div class = "settings-field-inline" > < input type = "checkbox" id = "trigger-stream" checked > < label
for = "trigger-stream" > 启用流式生成< / label > < / div >
for = "trigger-stream" > 启用流式生成< / label > < / div >
< div class = "settings-field-inline" > < input type = "checkbox" id = "trigger-insert-at-end" > < label
for = "trigger-insert-at-end" > 强制插入到聊天最后< / label > < / div >
< / div >
< / div >
< div class = "settings-hint" style = "margin-top:8px" > 若 API 不支持非流式请求,请勾选"启用流式生成"< / div >
< div class = "settings-hint" style = "margin-top:8px" > 若 API 不支持非流式请求,请勾选"启用流式生成"< / div >
< / div >
< / div >
< div class = "modal-foot" >
< button class = "btn" id = "settings-cancel" > 取消< / button > < button class = "btn btn-p"
id = "settings-save" > 保存< / button >
< / div >
< / div >
< / div >
< div class = "modal-foot" > < button class = "btn" id = "settings-cancel" > 取消< / button > < button class = "btn btn-p"
id = "settings-save" > 保存< / button > < / div >
< / div >
< / div >
< / div >
< / div >
@@ -1612,7 +1626,7 @@
const $ = id => document . getElementById ( id ) , $$ = sel => document . querySelectorAll ( sel ) ;
const $ = id => document . getElementById ( id ) , $$ = sel => document . querySelectorAll ( sel ) ;
const escapeHtml = ( v ) => String ( v ? ? "" ) . replace ( /[&<>"']/g , c => ( { "&" : "&" , "<" : "<" , ">" : ">" , "\"" : """ , "'" : "'" } ) [ c ] ) ;
const escapeHtml = ( v ) => String ( v ? ? "" ) . replace ( /[&<>"']/g , c => ( { "&" : "&" , "<" : "<" , ">" : ">" , "\"" : """ , "'" : "'" } ) [ c ] ) ;
const h = ( v ) => escapeHtml ( v ) ;
const h = ( v ) => escapeHtml ( v ) ;
const config = { api : { provider : 'st' , url : '' , key : '' , model : '' , modelCache : [ ] } , gen : { temperature : null , top _p : null , top _k : null , presence _penalty : null , frequency _penalty : null } , trigger : { enabled : false , interval : 20 , timing : 'after_ai' , useStream : true , maxPerRun : 100 } } ;
const config = { api : { provider : 'st' , url : '' , key : '' , model : '' , modelCache : [ ] } , gen : { temperature : null , top _p : null , top _k : null , presence _penalty : null , frequency _penalty : null } , trigger : { enabled : false , interval : 20 , timing : 'after_ai' , useStream : true , maxPerRun : 100 , wrapperHead : '' , wrapperTail : '' , forceInsertAtEnd : false } } ;
let summaryData = { keywords : [ ] , events : [ ] , characters : { main : [ ] , relationships : [ ] } , arcs : [ ] } , localGenerating = false , relationChart = null , relationChartFullscreen = null , currentEditSection = null , currentCharacterId = null , allNodes = [ ] , allLinks = [ ] , activeRelationTooltip = null ;
let summaryData = { keywords : [ ] , events : [ ] , characters : { main : [ ] , relationships : [ ] } , arcs : [ ] } , localGenerating = false , relationChart = null , relationChartFullscreen = null , currentEditSection = null , currentCharacterId = null , allNodes = [ ] , allLinks = [ ] , activeRelationTooltip = null ;
const providerDefaults = { st : { url : '' , needKey : false , canFetch : false , needManualModel : false } , openai : { url : 'https://api.openai.com' , needKey : true , canFetch : true , needManualModel : false } , google : { url : 'https://generativelanguage.googleapis.com' , needKey : true , canFetch : false , needManualModel : true } , claude : { url : 'https://api.anthropic.com' , needKey : true , canFetch : false , needManualModel : true } , deepseek : { url : 'https://api.deepseek.com' , needKey : true , canFetch : true , needManualModel : false } , cohere : { url : 'https://api.cohere.ai' , needKey : true , canFetch : false , needManualModel : true } , custom : { url : '' , needKey : true , canFetch : true , needManualModel : false } } ;
const providerDefaults = { st : { url : '' , needKey : false , canFetch : false , needManualModel : false } , openai : { url : 'https://api.openai.com' , needKey : true , canFetch : true , needManualModel : false } , google : { url : 'https://generativelanguage.googleapis.com' , needKey : true , canFetch : false , needManualModel : true } , claude : { url : 'https://api.anthropic.com' , needKey : true , canFetch : false , needManualModel : true } , deepseek : { url : 'https://api.deepseek.com' , needKey : true , canFetch : true , needManualModel : false } , cohere : { url : 'https://api.cohere.ai' , needKey : true , canFetch : false , needManualModel : true } , custom : { url : '' , needKey : true , canFetch : true , needManualModel : false } } ;
@@ -1690,8 +1704,8 @@
function saveEditor ( ) { const section = currentEditSection , es = $ ( 'editor-struct' ) , ta = $ ( 'editor-ta' ) ; let parsed ; try { if ( section === 'keywords' ) { const oldMap = new Map ( ( summaryData . keywords || [ ] ) . map ( k => [ k . text , k ] ) ) ; parsed = ta . value . trim ( ) . split ( '\n' ) . filter ( l => l . trim ( ) ) . map ( line => { const [ text , weight ] = line . split ( '|' ) . map ( s => s . trim ( ) ) ; return preserveAddedAt ( { text : text || '' , weight : weight || '一般' } , oldMap . get ( text ) ) } ) } else if ( section === 'events' ) { const oldMap = new Map ( ( summaryData . events || [ ] ) . map ( e => [ e . id , e ] ) ) ; parsed = Array . from ( es . querySelectorAll ( '.event-item' ) ) . map ( it => { const id = it . dataset . id ; return preserveAddedAt ( { id , title : it . querySelector ( '.event-title' ) . value . trim ( ) , timeLabel : it . querySelector ( '.event-time' ) . value . trim ( ) , summary : it . querySelector ( '.event-summary' ) . value . trim ( ) , participants : it . querySelector ( '.event-participants' ) . value . trim ( ) . split ( /[,、,]/ ) . map ( s => s . trim ( ) ) . filter ( Boolean ) , type : it . querySelector ( '.event-type' ) . value , weight : it . querySelector ( '.event-weight' ) . value } , oldMap . get ( id ) ) } ) . filter ( e => e . title || e . summary ) } else if ( section === 'characters' ) { const oldMainMap = new Map ( ( summaryData . characters ? . main || [ ] ) . map ( m => [ getCharName ( m ) , m ] ) ) , mainNames = Array . from ( es . querySelectorAll ( '.char-main-name' ) ) . map ( i => i . value . trim ( ) ) . filter ( Boolean ) , main = mainNames . map ( n => preserveAddedAt ( { name : n } , oldMainMap . get ( n ) ) ) ; const oldRelMap = new Map ( ( summaryData . characters ? . relationships || [ ] ) . map ( r => [ ` ${ r . from } -> ${ r . to } ` , r ] ) ) , rels = Array . from ( es . querySelectorAll ( '.char-rel-item' ) ) . map ( it => { const from = it . querySelector ( '.char-rel-from' ) . value . trim ( ) , to = it . querySelector ( '.char-rel-to' ) . value . trim ( ) ; return preserveAddedAt ( { from , to , label : it . querySelector ( '.char-rel-label' ) . value . trim ( ) , trend : it . querySelector ( '.char-rel-trend' ) . value } , oldRelMap . get ( ` ${ from } -> ${ to } ` ) ) } ) . filter ( r => r . from && r . to ) ; parsed = { main , relationships : rels } } else if ( section === 'arcs' ) { const oldArcMap = new Map ( ( summaryData . arcs || [ ] ) . map ( a => [ a . name , a ] ) ) ; parsed = Array . from ( es . querySelectorAll ( '.arc-item' ) ) . map ( it => { const name = it . querySelector ( '.arc-name' ) . value . trim ( ) , oldArc = oldArcMap . get ( name ) , oldMomentMap = new Map ( ( oldArc ? . moments || [ ] ) . map ( m => [ typeof m === 'string' ? m : m . text , m ] ) ) , momentsRaw = it . querySelector ( '.arc-moments' ) . value . trim ( ) , moments = momentsRaw ? momentsRaw . split ( '\n' ) . map ( s => s . trim ( ) ) . filter ( Boolean ) . map ( t => preserveAddedAt ( { text : t } , oldMomentMap . get ( t ) ) ) : [ ] ; return preserveAddedAt ( { name , trajectory : it . querySelector ( '.arc-trajectory' ) . value . trim ( ) , progress : Math . max ( 0 , Math . min ( 1 , ( parseFloat ( it . querySelector ( '.arc-progress' ) . value ) || 0 ) / 100 ) ) , moments } , oldArc ) } ) . filter ( a => a . name || a . trajectory || a . moments ? . length ) } } catch ( e ) { $ ( 'editor-err' ) . textContent = ` 格式错误: ${ e . message } ` ; $ ( 'editor-err' ) . classList . add ( 'visible' ) ; return } postMsg ( 'UPDATE_SECTION' , { section , data : parsed } ) ; if ( section === 'keywords' ) renderKeywords ( parsed ) ; else if ( section === 'events' ) { renderTimeline ( parsed ) ; $ ( 'stat-events' ) . textContent = parsed . length } else if ( section === 'characters' ) renderRelations ( parsed ) ; else if ( section === 'arcs' ) renderArcs ( parsed ) ; closeEditor ( ) }
function saveEditor ( ) { const section = currentEditSection , es = $ ( 'editor-struct' ) , ta = $ ( 'editor-ta' ) ; let parsed ; try { if ( section === 'keywords' ) { const oldMap = new Map ( ( summaryData . keywords || [ ] ) . map ( k => [ k . text , k ] ) ) ; parsed = ta . value . trim ( ) . split ( '\n' ) . filter ( l => l . trim ( ) ) . map ( line => { const [ text , weight ] = line . split ( '|' ) . map ( s => s . trim ( ) ) ; return preserveAddedAt ( { text : text || '' , weight : weight || '一般' } , oldMap . get ( text ) ) } ) } else if ( section === 'events' ) { const oldMap = new Map ( ( summaryData . events || [ ] ) . map ( e => [ e . id , e ] ) ) ; parsed = Array . from ( es . querySelectorAll ( '.event-item' ) ) . map ( it => { const id = it . dataset . id ; return preserveAddedAt ( { id , title : it . querySelector ( '.event-title' ) . value . trim ( ) , timeLabel : it . querySelector ( '.event-time' ) . value . trim ( ) , summary : it . querySelector ( '.event-summary' ) . value . trim ( ) , participants : it . querySelector ( '.event-participants' ) . value . trim ( ) . split ( /[,、,]/ ) . map ( s => s . trim ( ) ) . filter ( Boolean ) , type : it . querySelector ( '.event-type' ) . value , weight : it . querySelector ( '.event-weight' ) . value } , oldMap . get ( id ) ) } ) . filter ( e => e . title || e . summary ) } else if ( section === 'characters' ) { const oldMainMap = new Map ( ( summaryData . characters ? . main || [ ] ) . map ( m => [ getCharName ( m ) , m ] ) ) , mainNames = Array . from ( es . querySelectorAll ( '.char-main-name' ) ) . map ( i => i . value . trim ( ) ) . filter ( Boolean ) , main = mainNames . map ( n => preserveAddedAt ( { name : n } , oldMainMap . get ( n ) ) ) ; const oldRelMap = new Map ( ( summaryData . characters ? . relationships || [ ] ) . map ( r => [ ` ${ r . from } -> ${ r . to } ` , r ] ) ) , rels = Array . from ( es . querySelectorAll ( '.char-rel-item' ) ) . map ( it => { const from = it . querySelector ( '.char-rel-from' ) . value . trim ( ) , to = it . querySelector ( '.char-rel-to' ) . value . trim ( ) ; return preserveAddedAt ( { from , to , label : it . querySelector ( '.char-rel-label' ) . value . trim ( ) , trend : it . querySelector ( '.char-rel-trend' ) . value } , oldRelMap . get ( ` ${ from } -> ${ to } ` ) ) } ) . filter ( r => r . from && r . to ) ; parsed = { main , relationships : rels } } else if ( section === 'arcs' ) { const oldArcMap = new Map ( ( summaryData . arcs || [ ] ) . map ( a => [ a . name , a ] ) ) ; parsed = Array . from ( es . querySelectorAll ( '.arc-item' ) ) . map ( it => { const name = it . querySelector ( '.arc-name' ) . value . trim ( ) , oldArc = oldArcMap . get ( name ) , oldMomentMap = new Map ( ( oldArc ? . moments || [ ] ) . map ( m => [ typeof m === 'string' ? m : m . text , m ] ) ) , momentsRaw = it . querySelector ( '.arc-moments' ) . value . trim ( ) , moments = momentsRaw ? momentsRaw . split ( '\n' ) . map ( s => s . trim ( ) ) . filter ( Boolean ) . map ( t => preserveAddedAt ( { text : t } , oldMomentMap . get ( t ) ) ) : [ ] ; return preserveAddedAt ( { name , trajectory : it . querySelector ( '.arc-trajectory' ) . value . trim ( ) , progress : Math . max ( 0 , Math . min ( 1 , ( parseFloat ( it . querySelector ( '.arc-progress' ) . value ) || 0 ) / 100 ) ) , moments } , oldArc ) } ) . filter ( a => a . name || a . trajectory || a . moments ? . length ) } } catch ( e ) { $ ( 'editor-err' ) . textContent = ` 格式错误: ${ e . message } ` ; $ ( 'editor-err' ) . classList . add ( 'visible' ) ; return } postMsg ( 'UPDATE_SECTION' , { section , data : parsed } ) ; if ( section === 'keywords' ) renderKeywords ( parsed ) ; else if ( section === 'events' ) { renderTimeline ( parsed ) ; $ ( 'stat-events' ) . textContent = parsed . length } else if ( section === 'characters' ) renderRelations ( parsed ) ; else if ( section === 'arcs' ) renderArcs ( parsed ) ; closeEditor ( ) }
function updateProviderUI ( provider ) { const pv = providerDefaults [ provider ] || providerDefaults . custom , isSt = provider === 'st' ; $ ( 'api-url-row' ) . classList . toggle ( 'hidden' , isSt ) ; $ ( 'api-key-row' ) . classList . toggle ( 'hidden' , ! pv . needKey ) ; $ ( 'api-model-manual-row' ) . classList . toggle ( 'hidden' , isSt || ! pv . needManualModel ) ; $ ( 'api-model-select-row' ) . classList . toggle ( 'hidden' , isSt || pv . needManualModel || ! config . api . modelCache . length ) ; $ ( 'api-connect-row' ) . classList . toggle ( 'hidden' , isSt || ! pv . canFetch ) ; const urlInput = $ ( 'api-url' ) ; if ( ! urlInput . value && pv . url ) urlInput . value = pv . url }
function updateProviderUI ( provider ) { const pv = providerDefaults [ provider ] || providerDefaults . custom , isSt = provider === 'st' ; $ ( 'api-url-row' ) . classList . toggle ( 'hidden' , isSt ) ; $ ( 'api-key-row' ) . classList . toggle ( 'hidden' , ! pv . needKey ) ; $ ( 'api-model-manual-row' ) . classList . toggle ( 'hidden' , isSt || ! pv . needManualModel ) ; $ ( 'api-model-select-row' ) . classList . toggle ( 'hidden' , isSt || pv . needManualModel || ! config . api . modelCache . length ) ; $ ( 'api-connect-row' ) . classList . toggle ( 'hidden' , isSt || ! pv . canFetch ) ; const urlInput = $ ( 'api-url' ) ; if ( ! urlInput . value && pv . url ) urlInput . value = pv . url }
function openSettings ( ) { const pn = id => { const v = $ ( id ) . value ; return v === '' ? null : parseFloat ( v ) } ; $ ( 'api-provider' ) . value = config . api . provider ; $ ( 'api-url' ) . value = config . api . url ; $ ( 'api-key' ) . value = config . api . key ; $ ( 'api-model-text' ) . value = config . api . model ; $ ( 'gen-temp' ) . value = config . gen . temperature ? ? '' ; $ ( 'gen-top-p' ) . value = config . gen . top _p ? ? '' ; $ ( 'gen-top-k' ) . value = config . gen . top _k ? ? '' ; $ ( 'gen-presence' ) . value = config . gen . presence _penalty ? ? '' ; $ ( 'gen-frequency' ) . value = config . gen . frequency _penalty ? ? '' ; $ ( 'trigger-enabled' ) . checked = config . trigger . enabled ; $ ( 'trigger-interval' ) . value = config . trigger . interval ; $ ( 'trigger-timing' ) . value = config . trigger . timing ; $ ( 'trigger-stream' ) . checked = config . trigger . useStream !== false ; $ ( 'trigger-max-per-run' ) . value = config . trigger . maxPerRun || 100 ; const en = $ ( 'trigger-enabled' ) ; if ( config . trigger . timing === 'manual' ) { en . checked = false ; en . disabled = true ; en . parentElement . style . opacity = '.5' } else { en . disabled = false ; en . parentElement . style . opacity = '1' } if ( config . api . modelCache . length ) { $ ( 'api-model-select' ) . innerHTML = config . api . modelCache . map ( m => ` <option value=" ${ m } " ${ m === config . api . model ? ' selected' : '' } > ${ m } </option> ` ) . join ( '' ) } updateProviderUI ( config . api . provider ) ; $ ( 'settings-modal' ) . classList . add ( 'active' ) ; postMsg ( 'SETTINGS_OPENED' ) }
function openSettings ( ) { const pn = id => { const v = $ ( id ) . value ; return v === '' ? null : parseFloat ( v ) } ; $ ( 'api-provider' ) . value = config . api . provider ; $ ( 'api-url' ) . value = config . api . url ; $ ( 'api-key' ) . value = config . api . key ; $ ( 'api-model-text' ) . value = config . api . model ; $ ( 'gen-temp' ) . value = config . gen . temperature ? ? '' ; $ ( 'gen-top-p' ) . value = config . gen . top _p ? ? '' ; $ ( 'gen-top-k' ) . value = config . gen . top _k ? ? '' ; $ ( 'gen-presence' ) . value = config . gen . presence _penalty ? ? '' ; $ ( 'gen-frequency' ) . value = config . gen . frequency _penalty ? ? '' ; $ ( 'trigger-enabled' ) . checked = config . trigger . enabled ; $ ( 'trigger-interval' ) . value = config . trigger . interval ; $ ( 'trigger-timing' ) . value = config . trigger . timing ; $ ( 'trigger-stream' ) . checked = config . trigger . useStream !== false ; $ ( 'trigger-max-per-run' ) . value = config . trigger . maxPerRun || 100 ; $ ( 'trigger-wrapper-head' ) . value = config . trigger . wrapperHead || '' ; $ ( 'trigger-wrapper-tail' ) . value = config . trigger . wrapperTail || '' ; $ ( 'trigger-insert-at-end' ) . checked = ! ! config . trigger . forceInsertAtEnd ; const en = $ ( 'trigger-enabled' ) ; if ( config . trigger . timing === 'manual' ) { en . checked = false ; en . disabled = true ; en . parentElement . style . opacity = '.5' } else { en . disabled = false ; en . parentElement . style . opacity = '1' } if ( config . api . modelCache . length ) { $ ( 'api-model-select' ) . innerHTML = config . api . modelCache . map ( m => ` <option value=" ${ m } " ${ m === config . api . model ? ' selected' : '' } > ${ m } </option> ` ) . join ( '' ) } updateProviderUI ( config . api . provider ) ; $ ( 'settings-modal' ) . classList . add ( 'active' ) ; postMsg ( 'SETTINGS_OPENED' ) }
function closeSettings ( save ) { if ( save ) { const pn = id => { const v = $ ( id ) . value ; return v === '' ? null : parseFloat ( v ) } ; const provider = $ ( 'api-provider' ) . value , pv = providerDefaults [ provider ] || providerDefaults . custom ; config . api . provider = provider ; config . api . url = $ ( 'api-url' ) . value ; config . api . key = $ ( 'api-key' ) . value ; config . api . model = provider === 'st' ? '' : pv . needManualModel ? $ ( 'api-model-text' ) . value : $ ( 'api-model-select' ) . value ; config . gen . temperature = pn ( 'gen-temp' ) ; config . gen . top _p = pn ( 'gen-top-p' ) ; config . gen . top _k = pn ( 'gen-top-k' ) ; config . gen . presence _penalty = pn ( 'gen-presence' ) ; config . gen . frequency _penalty = pn ( 'gen-frequency' ) ; const timing = $ ( 'trigger-timing' ) . value ; config . trigger . timing = timing ; config . trigger . enabled = timing === 'manual' ? false : $ ( 'trigger-enabled' ) . checked ; config . trigger . interval = parseInt ( $ ( 'trigger-interval' ) . value ) || 20 ; config . trigger . useStream = $ ( 'trigger-stream' ) . checked ; config . trigger . maxPerRun = parseInt ( $ ( 'trigger-max-per-run' ) . value ) || 100 ; saveConfig ( ) } $ ( 'settings-modal' ) . classList . remove ( 'active' ) ; postMsg ( 'SETTINGS_CLOSED' ) }
function closeSettings ( save ) { if ( save ) { const pn = id => { const v = $ ( id ) . value ; return v === '' ? null : parseFloat ( v ) } ; const provider = $ ( 'api-provider' ) . value , pv = providerDefaults [ provider ] || providerDefaults . custom ; config . api . provider = provider ; config . api . url = $ ( 'api-url' ) . value ; config . api . key = $ ( 'api-key' ) . value ; config . api . model = provider === 'st' ? '' : pv . needManualModel ? $ ( 'api-model-text' ) . value : $ ( 'api-model-select' ) . value ; config . gen . temperature = pn ( 'gen-temp' ) ; config . gen . top _p = pn ( 'gen-top-p' ) ; config . gen . top _k = pn ( 'gen-top-k' ) ; config . gen . presence _penalty = pn ( 'gen-presence' ) ; config . gen . frequency _penalty = pn ( 'gen-frequency' ) ; const timing = $ ( 'trigger-timing' ) . value ; config . trigger . timing = timing ; config . trigger . enabled = timing === 'manual' ? false : $ ( 'trigger-enabled' ) . checked ; config . trigger . interval = parseInt ( $ ( 'trigger-interval' ) . value ) || 20 ; config . trigger . useStream = $ ( 'trigger-stream' ) . checked ; config . trigger . maxPerRun = parseInt ( $ ( 'trigger-max-per-run' ) . value ) || 100 ; config . trigger . wrapperHead = $ ( 'trigger-wrapper-head' ) . value ; config . trigger . wrapperTail = $ ( 'trigger-wrapper-tail' ) . value ; config . trigger . forceInsertAtEnd = $ ( 'trigger-insert-at-end' ) . checked ; saveConfig ( ) } $ ( 'settings-modal' ) . classList . remove ( 'active' ) ; postMsg ( 'SETTINGS_CLOSED' ) }
async function fetchModels ( ) { const btn = $ ( 'btn-connect' ) , provider = $ ( 'api-provider' ) . value ; if ( ! providerDefaults [ provider ] ? . canFetch ) { alert ( '当前渠道不支持自动拉取模型' ) ; return } let baseUrl = $ ( 'api-url' ) . value . trim ( ) . replace ( /\/+$/ , '' ) ; const apiKey = $ ( 'api-key' ) . value . trim ( ) ; if ( ! apiKey ) { alert ( '请先填写 API KEY' ) ; return } btn . disabled = true ; btn . textContent = '连接中...' ; try { const tryFetch = async url => { const res = await fetch ( url , { headers : { Authorization : ` Bearer ${ apiKey } ` , Accept : 'application/json' } } ) ; return res . ok ? ( await res . json ( ) ) ? . data ? . map ( m => m ? . id ) . filter ( Boolean ) || null : null } ; if ( baseUrl . endsWith ( '/v1' ) ) baseUrl = baseUrl . slice ( 0 , - 3 ) ; let models = await tryFetch ( ` ${ baseUrl } /v1/models ` ) ; if ( ! models ) models = await tryFetch ( ` ${ baseUrl } /models ` ) ; if ( ! models ? . length ) throw new Error ( '未获取到模型列表' ) ; config . api . modelCache = [ ... new Set ( models ) ] ; const sel = $ ( 'api-model-select' ) ; sel . innerHTML = config . api . modelCache . map ( m => ` <option value=" ${ m } "> ${ m } </option> ` ) . join ( '' ) ; $ ( 'api-model-select-row' ) . classList . remove ( 'hidden' ) ; if ( ! config . api . model && models . length ) { config . api . model = models [ 0 ] ; sel . value = models [ 0 ] } else if ( config . api . model ) sel . value = config . api . model ; saveConfig ( ) ; alert ( ` 成功获取 ${ models . length } 个模型 ` ) } catch ( e ) { alert ( '连接失败:' + ( e . message || '请检查 URL 和 KEY' ) ) } finally { btn . disabled = false ; btn . textContent = '连接 / 拉取模型列表' } }
async function fetchModels ( ) { const btn = $ ( 'btn-connect' ) , provider = $ ( 'api-provider' ) . value ; if ( ! providerDefaults [ provider ] ? . canFetch ) { alert ( '当前渠道不支持自动拉取模型' ) ; return } let baseUrl = $ ( 'api-url' ) . value . trim ( ) . replace ( /\/+$/ , '' ) ; const apiKey = $ ( 'api-key' ) . value . trim ( ) ; if ( ! apiKey ) { alert ( '请先填写 API KEY' ) ; return } btn . disabled = true ; btn . textContent = '连接中...' ; try { const tryFetch = async url => { const res = await fetch ( url , { headers : { Authorization : ` Bearer ${ apiKey } ` , Accept : 'application/json' } } ) ; return res . ok ? ( await res . json ( ) ) ? . data ? . map ( m => m ? . id ) . filter ( Boolean ) || null : null } ; if ( baseUrl . endsWith ( '/v1' ) ) baseUrl = baseUrl . slice ( 0 , - 3 ) ; let models = await tryFetch ( ` ${ baseUrl } /v1/models ` ) ; if ( ! models ) models = await tryFetch ( ` ${ baseUrl } /models ` ) ; if ( ! models ? . length ) throw new Error ( '未获取到模型列表' ) ; config . api . modelCache = [ ... new Set ( models ) ] ; const sel = $ ( 'api-model-select' ) ; sel . innerHTML = config . api . modelCache . map ( m => ` <option value=" ${ m } "> ${ m } </option> ` ) . join ( '' ) ; $ ( 'api-model-select-row' ) . classList . remove ( 'hidden' ) ; if ( ! config . api . model && models . length ) { config . api . model = models [ 0 ] ; sel . value = models [ 0 ] } else if ( config . api . model ) sel . value = config . api . model ; saveConfig ( ) ; alert ( ` 成功获取 ${ models . length } 个模型 ` ) } catch ( e ) { alert ( '连接失败:' + ( e . message || '请检查 URL 和 KEY' ) ) } finally { btn . disabled = false ; btn . textContent = '连接 / 拉取模型列表' } }
$$ ( '.sec-btn[data-section]' ) . forEach ( b => b . onclick = ( ) => openEditor ( b . dataset . section ) ) ;
$$ ( '.sec-btn[data-section]' ) . forEach ( b => b . onclick = ( ) => openEditor ( b . dataset . section ) ) ;