(function(){
const {useState,useEffect,useRef}=React;
const paths={plug:'M8 3v5m8-5v5M6 8h12v4a6 6 0 0 1-12 0V8Zm6 10v4',package:'m12 3 9 5v9l-9 5-9-5V8l9-5ZM3 8l9 5 9-5M12 13v9M7.5 5.5l9 5v4',tools:'M14 4a5 5 0 0 0-6 6L3 16a3 3 0 0 0 5 4l5-6a5 5 0 0 0 7-6l-4 3-3-3 3-4Z',search:'M16 16l5 5M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0',plus:'M12 5v14M5 12h14',close:'m6 6 12 12M6 18 18 6',check:'m5 12 4.5 4.5L19 7',down:'m7 10 5 5 5-5',right:'m9 6 6 6-6 6',back:'m14 6-6 6 6 6',refresh:'M20 8a8 8 0 0 0-14-3L3 8m0-5v5h5M4 16a8 8 0 0 0 14 3l3-3m0 5v-5h-5',settings:'m9 3-1 3-3 1-2 4 2 2v4l4 3 3-1 3 1 4-3v-4l2-2-2-4-3-1-1-3H9ZM15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0',upload:'M12 16V3m-5 5 5-5 5 5M4 15v5h16v-5',download:'M12 3v13m-5-5 5 5 5-5M4 17v4h16v-4',chat:'M5 4h14a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9l-6 4V6a2 2 0 0 1 2-2Z',folder:'M3 7V5a2 2 0 0 1 2-2h5l3 3h6a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7h18',doc:'M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9l-6-6ZM14 3v6h6M8 13h8m-8 4h5',github:'M9 20c-5 1-5-2-7-2m15 4v-4a3 3 0 0 0-1-2c4-.5 6-2 6-6a5 5 0 0 0-1-3 5 5 0 0 0-.2-4S19 2.7 17 4a14 14 0 0 0-8 0C7 2.7 5.2 3 5.2 3A5 5 0 0 0 5 7a5 5 0 0 0-1 3c0 4 2 5.5 6 6a3 3 0 0 0-1 2v4',diagram:'M9 2h6v5H9ZM2 16h6v5H2ZM16 16h6v5h-6ZM12 7v5H5v4m7-4h7v4',calendar:'M5 5h14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2ZM3 10h18M8 3v4m8-4v4',palette:'M12 3a9 9 0 1 0 0 18h2a2 2 0 0 0 1-4 2 2 0 0 1 1-4h2a3 3 0 0 0 3-3c0-4-5-7-9-7ZM7 8h.1M12 6h.1M17 8h.1M6 13h.1',book:'M4 3h15v18H6a3 3 0 0 1 0-6h13M4 3v15M8 7h7m-7 4h5',code:'m8 7-5 5 5 5m8-10 5 5-5 5m-3-13-2 20',terminal:'M4 4h16v16H4ZM7 8l3 3-3 3m6 1h4',globe:'M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0ZM3 12h18M12 3c5 4 5 14 0 18-5-4-5-14 0-18Z',link:'m10 13 4-4M9 16l-2 2a4 4 0 0 1-6-6l5-5a4 4 0 0 1 6 0m0 10a4 4 0 0 0 6 0l5-5a4 4 0 0 0-6-6l-2 2',bell:'M5 17h14l-2-4V9a5 5 0 0 0-10 0v4l-2 4ZM9 20a3 3 0 0 0 6 0M12 2v2',info:'M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0ZM12 11v6m0-11v1',more:'M5 12h.2M12 12h.2M19 12h.2',trash:'M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7m4-7v7',shield:'m12 3 8 3v6c0 5-8 9-8 9s-8-4-8-9V6l8-3ZM8 11l3 3 5-5',arrow:'M5 12h14m-5-5 5 5-5 5',copy:'M9 8h12v13H9ZM15 8V3H3v13h6'};
function Icon({name,size=18,className=''}){return <svg className={'ext-icon '+className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={paths[name]||paths.package}/></svg>;}
function IconButton({name,label,onClick,children,disabled=false}){return <button type="button" className="ext-icon-button" title={label} aria-label={label} onClick={onClick} disabled={disabled}><Icon name={name} size={16}/>{children}</button>;}
function Button({children,onClick,primary=false,subtle=false,small=false,type='button',disabled=false,...rest}){return <button type={type} className={'ext-button '+(primary?'primary ':'')+(subtle?'subtle ':'')+(small?'small':'')} onClick={onClick} disabled={disabled} {...rest}>{children}</button>;}
function Switch({label,checked,onChange,disabled=false}){return <button type="button" role="switch" aria-label={label} aria-checked={checked} disabled={disabled} className={'ext-switch '+(checked?'on':'')} onClick={()=>onChange(!checked)}><span></span></button>;}
function Mark({icon,color='slate',large=false}){return <span className={'ext-mark '+color+(large?' large':'')}><Icon name={icon} size={large?25:22}/></span>;}
function Dialog({title,description,children,footer,onClose,wide=false,className=''}){const ref=useRef(null),close=useRef(onClose);close.current=onClose;useEffect(()=>{const old=document.activeElement;const list=()=>Array.from(ref.current?.querySelectorAll('button:not(:disabled),input:not(:disabled),textarea,select,[tabindex="0"]')||[]);(ref.current?.querySelector('[data-autofocus]')||list()[0])?.focus();const key=e=>{if(e.key==='Escape'){e.preventDefault();e.stopPropagation();close.current();}if(e.key==='Tab'){const all=list();if(e.shiftKey&&document.activeElement===all[0]){e.preventDefault();all.at(-1)?.focus();}else if(!e.shiftKey&&document.activeElement===all.at(-1)){e.preventDefault();all[0]?.focus();}}};document.addEventListener('keydown',key,true);return()=>{document.removeEventListener('keydown',key,true);old?.focus();};},[]);return <div className="ext-backdrop" onMouseDown={e=>{if(e.target===e.currentTarget)onClose();}}><section className={'ext-dialog '+(wide?'wide ':'')+className} role="dialog" aria-modal="true" aria-label={title} ref={ref}><header className="ext-dialog-header"><div><h2>{title}</h2>{description&&<p>{description}</p>}</div><IconButton name="close" label="关闭弹窗" onClick={onClose}/></header><div className="ext-dialog-body">{children}</div>{footer&&<footer className="ext-dialog-footer">{footer}</footer>}</section></div>;}
function Empty({title='没有找到相关内容',description='试试其他关键词。',action}){return <div className="ext-empty"><Icon name="search" size={24}/><h3>{title}</h3><p>{description}</p>{action}</div>;}
function KeyValues({label,rows,onChange,secret=false,keyPlaceholder='名称',valuePlaceholder='值'}){return <fieldset className="ext-keyvalues"><legend>{label}<span>选填</span></legend>{rows.map((r,i)=><div className="ext-kv-row" key={i}><input aria-label={label+'名称 '+(i+1)} placeholder={keyPlaceholder} value={r.key} onChange={e=>onChange(rows.map((x,j)=>j===i?{...x,key:e.target.value}:x))}/><input aria-label={label+'值 '+(i+1)} placeholder={valuePlaceholder} type={secret?'password':'text'} autoComplete="off" value={r.value} onChange={e=>onChange(rows.map((x,j)=>j===i?{...x,value:e.target.value}:x))}/><IconButton name="close" label={'删除'+label+' '+(i+1)} onClick={()=>onChange(rows.filter((_,j)=>j!==i))}/></div>)}<button type="button" className="ext-text-action" onClick={()=>onChange([...rows,{key:'',value:''}])}><Icon name="plus" size={13}/>添加{label}</button></fieldset>;}
function inline(text){return String(text).split(/(\*\*[^*]+\*\*|\x60[^\x60]+\x60)/g).map((p,i)=>p.startsWith('**')?<strong key={i}>{p.slice(2,-2)}</strong>:p.charCodeAt(0)===96?<code key={i}>{p.slice(1,-1)}</code>:p);}
function Code({text,language=''}){let pretty=text;if(language==='json'){try{pretty=JSON.stringify(JSON.parse(text),null,2);}catch{}}return <pre className="ext-code"><code>{pretty.split('\n').map((line,i)=><span className="ext-code-line" key={i}><span className="ext-line-number">{i+1}</span><span>{line.split(/("(?:[^"\\]|\\.)*"|\b(?:true|false|null|import|from|def|return|if)\b|\b\d+\b|#.*$)/g).map((part,j)=><span key={j} className={part.startsWith('"')?'str':/^(true|false|null|import|from|def|return|if)$/.test(part)?'kw':/^\d+$/.test(part)?'num':part.startsWith('#')?'comment':''}>{part}</span>)}</span></span>)}</code></pre>;}
function Markdown({text}){const lines=text.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/,'').split('\n'),out=[];let i=0;while(i<lines.length){const line=lines[i];if(!line.trim()){i++;continue;}if(/^\x60{3}/.test(line)){const lang=line.slice(3).trim(),code=[];i++;while(i<lines.length&&!/^\x60{3}/.test(lines[i]))code.push(lines[i++]);i++;out.push(<Code key={i} text={code.join('\n')} language={lang}/>);continue;}if(/^#{1,6} /.test(line)){const n=line.match(/^#+/)[0].length;out.push(React.createElement('h'+Math.min(n+1,6),{key:i},inline(line.slice(n+1))));i++;continue;}if(line.startsWith('|')&&lines[i+1]?.match(/^\|[\s:|-]+\|$/)){const head=line.split('|').slice(1,-1).map(s=>s.trim()),rows=[];i+=2;while(i<lines.length&&lines[i].startsWith('|'))rows.push(lines[i++].split('|').slice(1,-1));out.push(<table key={i}><thead><tr>{head.map((s,j)=><th key={j}>{inline(s)}</th>)}</tr></thead><tbody>{rows.map((r,k)=><tr key={k}>{r.map((c,j)=><td key={j}>{inline(c)}</td>)}</tr>)}</tbody></table>);continue;}if(/^[-*] /.test(line)||/^\d+\. /.test(line)){const ordered=/^\d/.test(line),items=[];while(i<lines.length&&(ordered?/^\d+\. /:/^[-*] /).test(lines[i]))items.push(lines[i++].replace(/^(?:[-*]|\d+\.) /,''));out.push(React.createElement(ordered?'ol':'ul',{key:i},items.map((s,j)=><li key={j}>{inline(s)}</li>)));continue;}if(line.startsWith('> ')){out.push(<blockquote key={i}>{inline(line.slice(2))}</blockquote>);i++;continue;}const para=[line];i++;while(i<lines.length&&lines[i].trim()&&!/^(#|>|[-*] |\d+\. |\x60{3}|\|)/.test(lines[i]))para.push(lines[i++]);out.push(<p key={i}>{inline(para.join(' '))}</p>);}return <article className="ext-markdown">{out}</article>;}
function FileTree({files,selected,onSelect}){const [opened,setOpened]=useState(['references','scripts']);const root={};files.forEach(f=>{let cur=root;const parts=f.path.split('/');parts.forEach((p,i)=>{cur[p]??={path:parts.slice(0,i+1).join('/'),children:{},file:i===parts.length-1};cur=cur[p].children;});});function nodes(tree,depth=0){return Object.entries(tree).sort((a,b)=>Number(a[1].file)-Number(b[1].file)||a[0].localeCompare(b[0])).map(([name,n])=><div key={n.path}><button className={'ext-tree-row '+(selected===n.path?'active':'')} style={{paddingLeft:12+depth*17}} onClick={()=>n.file?onSelect(n.path):setOpened(all=>all.includes(n.path)?all.filter(x=>x!==n.path):[...all,n.path])} aria-expanded={n.file?undefined:opened.includes(n.path)} title={n.path}>{!n.file&&<Icon name={opened.includes(n.path)?'down':'right'} size={11}/>}<Icon name={n.file?(name.endsWith('.md')?'doc':'code'):'folder'} size={14}/><span>{name}</span></button>{!n.file&&opened.includes(n.path)&&nodes(n.children,depth+1)}</div>);}return <nav className="ext-file-tree" aria-label="技能文件树">{nodes(root)}</nav>;}
function SkillViewer({skill,onClose,onCompose,onInstall}){const [selected,setSelected]=useState(skill.files[0]?.path||'');const file=skill.files.find(f=>f.path===selected),ext=selected.split('.').pop();return <Dialog wide className="ext-skill-dialog" title={skill.name} description={skill.description} onClose={onClose} footer={<><span className="ext-footer-note">{skill.files.length} 个文件 · {skill.folder}</span><Button onClick={()=>onCompose('使用 '+skill.name+' 帮我完成一项工作。请先了解我的目标和现有材料。')}><Icon name="chat" size={14}/>在对话中使用</Button>{!skill.installed&&<Button primary onClick={onInstall}>安装技能</Button>}</>}><div className="ext-skill-files"><aside><div className="ext-tree-title"><Icon name="folder" size={14}/>{skill.name}</div><FileTree files={skill.files} selected={selected} onSelect={setSelected}/></aside><div className="ext-file-content"><div className="ext-file-toolbar"><span>{selected}</span><small>{ext==='md'?'Markdown · 阅读视图':ext==='json'?'JSON':ext?.toUpperCase()||'文本'}</small></div><div className="ext-file-scroll">{file?(ext==='md'?<Markdown text={file.content}/>:<Code text={file.content} language={ext}/>):<Empty title="选择一个文件" description="查看技能说明与参考资料。"/>}</div></div></div></Dialog>;}
window.ExtensionUI={Icon,IconButton,Button,Switch,Mark,Dialog,Empty,KeyValues,Code,Markdown,SkillViewer};
})();
