setChatFontSize was setting --hermes-chat-font-size on this.containerEl (outer Obsidian view wrapper), but the CSS default was defined on .hermes-chat-container (inner child). The child's explicit default overrode the inherited value, so the font size slider had no visible effect. Now targets .hermes-chat-container directly.
14 lines
19 KiB
JavaScript
14 lines
19 KiB
JavaScript
"use strict";var I=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var U=(r,t)=>{for(var s in t)I(r,s,{get:t[s],enumerable:!0})},R=(r,t,s,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of N(t))!H.call(r,i)&&i!==s&&I(r,i,{get:()=>t[i],enumerable:!(e=P(t,i))||e.enumerable});return r};var O=r=>R(I({},"__esModule",{value:!0}),r);var V={};U(V,{default:()=>b});module.exports=O(V);var L=require("obsidian");var g=require("obsidian"),B=[{trigger:"/prep-session",description:"Prep the next D&D session",prompt:"Run the full session-prep pipeline for the next SKT session: scan recent session notes, identify loose threads, generate a session guide with modular flow-chart sections."},{trigger:"/sync-content",description:"Sync Content Sink notes to vault",prompt:"/run content-sink-sync"},{trigger:"/lint-wiki",description:"Lint the campaign wiki",prompt:"Scan the campaign wiki for broken links, orphaned entities, missing templates, and formatting inconsistencies. Report a prioritized list of issues."}],D={gatewayUrl:"http://192.168.1.12:8642/v1",apiKey:"0f224b188b52e8728d135ec339f8db8dfbf82d89a60021dcfe20d91c6174cf9b",showModelInfo:!0,enableContextInjection:!0,chatFontSize:14,customCommands:[],chatMessages:[]},x=class extends g.PluginSettingTab{plugin;constructor(t,s){super(t,s),this.plugin=s}display(){let{containerEl:t}=this;t.empty(),t.createEl("h2",{text:"Hermes Chat Settings"}),new g.Setting(t).setName("Gateway URL").setDesc("Base URL of your Hermes Gateway API server (e.g. http://192.168.1.12:8642/v1)").addText(i=>i.setPlaceholder("http://192.168.1.12:8642/v1").setValue(this.plugin.settings.gatewayUrl).onChange(async n=>{this.plugin.settings.gatewayUrl=n,await this.plugin.saveData(this.plugin.settings)})),new g.Setting(t).setName("API Key").setDesc("API key for authenticating with the Hermes Gateway").addText(i=>i.setPlaceholder("Enter your API key").setValue(this.plugin.settings.apiKey).onChange(async n=>{this.plugin.settings.apiKey=n,await this.plugin.saveData(this.plugin.settings)})),new g.Setting(t).setName("Show model info").setDesc("Display the current model name in the status bar").addToggle(i=>i.setValue(this.plugin.settings.showModelInfo).onChange(async n=>{this.plugin.settings.showModelInfo=n,await this.plugin.saveData(this.plugin.settings)})),new g.Setting(t).setName("Enable context injection").setDesc("Automatically send the active note as context with each message").addToggle(i=>i.setValue(this.plugin.settings.enableContextInjection).onChange(async n=>{this.plugin.settings.enableContextInjection=n,await this.plugin.saveData(this.plugin.settings)})),new g.Setting(t).setName("Chat font size").setDesc("Font size for chat messages and input (10\u201424 px)").addSlider(i=>i.setLimits(10,24,1).setValue(this.plugin.settings.chatFontSize).setDynamicTooltip().onChange(async n=>{this.plugin.settings.chatFontSize=n,await this.plugin.saveSettings()})),t.createEl("h3",{text:"Slash Commands"}),t.createEl("p",{text:"Type / in the chat input to see available commands. Built-in: /prep-session, /sync-content, /lint-wiki. Add custom commands below as JSON."}),t.createDiv("setting-item-description").setText('Format: [{"trigger":"/my-cmd","description":"What it does","prompt":"Full prompt text..."}]');let e=t.createEl("textarea");e.setAttribute("rows","6"),e.style.width="100%",e.style.fontFamily="monospace",e.style.fontSize="12px",e.value=JSON.stringify(this.plugin.settings.customCommands,null,2),e.addEventListener("change",async()=>{try{let i=JSON.parse(e.value);Array.isArray(i)&&(this.plugin.settings.customCommands=i,await this.plugin.saveData(this.plugin.settings))}catch{}})}};var u=require("obsidian"),d="hermes-chat-view",y=class extends u.ItemView{apiClient=null;contextBridge=null;persistence=null;chatState;messagesContainer=null;statusBar=null;loadingEl=null;textarea=null;sendButton=null;capturedNote=null;slashCommands=[];suggestEl=null;suggestIndex=-1;filteredCommands=[];constructor(t,s,e,i){super(t),this.apiClient=s??null,this.contextBridge=e??null,this.persistence=i??null,this.chatState={sessionId:null,messages:[],isLoading:!1,model:"hermes-agent",contextActive:!1}}getViewType(){return d}getDisplayText(){return"Hermes"}async onOpen(){let t=this.containerEl.children[1];t.empty(),t.addClass("hermes-chat-container"),this.messagesContainer=t.createDiv("chat-messages"),this.messagesContainer.id="chat-messages",this.containerEl.addEventListener("focusin",()=>{this.contextBridge&&(this.capturedNote=this.contextBridge.snapshot())}),this.registerEvent(this.app.workspace.on("active-leaf-change",e=>{e?.view instanceof u.MarkdownView&&this.contextBridge&&(this.capturedNote=this.contextBridge.snapshot())})),this.loadingEl=this.messagesContainer.createDiv("chat-loading"),this.loadingEl.setText("Thinking\u2026"),this.loadingEl.style.display="none";let s=t.createDiv("chat-input-area");if(this.textarea=s.createEl("textarea"),this.textarea.placeholder="Type your message\u2026",this.textarea.setAttribute("rows","1"),this.textarea.addEventListener("keydown",e=>{if(this.suggestEl&&this.suggestEl.style.display!=="none"){if(e.key==="ArrowDown"){e.preventDefault(),this.suggestMove(1);return}if(e.key==="ArrowUp"){e.preventDefault(),this.suggestMove(-1);return}if(e.key==="Enter"&&!e.shiftKey){e.preventDefault(),this.suggestIndex>=0&&this.suggestIndex<this.filteredCommands.length?this.suggestSelect(this.filteredCommands[this.suggestIndex]):this.suggestClose();return}if(e.key==="Escape"){e.preventDefault(),this.suggestClose();return}if(e.key==="Tab"){e.preventDefault(),this.suggestIndex>=0&&this.suggestIndex<this.filteredCommands.length&&this.suggestSelect(this.filteredCommands[this.suggestIndex]);return}}e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),this.handleSend())}),this.textarea.addEventListener("input",()=>{this.checkSlashTrigger()}),this.sendButton=s.createEl("button"),this.sendButton.setText("Send"),this.sendButton.addEventListener("click",()=>this.handleSend()),this.suggestEl=t.createDiv("hermes-slash-suggest"),this.suggestEl.style.display="none",this.suggestEl.style.position="absolute",this.suggestEl.style.zIndex="1000",this.statusBar=t.createDiv("chat-status-bar"),this.updateStatus(this.chatState.model,null),this.persistence){let e=this.persistence.loadMessages();if(e.length>0)for(let i of e)await this.appendMessage(i.role,i.content)}}async onClose(){await this.persistence?.saveMessages(this.chatState.messages)}setApiClient(t){this.apiClient=t}setContextActive(t){this.chatState.contextActive=t}setSlashCommands(t){this.slashCommands=t}setChatFontSize(t){let s=this.containerEl.querySelector(".hermes-chat-container");s&&s.style.setProperty("--hermes-chat-font-size",`${t}px`)}async appendMessage(t,s){if(!this.messagesContainer)return;let e={id:`${Date.now()}-${Math.random().toString(36).slice(2,8)}`,role:t,content:s,timestamp:Date.now()};this.chatState.messages.push(e),this.persistence?.saveMessages(this.chatState.messages);let i=this.messagesContainer.createDiv("chat-message");i.addClass(`chat-message-${t}`),i.createDiv("chat-message-role").setText(t==="user"?"You":t==="assistant"?"Hermes":"System");let a=i.createDiv("chat-message-content");await u.MarkdownRenderer.render(this.app,s,a,"",this),this.scrollToBottom()}setLoading(t){this.chatState.isLoading=t,this.loadingEl&&(this.loadingEl.style.display=t?"block":"none"),this.textarea&&(this.textarea.disabled=t),this.sendButton&&(this.sendButton.disabled=t)}updateStatus(t,s){if(this.chatState.model=t,this.chatState.sessionId=s,this.statusBar){let e=s?`Session: ${s}`:"Session: \u2014";this.statusBar.setText(`Model: ${t} | ${e}`)}}clearChat(){if(!this.messagesContainer)return;let t=Array.from(this.messagesContainer.children);for(let s of t)s!==this.loadingEl&&s.remove();this.chatState.messages=[]}scrollToBottom(){this.messagesContainer&&(this.messagesContainer.scrollTop=this.messagesContainer.scrollHeight)}checkSlashTrigger(){if(!this.textarea)return;let t=this.textarea.value,s=this.textarea.selectionStart,e=s-1;for(;e>=0&&t[e]!==" "&&t[e]!==`
|
|
`;)e--;e++;let i=t.slice(e,s);if(i.startsWith("/")&&(e===0||t[e-1]===" "||t[e-1]===`
|
|
`)){let n=i.slice(1).toLowerCase();if(this.filteredCommands=this.slashCommands.filter(a=>a.trigger.toLowerCase().includes(n)||a.description.toLowerCase().includes(n)),this.filteredCommands.length>0){this.suggestShow();return}}this.suggestClose()}suggestShow(){if(!this.suggestEl||!this.textarea)return;this.suggestIndex=-1,this.suggestEl.empty();for(let e=0;e<this.filteredCommands.length;e++){let i=this.filteredCommands[e],n=this.suggestEl.createDiv("hermes-slash-item");n.createSpan("hermes-slash-trigger").setText(i.trigger),n.createSpan("hermes-slash-desc").setText(i.description),n.addEventListener("click",()=>this.suggestSelect(i)),n.addEventListener("mouseenter",()=>{this.suggestIndex=e,this.suggestHighlight()})}let t=this.textarea.getBoundingClientRect(),s=this.containerEl.getBoundingClientRect();this.suggestEl.style.left=`${t.left-s.left}px`,this.suggestEl.style.top=`${t.bottom-s.top}px`,this.suggestEl.style.width=`${t.width}px`,this.suggestEl.style.display="block"}suggestClose(){this.suggestEl&&(this.suggestEl.style.display="none",this.suggestEl.empty()),this.suggestIndex=-1,this.filteredCommands=[]}suggestMove(t){this.filteredCommands.length!==0&&(this.suggestIndex+=t,this.suggestIndex<0&&(this.suggestIndex=this.filteredCommands.length-1),this.suggestIndex>=this.filteredCommands.length&&(this.suggestIndex=0),this.suggestHighlight())}suggestHighlight(){if(!this.suggestEl)return;this.suggestEl.querySelectorAll(".hermes-slash-item").forEach((s,e)=>{e===this.suggestIndex?s.addClass("hermes-slash-item-active"):s.removeClass("hermes-slash-item-active")})}suggestSelect(t){if(!this.textarea)return;let s=this.textarea.value,e=this.textarea.selectionStart,i=e-1;for(;i>=0&&s[i]!==" "&&s[i]!==`
|
|
`;)i--;i++;let n=s.slice(0,i),a=s.slice(e);this.textarea.value=n+t.prompt+a,this.suggestClose(),this.textarea.focus()}resolveSlashCommand(t){let s=t.trim();for(let e of this.slashCommands)if(s.startsWith(e.trigger))return e.prompt+s.slice(e.trigger.length);return t}async handleSend(){if(!this.textarea)return;let t=this.textarea.value.trim();if(!t)return;this.textarea.value="";let s=this.resolveSlashCommand(t);if(await this.appendMessage("user",s),!this.apiClient){await this.appendMessage("assistant","[API client not configured. Check plugin settings.]");return}this.setLoading(!0);let e=this.messagesContainer?.createDiv("chat-message chat-message-assistant"),i=e?.createDiv("chat-message-role");i&&i.setText("Hermes");let n=e?.createDiv("chat-message-content");n&&n.setText("");let a="";try{let c;if(this.chatState.contextActive&&this.capturedNote){let o=this.capturedNote;o.selection?c=[{role:"system",content:`[Selection from ${o.vault}/${o.path}]
|
|
|
|
${o.selection}`},{role:"user",content:s}]:c=[{role:"system",content:[`[Current note: ${o.vault}/${o.path}]`,`Use mcp_obsidian_get_vault_document(vault_name="${o.vault}", doc_path="${o.path}") to read the full contents.`].join(`
|
|
`)},{role:"user",content:s}]}else c=this.contextBridge?this.contextBridge.buildMessages(s,this.chatState.contextActive):[{role:"user",content:s}];let h=await this.apiClient.streamChat(c,o=>{a+=o,n&&n.setText(a),this.scrollToBottom()});n&&(n.empty(),await u.MarkdownRenderer.render(this.app,a,n,"",this));let p={id:`${Date.now()}-${Math.random().toString(36).slice(2,8)}`,role:"assistant",content:a,timestamp:Date.now()};this.chatState.messages.push(p),this.persistence?.saveMessages(this.chatState.messages),h.sessionId&&(this.chatState.sessionId=h.sessionId,this.updateStatus(h.model,h.sessionId))}catch(c){n&&n.setText(`Error: ${c.message||"Unknown error"}`)}finally{this.setLoading(!1)}}};var C=class{baseUrl;apiKey;model;currentSessionId;constructor(t){this.baseUrl=t.baseUrl.replace(/\/+$/,""),this.apiKey=t.apiKey,this.model=t.model,this.currentSessionId=null}updateConfig(t){t.baseUrl!==void 0&&(this.baseUrl=t.baseUrl.replace(/\/+$/,"")),t.apiKey!==void 0&&(this.apiKey=t.apiKey),t.model!==void 0&&(this.model=t.model)}getSessionId(){return this.currentSessionId}get rootUrl(){return this.baseUrl.endsWith("/v1")?this.baseUrl.slice(0,-3):new URL("/",this.baseUrl).href.replace(/\/+$/,"")}async fetchWithTimeout(t,s,e=6e4){let i=new AbortController,n=setTimeout(()=>i.abort(),e);try{return await fetch(t,{...s,signal:i.signal})}finally{clearTimeout(n)}}async extractErrorBody(t){let s="";if((t.headers.get("content-type")??"").includes("application/json"))try{let i=await t.json();if(i.error?.message?s=i.error.message:typeof i.message=="string"?s=i.message:typeof i.error=="string"&&(s=i.error),s)return s}catch{}try{let i=await t.text();if(i.trim())return s=i.trim().substring(0,300),s}catch{}return t.statusText||`HTTP ${t.status}`}async chat(t,s=!1){let e={Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"};this.currentSessionId&&(e["X-Hermes-Session-Id"]=this.currentSessionId);let i=await this.fetchWithTimeout(`${this.baseUrl}/chat/completions`,{method:"POST",headers:e,body:JSON.stringify({model:this.model,messages:t,stream:s})});if(!i.ok){let p=await this.extractErrorBody(i);throw new Error(`API error ${i.status}: ${p}`)}let n=i.headers.get("X-Hermes-Session-Id");n&&(this.currentSessionId=n);let a=await i.json();if(a.error)throw new Error(a.error.message||"Unknown API error");return{content:a.choices?.[0]?.message?.content??"",sessionId:this.currentSessionId??"",model:a.model??this.model,usage:a.usage}}async streamChat(t,s){let e={Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"};this.currentSessionId&&(e["X-Hermes-Session-Id"]=this.currentSessionId);let i=await this.fetchWithTimeout(`${this.baseUrl}/chat/completions`,{method:"POST",headers:e,body:JSON.stringify({model:this.model,messages:t,stream:!0})});if(!i.ok){let m=await this.extractErrorBody(i);throw new Error(`API error ${i.status}: ${m}`)}let n=i.headers.get("X-Hermes-Session-Id");n&&(this.currentSessionId=n);let a=i.body?.getReader();if(!a)throw new Error("Response body is not readable");let c=new TextDecoder("utf-8"),h="",p="",o=this.model,T;for(;;){let{done:m,value:w}=await a.read();if(m)break;h+=c.decode(w,{stream:!0});let f=h.split(`
|
|
|
|
`);h=f.pop()??"";for(let k of f){let v=k.split(`
|
|
`);for(let M of v){if(!M.startsWith("data: "))continue;let A=M.slice(6).trim();if(A!=="[DONE]")try{let l=JSON.parse(A);if(l.error)throw new Error(l.error.message||"Stream error");let E=l.choices?.[0]?.delta?.content;E&&(p+=E,s(E)),l.model&&(o=l.model),l.usage&&(T=l.usage)}catch(l){if(l instanceof Error&&l.message!=="Unexpected token"&&!l.message.includes("JSON"))throw l}}}}if(h.trim()){let m=h.split(`
|
|
`);for(let w of m){if(!w.startsWith("data: "))continue;let f=w.slice(6).trim();if(f!=="[DONE]")try{let v=JSON.parse(f).choices?.[0]?.delta?.content;v&&(p+=v,s(v))}catch{}}}return{content:p,sessionId:this.currentSessionId??"",model:o,usage:T}}async getModels(){let t=await this.fetchWithTimeout(`${this.baseUrl}/models`,{headers:{Authorization:`Bearer ${this.apiKey}`}});if(!t.ok){let e=await this.extractErrorBody(t);throw new Error(`Failed to fetch models: ${t.status} \u2014 ${e}`)}return(await t.json()).data??[]}async getCapabilities(){let t=await this.fetchWithTimeout(`${this.baseUrl}/capabilities`,{headers:{Authorization:`Bearer ${this.apiKey}`}});if(!t.ok){let s=await this.extractErrorBody(t);throw new Error(`Failed to fetch capabilities: ${t.status} \u2014 ${s}`)}return t.json()}async healthCheck(){try{let t=await this.fetchWithTimeout(`${this.rootUrl}/health`,{},1e4);return t.ok?(await t.json()).status==="ok":!1}catch{return!1}}};var $=require("obsidian"),S=class{app;constructor(t){this.app=t}getActiveNoteContext(){let t=this.app.workspace.activeEditor;if(!t||!t.editor)return null;let s=this.app.workspace.activeLeaf;if(!s||!(s.view instanceof $.MarkdownView))return null;let e=s.view.file;return e?{vault:this.app.vault.getName(),path:e.path,title:e.basename}:null}snapshot(){let t=this.getActiveNoteContext();return t?{...t,selection:this.getSelectionContext()}:null}getSelectionContext(){let t=this.app.workspace.activeEditor;return!t||!t.editor?null:t.editor.getSelection()||null}formatContextMessage(t){return{role:"system",content:[`[Current note: ${t.vault}/${t.path}]`,`Use mcp_obsidian_get_vault_document(vault_name="${t.vault}", doc_path="${t.path}") to read the full contents.`].join(`
|
|
`)}}buildMessages(t,s){let e=[];if(s){let i=this.getSelectionContext();if(i){let n=this.getActiveNoteContext(),a=n?`${n.vault}/${n.path}`:"current note";e.push({role:"system",content:`[Selection from ${a}]
|
|
|
|
${i}`})}else{let n=this.getActiveNoteContext();n&&e.push(this.formatContextMessage(n))}}return e.push({role:"user",content:t}),e}};var b=class extends L.Plugin{settings;statusBarItem=null;contextBridge=null;async onload(){await this.loadSettings(),this.registerView(d,t=>{let s=new C(this.getGatewayConfig()),e={loadMessages:()=>this.settings.chatMessages,saveMessages:async n=>{this.settings.chatMessages=n,await this.saveData(this.settings)}},i=new y(t,s,this.contextBridge,e);return i.setContextActive(this.settings.enableContextInjection),i.setSlashCommands(this.getAllCommands()),i.setChatFontSize(this.settings.chatFontSize),i}),this.addSettingTab(new x(this.app,this)),this.addRibbonIcon("message-square","Open Hermes Chat",()=>{this.activateView()}),this.settings.showModelInfo&&(this.statusBarItem=this.addStatusBarItem(),this.statusBarItem.setText("Hermes: connecting...")),this.contextBridge=new S(this.app),this.addCommand({id:"open-hermes-chat",name:"Open Hermes Chat",callback:()=>{this.activateView()}}),this.addCommand({id:"open-hermes-chat-new-tab",name:"Open Hermes Chat in New Tab",callback:()=>{this.openNewTab()}}),this.addCommand({id:"send-note-as-context",name:"Send Current Note as Context",callback:()=>{let t=this.contextBridge?.getActiveNoteContext();t?(this.activateView(),console.log(`Context ready: vault=${t.vault}, path=${t.path}`)):console.log("No active note to send as context")}}),this.statusBarItem&&(this.statusBarItem.setText("Hermes: checking..."),new C(this.getGatewayConfig()).healthCheck().then(s=>{this.statusBarItem&&this.statusBarItem.setText(s?"Hermes: connected":"Hermes: unreachable")}))}async onunload(){this.app.workspace.detachLeavesOfType(d)}async activateView(){let{workspace:t}=this.app,s=t.getLeavesOfType(d);if(s.length>0){t.revealLeaf(s[0]);return}let e=t.getRightLeaf(!1);e&&(await e.setViewState({type:d,active:!0}),t.revealLeaf(e))}async openNewTab(){let t=this.app.workspace.getRightLeaf(!1);t&&(await t.setViewState({type:d,active:!0}),this.app.workspace.revealLeaf(t))}getGatewayConfig(){return{baseUrl:this.settings.gatewayUrl,apiKey:this.settings.apiKey,model:"hermes-agent"}}async loadSettings(){this.settings=Object.assign({},D,await this.loadData())}async saveSettings(){await this.saveData(this.settings),this.syncSettingsToViews()}syncSettingsToViews(){let t=this.app.workspace.getLeavesOfType(d),s=this.getAllCommands();for(let e of t)e.view instanceof y&&(e.view.setContextActive(this.settings.enableContextInjection),e.view.setSlashCommands(s),e.view.setChatFontSize(this.settings.chatFontSize))}getAllCommands(){return[...B,...this.settings.customCommands]}};
|