Files
nc-talk-ai/js/educai-admin.mjs
T
Pascal Kienast f4d56bd354 Fix last EDUCAI UI strings → Talk AI
- AdminSettings.vue: 'bundled EDUCAI icon pair' → 'Talk AI icon pair'
- PersonalBots.vue: console log prefix [EDUCAI] → [TalkAI]
- rebuilt frontend bundles
2026-07-12 02:39:55 +02:00

9 lines
101 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function(){"use strict";try{if(typeof document<"u"){var a=document.createElement("style");a.appendChild(document.createTextNode(".admin-settings[data-v-f1e4250d]{margin-top:40px;border:1px solid var(--color-border);border-radius:8px;overflow:hidden}.settings-title[data-v-f1e4250d]{margin:0;padding:16px 20px;background:var(--color-background-dark);font-size:16px;font-weight:600;display:flex;align-items:center;gap:8px}.settings-body[data-v-f1e4250d]{padding:20px;display:flex;flex-direction:column;gap:12px}.accordion-section[data-v-f1e4250d]{border-top:1px solid var(--color-border);background:var(--color-main-background)}.settings-title+.settings-body .accordion-section[data-v-f1e4250d]:first-child,.admin-settings>.accordion-section[data-v-f1e4250d]:first-of-type{border-top:0}.accordion-header[data-v-f1e4250d]{width:100%;padding:16px 20px;border:0;background:transparent;color:var(--color-main-text);cursor:pointer;display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:16px;text-align:left;font:inherit}.accordion-header[data-v-f1e4250d]:hover,.accordion-header[data-v-f1e4250d]:focus-visible{background:var(--color-background-hover)}.accordion-header[data-v-f1e4250d]:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px}.accordion-heading[data-v-f1e4250d]{min-width:0;display:flex;flex-direction:column;gap:3px}.accordion-title[data-v-f1e4250d]{font-size:15px;font-weight:700}.accordion-description[data-v-f1e4250d]{font-size:13px;color:var(--color-text-lighter);line-height:1.35}.accordion-meta[data-v-f1e4250d]{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-radius:999px;padding:3px 9px;background:var(--color-background-dark);color:var(--color-text-lighter);font-size:12px;font-weight:600}.accordion-meta--warning[data-v-f1e4250d]{background:#eca70026;color:var(--color-main-text)}.accordion-chevron[data-v-f1e4250d]{font-size:22px;line-height:1;color:var(--color-text-lighter);transform:rotate(0);transition:transform .12s ease}.accordion-section--open .accordion-chevron[data-v-f1e4250d]{transform:rotate(90deg)}.accordion-panel[data-v-f1e4250d]{padding:4px 20px 20px}.settings-body .accordion-header[data-v-f1e4250d],.settings-body .accordion-panel[data-v-f1e4250d]{padding-left:0;padding-right:0}.settings-body .accordion-section[data-v-f1e4250d]{border-radius:6px;border:1px solid var(--color-border)}.settings-body>.button.primary[data-v-f1e4250d]{align-self:flex-start;margin-top:8px}.form-group[data-v-f1e4250d]{margin-bottom:20px}.form-group label[data-v-f1e4250d]{display:block;margin-bottom:6px;font-weight:600}.form-group input[data-v-f1e4250d],.form-group select[data-v-f1e4250d]{width:100%;padding:10px;border:1px solid var(--color-border);border-radius:3px;font-family:inherit;font-size:14px}.form-group input[data-v-f1e4250d]:focus,.form-group select[data-v-f1e4250d]:focus{outline:none;border-color:var(--color-primary)}.hint[data-v-f1e4250d]{margin:6px 0 0;font-size:13px;color:var(--color-text-lighter)}.button.primary[data-v-f1e4250d]{background-color:var(--color-primary);color:#fff;border:none;padding:10px 20px;border-radius:3px;cursor:pointer;font-size:14px}.button.primary[data-v-f1e4250d]:hover:not(:disabled){background-color:var(--color-primary-element-light)}.button.primary[data-v-f1e4250d]:disabled{opacity:.6;cursor:not-allowed}.app-icon-config[data-v-f1e4250d]{display:flex;flex-direction:column;gap:16px}.icon-mode-tabs[data-v-f1e4250d]{display:flex;flex-wrap:wrap;gap:8px}.icon-mode-tab[data-v-f1e4250d]{border:1px solid var(--color-border);border-radius:6px;background:var(--color-main-background);color:var(--color-main-text);padding:8px 12px;font:inherit;font-weight:600;cursor:pointer}.icon-mode-tab[data-v-f1e4250d]:hover,.icon-mode-tab[data-v-f1e4250d]:focus-visible{border-color:var(--color-primary)}.icon-mode-tab--active[data-v-f1e4250d]{background:var(--color-primary);border-color:var(--color-primary);color:#fff}.app-icon-preview-grid[data-v-f1e4250d]{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.app-icon-preview-card[data-v-f1e4250d]{border:1px solid var(--color-border);border-radius:6px;padding:12px;display:flex;align-items:center;justify-content:space-between;gap:12px}.app-icon-preview-card--light[data-v-f1e4250d]{background:var(--color-main-background);color:var(--color-main-text)}.app-icon-preview-card--dark[data-v-f1e4250d]{background:#111;color:#fff}.app-icon-preview-card__label[data-v-f1e4250d]{font-size:13px;font-weight:700}.app-icon-preview-card__frame[data-v-f1e4250d]{width:52px;height:52px;border-radius:6px;display:inline-flex;align-items:center;justify-content:center;background:#7f7f7f1f;flex:0 0 auto}.app-icon-preview-card__frame img[data-v-f1e4250d]{display:block;max-width:34px;max-height:34px;object-fit:contain}.app-icon-preview-card__missing[data-v-f1e4250d]{font-size:11px;font-weight:700;opacity:.7}.app-icon-mode-panel[data-v-f1e4250d]{border-top:1px solid var(--color-border);padding-top:16px}.app-icon-input-row[data-v-f1e4250d]{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center}.app-icon-file-input[data-v-f1e4250d]{display:none}.app-icon-actions[data-v-f1e4250d]{margin-bottom:0}.form-group label.checkbox[data-v-f1e4250d]{display:inline-flex;align-items:center;gap:8px}.form-group label.checkbox input[type=checkbox][data-v-f1e4250d]{width:16px;height:16px;margin:0;vertical-align:middle}.form-group select[multiple][data-v-f1e4250d]{min-height:240px}.section-divider[data-v-f1e4250d]{border-top:1px solid var(--color-border);margin:28px 0 20px}.section-heading[data-v-f1e4250d]{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin:0 0 16px}.section-heading h4[data-v-f1e4250d]{margin:0;font-size:15px;font-weight:600}.section-heading .hint[data-v-f1e4250d]{flex-basis:100%;margin:0}.form-grid[data-v-f1e4250d]{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:20px}.section-body[data-v-f1e4250d]{padding:0 0 20px}.tool-list[data-v-f1e4250d]{display:flex;flex-direction:column;gap:16px}.tool-row[data-v-f1e4250d]{display:flex;justify-content:space-between;align-items:flex-start;gap:24px;border:1px solid var(--color-border);border-radius:6px;padding:16px;background:var(--color-background-dark)}.tool-row--disabled[data-v-f1e4250d]{opacity:.7}.tool-info[data-v-f1e4250d]{flex:1;min-width:0}.tool-name[data-v-f1e4250d]{display:flex;align-items:center;gap:10px;margin-bottom:6px}.tool-name__label[data-v-f1e4250d]{font-weight:600;font-size:15px}.badge[data-v-f1e4250d]{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600}.badge-success[data-v-f1e4250d]{background:#00ad7f1f;color:#096}.badge-muted[data-v-f1e4250d]{background:#7878781f;color:#555}.tool-actions[data-v-f1e4250d]{display:flex;flex-direction:column;gap:10px;min-width:160px;align-items:flex-end}.button.danger[data-v-f1e4250d]{background:var(--color-error);color:#fff}.button-row[data-v-f1e4250d]{display:flex;gap:12px;flex-wrap:wrap}.tool-form[data-v-f1e4250d]{border-top:1px solid var(--color-border);background:var(--color-main-background);padding:20px}.tool-form form[data-v-f1e4250d]{display:flex;flex-direction:column;gap:16px}.tool-form textarea[data-v-f1e4250d]{font-family:monospace}.tool-test-result[data-v-f1e4250d]{margin:16px 20px 20px;padding:12px;border-radius:6px;background:var(--color-background-dark);color:var(--color-text);max-height:240px;overflow:auto;font-size:12px}code[data-v-f1e4250d]{font-family:monospace;font-size:12px;background:#0000000d;padding:1px 4px;border-radius:4px}.success-text[data-v-f1e4250d]{color:var(--color-success, #46ba61);font-size:13px}.error-text[data-v-f1e4250d]{color:var(--color-error, #e53e3e);font-size:13px}.button-row[data-v-f1e4250d]{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:20px}.supported-formats[data-v-f1e4250d]{margin-top:8px;margin-bottom:16px}.supported-formats .hint strong[data-v-f1e4250d]{color:var(--color-text)}.embedding-warning[data-v-f1e4250d]{margin:-4px 0 20px;padding:12px;border:1px solid var(--color-warning, #eca700);border-radius:6px;background:#eca7001f}.embedding-warning p[data-v-f1e4250d]{margin:6px 0 0;font-size:13px;color:var(--color-main-text)}.rate-limit-status[data-v-f1e4250d]{margin:20px 0}.rate-limit-hint[data-v-f1e4250d]{margin-top:16px}.rate-limit-subsection[data-v-f1e4250d]{margin-top:20px}.rate-limit-subsection__header h5[data-v-f1e4250d]{margin:0 0 6px;font-size:14px}.rate-limit-subsection__header .hint[data-v-f1e4250d]{margin:0}.status-grid[data-v-f1e4250d]{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:16px;margin-top:16px}.status-grid--compact[data-v-f1e4250d]{margin-top:12px}.status-card[data-v-f1e4250d]{padding:16px;border:1px solid var(--color-border);border-radius:8px;text-align:center;background:var(--color-background-dark)}.status-card.status-ok[data-v-f1e4250d]{border-color:var(--color-success, #46ba61);background:#46ba611a}.status-card.status-limited[data-v-f1e4250d]{border-color:var(--color-warning, #eca700);background:#eca7001a}.status-value[data-v-f1e4250d]{font-size:24px;font-weight:600;color:var(--color-main-text);margin-bottom:4px}.status-label[data-v-f1e4250d]{font-size:12px;color:var(--color-text-lighter);text-transform:uppercase;letter-spacing:.5px}.bot-table[data-v-f1e4250d]{display:flex;flex-direction:column;gap:8px}.bot-row[data-v-f1e4250d]{display:grid;grid-template-columns:1.2fr 1fr 1fr 1fr 1fr 1fr;align-items:center;gap:8px;padding:10px 12px;border:1px solid var(--color-border);border-radius:6px;background:var(--color-main-background)}.bot-row--header[data-v-f1e4250d]{font-weight:700;background:var(--color-background-dark)}.actions-col[data-v-f1e4250d]{display:flex;gap:8px;justify-content:flex-end}.temperature-presets[data-v-f1e4250d]{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.status-pill[data-v-f1e4250d]{padding:2px 8px;border-radius:10px;font-size:12px;text-transform:capitalize;display:inline-block}.status-pill.status-draft[data-v-f1e4250d]{background:var(--color-background-dark);color:var(--color-text-lighter)}.status-pill.status-pending[data-v-f1e4250d]{background:var(--color-warning);color:#000}.status-pill.status-approved[data-v-f1e4250d]{background:var(--color-success);color:#000}.status-pill.status-personal[data-v-f1e4250d]{background:var(--color-primary-element-light);color:var(--color-primary-element-dark)}@media(max-width:700px){.accordion-header[data-v-f1e4250d]{grid-template-columns:minmax(0,1fr) auto;gap:10px}.accordion-meta[data-v-f1e4250d]{grid-column:1 / -1;max-width:100%;width:fit-content}.tool-row[data-v-f1e4250d],.bot-row[data-v-f1e4250d]{grid-template-columns:1fr}.tool-row[data-v-f1e4250d]{flex-direction:column}.app-icon-input-row[data-v-f1e4250d]{grid-template-columns:1fr}.tool-actions[data-v-f1e4250d],.actions-col[data-v-f1e4250d]{width:100%;align-items:stretch;justify-content:flex-start}}")),document.head.appendChild(a)}}catch(o){console.error("vite-plugin-css-injected-by-js",o)}})();
import{a as h,s as f}from"./appIconRuntime-ltIyK5iP.chunk.mjs";import{B as y}from"./BotForm-S-ACeDfi.chunk.mjs";import{n as C,c,a as r,s as p,b as n,A as S,V as w}from"./branding-cqWE0Otc.chunk.mjs";const A={name:"AdminSettings",components:{BotForm:y},data(){return{APP_DISPLAY_NAME:S,saving:!1,settings:{apiKey:"",apiProvider:"custom",apiEndpoint:"",secondaryApiEndpoint:"",secondaryApiKey:"",defaultModel:"llama-3.3-70b-instruct",defaultTemperature:.2,fallbackModel:"",appIconUrl:"",appIconMode:"default",appIconBlackUrl:"",appIconWhiteUrl:"",llmChatTimeout:90,llmStreamTimeout:240,llmModelsTimeout:20,webhookSecret:"",allowMultipleModels:!1,allowedModels:[],embeddingApiEndpoint:"",embeddingApiKey:"",embeddingModel:"",embeddingRateLimitMode:"inherit",embeddingRateLimitSecond:null,embeddingRateLimitMinute:100,embeddingRateLimitHour:2e3,embeddingRateLimitDay:4e3,ragChunkSize:750,ragChunkOverlap:50,ragEnabled:!1,catalogueEnabled:!1,catalogueApiEndpoint:"",catalogueReindexHours:24,doclingEnabled:!1,doclingApiEndpoint:"",doclingApiKey:"",visionApiEndpoint:"",visionApiKey:"",visionModel:"",speechApiEndpoint:"",speechApiKey:"",speechModel:"",rateLimitEnabled:!1,rateLimitSecond:null,rateLimitMinute:30,rateLimitHour:200,rateLimitDay:1e3,rateLimitQueueMessage:"",conversationContextTokens:8e3},openSections:{appearance:!0,essentials:!0,modelEndpoints:!1,fallbackTimeouts:!1,rag:!1,docling:!1,media:!1,limits:!1,memory:!1,tools:!1,bots:!1},availableModels:[],availableModelOptions:[],loadingModels:!1,modelLoadError:"",modelSelectSize:12,loadingTools:!1,toolSaving:!1,toolTesting:!1,toolFormVisible:!1,tools:[],toolForm:{id:null,name:"",mcpEndpointUrl:"",description:"",authentication:"",enabled:!1},toolHasStoredAuth:!1,toolTestResult:"",doclingTesting:!1,doclingTestResult:null,doclingHasStoredApiKey:!1,visionTesting:!1,visionTestResult:null,speechTesting:!1,speechTestResult:null,rateLimitLoading:!1,rateLimitStatus:null,queueProcessing:!1,globalEmbeddingReindexing:!1,embeddingConfigBaseline:{apiProvider:"custom",apiEndpoint:"",embeddingApiEndpoint:"",embeddingModel:"",hasApiKey:!1,hasEmbeddingApiKey:!1},loadingAllBots:!1,allBots:[],adminEditingBot:null,uploadingAppIconVariant:"",appIconUploadUrls:{black:"",white:""},appIconPreviewUrls:{black:"",white:""},appIconPreviewVersion:{black:0,white:0}}},computed:{appIconSummary(){return this.settings.appIconMode==="custom"?"Custom":"Default"},appIconBlackPreviewUrl(){return this.resolveAppIconPreviewUrl("black")},appIconWhitePreviewUrl(){return this.resolveAppIconPreviewUrl("white")},hasPendingEmbeddingConfigChange(){const t={apiKey:this.settings.apiKey||"",apiProvider:this.settings.apiProvider||"custom",apiEndpoint:this.settings.apiEndpoint||"",embeddingApiEndpoint:(this.settings.embeddingApiEndpoint||"").trim(),embeddingApiKey:this.settings.embeddingApiKey?this.settings.embeddingApiKey:null,embeddingModel:(this.settings.embeddingModel||"").trim()};return this.hasEmbeddingConfigChange(t)},chatQueueStats(){return this.rateLimitStatus?.queue_stats||{pending:0,processing:0,completed:0,failed:0,total:0}},chatRateLimitState(){return this.rateLimitStatus?.chat_status?.state||this.rateLimitStatus?.state||null},chatCanProcess(){return typeof this.rateLimitStatus?.chat_status?.can_process=="boolean"?this.rateLimitStatus.chat_status.can_process:!!this.rateLimitStatus?.can_process},embeddingRateLimitState(){return this.rateLimitStatus?.embedding_status?.state||null},embeddingRateLimitModeStatus(){return this.rateLimitStatus?.embedding_status?.mode||this.settings.embeddingRateLimitMode||"inherit"},embeddingStatusSource(){return this.rateLimitStatus?.embedding_status?.source||"configured"},embeddingConfiguredHint(){return this.embeddingRateLimitModeStatus==="custom"?"Using the configured custom embedding limits until the provider returns dedicated headers.":"Using inherited chat limits until the embedding endpoint returns its own rate-limit headers."},embeddingCanProcess(){return typeof this.rateLimitStatus?.embedding_status?.can_process=="boolean"?this.rateLimitStatus.embedding_status.can_process:!1},doclingKeyHint(){return this.doclingHasStoredApiKey?"A stored Docling key is configured and kept unless you enter a new value.":"Leave blank to reuse the main API key."},essentialsSummary(){return`Temp ${this.formatTemperature(this.settings.defaultTemperature)}`},modelEndpointsSummary(){return this.settings.allowMultipleModels?`${this.settings.allowedModels.length} allowed model(s)`:(this.settings.secondaryApiEndpoint||"").trim()!==""?"Primary + Secondary":this.formatModelLabel(this.settings.defaultModel)||"Primary only"},fallbackTimeoutSummary(){return this.formatModelLabel(this.settings.fallbackModel)||`${this.settings.llmChatTimeout||90}/${this.settings.llmStreamTimeout||240}/${this.settings.llmModelsTimeout||20}s`},modelOptionMap(){return this.availableModelOptions.reduce((t,e)=>(t[e.id]=e,t),{})},ragSummary(){return this.hasPendingEmbeddingConfigChange?"Changed - reindex needed":this.settings.ragEnabled?this.settings.embeddingModel||"Enabled":"Disabled"},doclingSummary(){return this.settings.doclingEnabled?"Enabled":"Disabled"},mediaSummary(){const t=[];return this.settings.visionModel&&t.push("Vision"),this.settings.speechModel&&t.push("Speech"),t.length?t.join(" + "):"No models"},rateLimitSummary(){return this.settings.rateLimitEnabled?`${this.chatQueueStats.pending} queued`:"Disabled"},memorySummary(){return`${this.settings.conversationContextTokens||8e3} tokens`},toolsSummary(){return this.loadingTools?"Loading":`${this.tools.length} registered`},botsSummary(){return this.loadingAllBots?"Loading":`${this.allBots.length} bot(s)`}},mounted(){this.loadSettings(),this.loadTools(),this.loadAllBots()},methods:{isSectionOpen(t){return!!this.openSections[t]},sectionId(t){return`educai-admin-section-${t}`},toggleSection(t){Object.prototype.hasOwnProperty.call(this.openSections,t)&&(this.openSections[t]=!this.openSections[t])},normalizeTemperatureValue(t){const e=Number(t);return!Number.isFinite(e)||e<0||e>1?null:Math.round(e*100)/100},normalizeAppIconUrl(t){const e=(t||"").trim();if(e==="")return"";if(e.length>1024)return null;if(this.isUploadedAppIconReference(e))return this.isValidUploadedAppIconReference(e)?e:null;if(/^https?:\/\//i.test(e))try{const i=new URL(e);return i.protocol==="http:"||i.protocol==="https:"?e:null}catch{return null}return e.startsWith("/")&&!e.startsWith("//")?e:null},isUploadedAppIconReference(t){return/^educai-upload:\/\/(black|white)$/i.test(String(t||"").trim())},isServerBackedAppIconReference(t){return this.isUploadedAppIconReference(t)},isValidUploadedAppIconReference(t){try{const e=new URL(String(t||"").trim());return e.protocol==="educai-upload:"&&["black","white"].includes(e.hostname)}catch{return!1}},normalizeLoadedAppIconValue(t){const e=String(t||"").trim();return/^nextcloud-file(id)?:\/\//i.test(e)?"":e},triggerAppIconUpload(t){const e=t==="white"?"appIconWhiteFileInput":"appIconBlackFileInput",i=this.$refs[e];i&&(i.value="",i.click())},async uploadAppIconSvg(t,e){const i=e?.target,s=i?.files?.[0]||null;if(s){if(!this.isLocalSvgUpload(s)){n("Please upload an SVG file up to 1 MB"),i.value="";return}this.uploadingAppIconVariant=t;try{const a=new FormData;a.append("icon",s);const l=(await c.post(this.resolveAppIconUploadUrl(t),a)).data?.value||`educai-upload://${t}`;t==="white"?this.settings.appIconWhiteUrl=l:this.settings.appIconBlackUrl=l,this.appIconPreviewVersion[t]=Date.now(),p("App icon SVG uploaded")}catch(a){console.error("Failed to upload app icon SVG:",a),n(a?.response?.data?.error||"Failed to upload app icon SVG")}finally{this.uploadingAppIconVariant="",i.value=""}}},isLocalSvgUpload(t){return t&&t.size>0&&t.size<=1024*1024&&(t.type==="image/svg+xml"||t.name.toLowerCase().endsWith(".svg"))},resolveAppIconUploadUrl(t){return this.appIconUploadUrls[t]||r.generateUrl(`/apps/educai/api/v1/admin/app-icon-upload/${t}`)},setAppIconMode(t){["default","custom"].includes(t)&&(this.settings.appIconMode=t,t==="default"&&(this.settings.appIconBlackUrl="",this.settings.appIconWhiteUrl=""))},resetAppIconConfig(){this.settings.appIconMode="default",this.settings.appIconBlackUrl="",this.settings.appIconWhiteUrl="",this.settings.appIconUrl=""},resolveAppIconPreviewUrl(t){if(this.settings.appIconMode==="default")return r.imagePath("educai",t==="white"?"app.svg":"app-dark.svg");if(this.settings.appIconMode==="custom"){const e=this.normalizeAppIconUrl(t==="white"?this.settings.appIconWhiteUrl:this.settings.appIconBlackUrl);if(this.isServerBackedAppIconReference(e)){const i=this.appIconPreviewVersion[t]||0;return`${this.appIconPreviewUrls[t]||r.generateUrl(`/apps/educai/api/v1/admin/app-icon-preview/${t}`)}?source=${encodeURIComponent(e)}&v=${i}`}return e||""}return""},buildAppIconPayload(){if((["default","custom"].includes(this.settings.appIconMode)?this.settings.appIconMode:"default")==="default")return{appIconMode:"default",appIconBlackUrl:null,appIconWhiteUrl:null,appIconUrl:""};const t=this.normalizeAppIconUrl(this.settings.appIconBlackUrl);if(t===null)return n("Black app icon must be an http(s) URL, an absolute Nextcloud path, or an uploaded SVG"),null;const e=this.normalizeAppIconUrl(this.settings.appIconWhiteUrl);return e===null?(n("White app icon must be an http(s) URL, an absolute Nextcloud path, or an uploaded SVG"),null):!t||!e?(n("Custom app icon mode requires both black and white icons"),null):{appIconMode:"custom",appIconBlackUrl:t,appIconWhiteUrl:e,appIconUrl:t||""}},formatTemperature(t){const e=this.normalizeTemperatureValue(t);return e===null?"0.20":e.toFixed(2)},toEndpointModelId(t){const e=(t||"").trim();return e===""?"":/^(primary|secondary):/.test(e)?e:`primary:${e}`},formatModelLabel(t){const e=this.toEndpointModelId(t);return e===""?"":this.modelOptionMap[e]?.label||e.replace(/^primary:/,"Primary · ").replace(/^secondary:/,"Secondary · ")},normalizeModelOptions(t,e){return Array.isArray(t)&&t.length>0?t.filter(i=>i&&i.id&&i.model).map(i=>({id:String(i.id),label:i.label||this.formatModelLabel(i.id),model:String(i.model),endpoint:i.endpoint==="secondary"?"secondary":"primary"})):Array.isArray(e)?e.map(i=>({id:this.toEndpointModelId(String(i)),label:this.formatModelLabel(String(i)),model:String(i).replace(/^(primary|secondary):/,""),endpoint:String(i).startsWith("secondary:")?"secondary":"primary"})):[]},availableModelOptionsWithCurrent(t){const e=Array.isArray(t)?t:[t],i=[...this.availableModelOptions],s=new Set(i.map(a=>a.id));return e.map(a=>this.toEndpointModelId(a)).filter(a=>a!==""&&!s.has(a)).forEach(a=>{const l=a.startsWith("secondary:")?"secondary":"primary",d=a.replace(/^(primary|secondary):/,"");i.push({id:a,label:this.formatModelLabel(a),model:d,endpoint:l}),s.add(a)}),i},applyDefaultTemperaturePreset(t){this.settings.defaultTemperature=t},toOptionalPositiveInteger(t){const e=Number(t);return Number.isFinite(e)&&e>0?e:null},toPositiveInteger(t,e){const i=Number(t);return Number.isFinite(i)&&i>0?i:e},async loadSettings(){try{const t=(await c.get(r.generateUrl("/apps/educai/api/v1/settings"))).data;this.settings.apiProvider=t.api_provider||"custom",this.settings.apiEndpoint=t.api_endpoint||"",this.settings.secondaryApiEndpoint=t.secondary_api_endpoint||"",this.settings.defaultModel=this.toEndpointModelId(t.default_model||"llama-3.3-70b-instruct"),this.settings.defaultTemperature=this.normalizeTemperatureValue(t.default_temperature)??.2,this.settings.fallbackModel=this.toEndpointModelId(t.fallback_model||""),this.appIconUploadUrls={black:t.app_icon_upload_urls?.black||"",white:t.app_icon_upload_urls?.white||""},this.appIconPreviewUrls={black:t.app_icon_preview_urls?.black||"",white:t.app_icon_preview_urls?.white||""},this.settings.appIconUrl=this.normalizeLoadedAppIconValue(t.app_icon_url||""),this.settings.appIconMode=t.app_icon_mode==="custom"||t.app_icon_url?"custom":"default",this.settings.appIconBlackUrl=this.normalizeLoadedAppIconValue(t.app_icon_black_url||this.settings.appIconMode==="custom"&&t.app_icon_url||""),this.settings.appIconWhiteUrl=this.normalizeLoadedAppIconValue(t.app_icon_white_url||this.settings.appIconMode==="custom"&&t.app_icon_url||""),h(t),this.settings.llmChatTimeout=typeof t.llm_chat_timeout=="number"&&t.llm_chat_timeout>0?t.llm_chat_timeout:90,this.settings.llmStreamTimeout=typeof t.llm_stream_timeout=="number"&&t.llm_stream_timeout>0?t.llm_stream_timeout:240,this.settings.llmModelsTimeout=typeof t.llm_models_timeout=="number"&&t.llm_models_timeout>0?t.llm_models_timeout:20,this.settings.allowMultipleModels=!!t.allow_multiple_models,this.settings.allowedModels=Array.isArray(t.allowed_models)?t.allowed_models.map(e=>this.toEndpointModelId(e)):[],this.settings.embeddingApiEndpoint=t.embedding_api_endpoint||"",this.settings.embeddingModel=t.embedding_model||"",this.settings.embeddingRateLimitMode=t.embedding_rate_limit_mode||"inherit",this.settings.embeddingRateLimitSecond=typeof t.embedding_rate_limit_second=="number"&&t.embedding_rate_limit_second>0?t.embedding_rate_limit_second:null,this.settings.embeddingRateLimitMinute=typeof t.embedding_rate_limit_minute=="number"&&t.embedding_rate_limit_minute>0?t.embedding_rate_limit_minute:100,this.settings.embeddingRateLimitHour=typeof t.embedding_rate_limit_hour=="number"&&t.embedding_rate_limit_hour>0?t.embedding_rate_limit_hour:2e3,this.settings.embeddingRateLimitDay=typeof t.embedding_rate_limit_day=="number"&&t.embedding_rate_limit_day>0?t.embedding_rate_limit_day:4e3,this.embeddingConfigBaseline={apiProvider:this.settings.apiProvider,apiEndpoint:this.settings.apiEndpoint,embeddingApiEndpoint:this.settings.embeddingApiEndpoint,embeddingModel:this.settings.embeddingModel,hasApiKey:t.api_key==="***",hasEmbeddingApiKey:t.embedding_api_key==="***"},this.settings.ragChunkSize=typeof t.rag_chunk_size=="number"?t.rag_chunk_size:750,this.settings.ragChunkOverlap=typeof t.rag_chunk_overlap=="number"?t.rag_chunk_overlap:50,this.settings.ragEnabled=!!t.rag_enabled,this.settings.catalogueEnabled=!!t.catalogue_enabled,this.settings.catalogueApiEndpoint=t.catalogue_api_endpoint||"",this.settings.catalogueReindexHours=typeof t.catalogue_reindex_hours=="number"?t.catalogue_reindex_hours:24,this.settings.doclingEnabled=!!t.docling_enabled,this.settings.doclingApiEndpoint=t.docling_api_endpoint||"",this.doclingHasStoredApiKey=t.docling_api_key==="***",this.settings.visionApiEndpoint=t.vision_api_endpoint||"",this.settings.visionModel=t.vision_model||"",this.settings.speechApiEndpoint=t.speech_api_endpoint||"",this.settings.speechModel=t.speech_model||"",this.settings.rateLimitEnabled=!!t.rate_limit_enabled,this.settings.rateLimitSecond=typeof t.rate_limit_second=="number"&&t.rate_limit_second>0?t.rate_limit_second:null,this.settings.rateLimitMinute=typeof t.rate_limit_minute=="number"&&t.rate_limit_minute>0?t.rate_limit_minute:30,this.settings.rateLimitHour=typeof t.rate_limit_hour=="number"&&t.rate_limit_hour>0?t.rate_limit_hour:200,this.settings.rateLimitDay=typeof t.rate_limit_day=="number"&&t.rate_limit_day>0?t.rate_limit_day:1e3,this.settings.rateLimitQueueMessage=t.rate_limit_queue_message||"",this.settings.conversationContextTokens=typeof t.conversation_context_tokens=="number"?t.conversation_context_tokens:8e3,(this.settings.allowMultipleModels||this.settings.secondaryApiEndpoint)&&this.loadModels(),this.settings.rateLimitEnabled&&this.loadRateLimitStatus()}catch(t){console.error("Failed to load settings:",t)}},onToggleMultiple(){this.settings.allowMultipleModels&&this.availableModelOptions.length===0&&this.loadModels()},async loadModels(){this.loadingModels=!0,this.modelLoadError="";try{const t=await c.get(r.generateUrl("/apps/educai/api/v1/models"));this.availableModelOptions=this.normalizeModelOptions(t.data?.model_options,t.data?.models),this.availableModels=this.availableModelOptions.map(i=>i.id);const e=this.availableModelOptions.length;this.modelSelectSize=Math.max(12,Math.min(18,e||12)),this.settings.allowedModels.length===0&&this.settings.defaultModel&&this.availableModels.includes(this.settings.defaultModel)&&(this.settings.allowedModels=[this.settings.defaultModel])}catch(t){console.error("Failed to load models",t),this.modelLoadError="Failed to load models from provider. Check API key and endpoint."}finally{this.loadingModels=!1}},async saveSettings(){this.saving=!0;try{const t=this.normalizeTemperatureValue(this.settings.defaultTemperature);if(t===null){n("Default temperature must be between 0.0 and 1.0");return}const e=this.buildAppIconPayload();if(e===null)return;const i={apiKey:this.settings.apiKey||"",apiEndpoint:(this.settings.apiEndpoint||"").trim(),secondaryApiEndpoint:(this.settings.secondaryApiEndpoint||"").trim(),secondaryApiKey:this.settings.secondaryApiKey?this.settings.secondaryApiKey:null,defaultModel:this.toEndpointModelId(this.settings.defaultModel),defaultTemperature:t,fallbackModel:this.toEndpointModelId(this.settings.fallbackModel),...e,llmChatTimeout:this.toPositiveInteger(this.settings.llmChatTimeout,90),llmStreamTimeout:this.toPositiveInteger(this.settings.llmStreamTimeout,240),llmModelsTimeout:this.toPositiveInteger(this.settings.llmModelsTimeout,20),apiProvider:this.settings.apiProvider||"custom",allowMultipleModels:this.settings.allowMultipleModels,allowedModels:this.settings.allowMultipleModels?this.settings.allowedModels.map(m=>this.toEndpointModelId(m)):null,embeddingApiEndpoint:(this.settings.embeddingApiEndpoint||"").trim(),embeddingApiKey:this.settings.embeddingApiKey?this.settings.embeddingApiKey:null,embeddingModel:(this.settings.embeddingModel||"").trim(),embeddingRateLimitMode:this.settings.embeddingRateLimitMode||"inherit",embeddingRateLimitSecond:this.toOptionalPositiveInteger(this.settings.embeddingRateLimitSecond),embeddingRateLimitMinute:this.toPositiveInteger(this.settings.embeddingRateLimitMinute,100),embeddingRateLimitHour:this.toPositiveInteger(this.settings.embeddingRateLimitHour,2e3),embeddingRateLimitDay:this.toPositiveInteger(this.settings.embeddingRateLimitDay,4e3),ragChunkSize:Number.isFinite(this.settings.ragChunkSize)?Number(this.settings.ragChunkSize):null,ragChunkOverlap:Number.isFinite(this.settings.ragChunkOverlap)?Number(this.settings.ragChunkOverlap):null,ragEnabled:this.settings.ragEnabled,catalogueEnabled:this.settings.catalogueEnabled,catalogueApiEndpoint:(this.settings.catalogueApiEndpoint||"").trim(),catalogueReindexHours:Number.isFinite(this.settings.catalogueReindexHours)?Number(this.settings.catalogueReindexHours):24,doclingEnabled:this.settings.doclingEnabled,doclingApiEndpoint:(this.settings.doclingApiEndpoint||"").trim(),doclingApiKey:this.settings.doclingApiKey?this.settings.doclingApiKey:null,visionApiEndpoint:(this.settings.visionApiEndpoint||"").trim(),visionApiKey:this.settings.visionApiKey?this.settings.visionApiKey:null,visionModel:(this.settings.visionModel||"").trim(),speechApiEndpoint:(this.settings.speechApiEndpoint||"").trim(),speechApiKey:this.settings.speechApiKey?this.settings.speechApiKey:null,speechModel:(this.settings.speechModel||"").trim(),rateLimitEnabled:this.settings.rateLimitEnabled,rateLimitSecond:this.toOptionalPositiveInteger(this.settings.rateLimitSecond),rateLimitMinute:this.toPositiveInteger(this.settings.rateLimitMinute,30),rateLimitHour:this.toPositiveInteger(this.settings.rateLimitHour,200),rateLimitDay:this.toPositiveInteger(this.settings.rateLimitDay,1e3),rateLimitQueueMessage:(this.settings.rateLimitQueueMessage||"").trim(),conversationContextTokens:Number.isFinite(this.settings.conversationContextTokens)?Number(this.settings.conversationContextTokens):8e3};this.settings.webhookSecret&&this.settings.webhookSecret.trim()!==""&&(i.webhookSecret=this.settings.webhookSecret);const s=this.hasEmbeddingConfigChange(i);if(s&&!confirm(`Embedding configuration changes detected (model/endpoint/key).
You must run "Reindex All Embeddings" after saving, otherwise retrieval quality will be degraded.
Continue saving settings?`)){this.saving=!1;return}const a=await c.put(r.generateUrl("/apps/educai/api/v1/settings"),i);h(a.data,{refreshNavigation:!0}),s?p('Settings saved. Run "Reindex All Embeddings" now to rebuild vectors for the new embedding configuration.'):p("Settings saved successfully");const l=!!(i.apiKey&&i.apiKey.trim()!==""),d=!!(i.secondaryApiKey&&i.secondaryApiKey.trim()!==""),o=!!(i.embeddingApiKey&&i.embeddingApiKey.trim()!==""),u=!!(i.doclingApiKey&&i.doclingApiKey.trim()!=="");this.doclingHasStoredApiKey=this.doclingHasStoredApiKey||u,this.clearSecretInputs(),this.embeddingConfigBaseline={apiProvider:this.settings.apiProvider||"custom",apiEndpoint:this.settings.apiEndpoint,embeddingApiEndpoint:this.settings.embeddingApiEndpoint,embeddingModel:this.settings.embeddingModel,hasApiKey:this.embeddingConfigBaseline.hasApiKey||l,hasEmbeddingApiKey:this.embeddingConfigBaseline.hasEmbeddingApiKey||o},d&&(this.settings.secondaryApiKey="")}catch(t){console.error("Failed to save settings:",t),n("Failed to save settings")}finally{this.saving=!1}},clearSecretInputs(){this.settings.apiKey="",this.settings.secondaryApiKey="",this.settings.webhookSecret="",this.settings.embeddingApiKey="",this.settings.doclingApiKey="",this.settings.visionApiKey="",this.settings.speechApiKey=""},openCreateTool(){this.resetToolForm(),this.toolFormVisible=!0},resetToolForm(){this.toolForm={id:null,name:"",mcpEndpointUrl:"",description:"",authentication:"",enabled:!1},this.toolHasStoredAuth=!1,this.toolTestResult=""},closeToolForm(){this.resetToolForm(),this.toolFormVisible=!1},async loadTools(){this.loadingTools=!0;try{const t=await c.get(r.generateUrl("/apps/educai/api/v1/admin/tools"));this.tools=Array.isArray(t.data?.tools)?t.data.tools:[]}catch(t){console.error("Failed to load tools",t),n("Failed to load tools")}finally{this.loadingTools=!1}},editTool(t){this.toolForm={id:t.id,name:t.name,mcpEndpointUrl:t.mcp_endpoint_url,description:t.description||"",authentication:"",enabled:!!t.enabled},this.toolHasStoredAuth=t.authentication==="***",this.toolFormVisible=!0,this.toolTestResult=""},async saveTool(){if(!this.toolForm.name||!this.toolForm.mcpEndpointUrl){n("Tool name and endpoint are required");return}const t=this.toolForm.authentication&&this.toolForm.authentication.trim()!=="";let e=null;if(t)try{e=JSON.parse(this.toolForm.authentication)}catch{n("Authentication must be valid JSON");return}const i={name:this.toolForm.name,mcpEndpointUrl:this.toolForm.mcpEndpointUrl,description:this.toolForm.description||null,enabled:this.toolForm.enabled};t?i.authentication=e:this.toolForm.id||(i.authentication=null),this.toolSaving=!0;try{this.toolForm.id?await c.put(r.generateUrl(`/apps/educai/api/v1/admin/tools/${this.toolForm.id}`),i):await c.post(r.generateUrl("/apps/educai/api/v1/admin/tools"),i),p("Tool saved"),await this.loadTools(),this.closeToolForm()}catch(s){console.error("Failed to save tool",s),n("Failed to save tool")}finally{this.toolSaving=!1}},async deleteTool(t){this.toolSaving=!0;try{await c.delete(r.generateUrl(`/apps/educai/api/v1/admin/tools/${t.id}`)),p("Tool deleted"),await this.loadTools()}catch(e){console.error("Failed to delete tool",e),n("Failed to delete tool")}finally{this.toolSaving=!1}},async toggleToolEnabled(t){this.toolSaving=!0;try{await c.put(r.generateUrl(`/apps/educai/api/v1/admin/tools/${t.id}`),{enabled:!t.enabled}),await this.loadTools()}catch(e){console.error("Failed to toggle tool",e),n("Failed to update tool state")}finally{this.toolSaving=!1}},async testToolConnection(){if(!this.toolForm.mcpEndpointUrl){n("Endpoint URL required to test");return}const t=this.toolForm.authentication&&this.toolForm.authentication.trim()!=="";let e=null;if(t)try{e=JSON.parse(this.toolForm.authentication)}catch{n("Authentication must be valid JSON");return}this.toolTestResult="",this.toolTesting=!0;try{const i=await c.post(r.generateUrl("/apps/educai/api/v1/admin/tools/test"),{mcpEndpointUrl:this.toolForm.mcpEndpointUrl,authentication:e});this.toolTestResult=JSON.stringify(i.data?.tools??[],null,2),p("Tool connection succeeded")}catch(i){console.error("Tool test failed",i),n("Failed to test tool connection")}finally{this.toolTesting=!1}},hasEmbeddingConfigChange(t){const e=(t.apiProvider||"custom").trim(),i=(t.apiEndpoint||"").trim(),s=(t.embeddingApiEndpoint||"").trim(),a=(t.embeddingModel||"").trim(),l=this.resolveEffectiveEmbeddingEndpoint(i,s),d=(this.embeddingConfigBaseline.apiProvider||"custom").trim(),o=(this.embeddingConfigBaseline.apiEndpoint||"").trim(),u=(this.embeddingConfigBaseline.embeddingApiEndpoint||"").trim(),m=(this.embeddingConfigBaseline.embeddingModel||"").trim(),v=this.resolveEffectiveEmbeddingEndpoint(o,u),g=!!(t.embeddingApiKey&&t.embeddingApiKey.trim()!==""),b=!!(t.apiKey&&t.apiKey.trim()!==""),_=!this.embeddingConfigBaseline.hasEmbeddingApiKey&&!g;return e!==d||a!==m||l!==v||g||_&&b},resolveEffectiveEmbeddingEndpoint(t,e){const i=(e||"").trim();return i!==""?i:(t||"").trim()},async reindexAllEmbeddings(){this.globalEmbeddingReindexing=!0;try{const t=await c.post(r.generateUrl("/apps/educai/api/v1/admin/embeddings/reindex-all"));if(t.data?.success){const e=t.data?.queued_catalogue_jobs??0,i=t.data?.queued_rag_sources??0;p(`Queued reindex jobs: catalogue=${e}, bot sources=${i}`),this.loadCatalogueStatus()}else n(t.data?.error||"Failed to queue global embedding reindex")}catch(t){console.error("Failed to queue global embedding reindex",t),n(t.response?.data?.error||"Failed to queue global embedding reindex")}finally{this.globalEmbeddingReindexing=!1}},formatTimestamp(t){if(!t)return"Never";const e=new Date(t*1e3),i=new Date-e,s=Math.floor(i/(1e3*60*60));return s<1?`${Math.floor(i/6e4)} min ago`:s<24?`${s}h ago`:`${Math.floor(s/24)}d ago`},async testDoclingConnection(){this.doclingTesting=!0,this.doclingTestResult=null;try{const t=await c.post(r.generateUrl("/apps/educai/api/v1/admin/docling/test"),{doclingApiEndpoint:this.settings.doclingApiEndpoint||null,doclingApiKey:this.settings.doclingApiKey||(this.doclingHasStoredApiKey?null:this.settings.apiKey)||null});this.doclingTestResult={success:t.data?.success??!1,error:t.data?.error??null},this.doclingTestResult.success?p("Docling connection successful"):n(this.doclingTestResult.error||"Connection test failed")}catch(t){console.error("Docling test failed",t),this.doclingTestResult={success:!1,error:t.response?.data?.error||t.message||"Connection failed"},n("Failed to test Docling connection")}finally{this.doclingTesting=!1}},async testVisionConnection(){this.visionTesting=!0,this.visionTestResult=null;try{const t=await c.post(r.generateUrl("/apps/educai/api/v1/admin/vision/test"),{visionApiEndpoint:this.settings.visionApiEndpoint||null,visionApiKey:this.settings.visionApiKey||null,visionModel:(this.settings.visionModel||"").trim()||null});this.visionTestResult={success:t.data?.success??!1,error:t.data?.error??null},this.visionTestResult.success?p("Vision connection successful"):n(this.visionTestResult.error||"Vision connection test failed")}catch(t){console.error("Vision test failed",t),this.visionTestResult={success:!1,error:t.response?.data?.error||t.message||"Connection failed"},n("Failed to test vision connection")}finally{this.visionTesting=!1}},async testSpeechConnection(){this.speechTesting=!0,this.speechTestResult=null;try{const t=await c.post(r.generateUrl("/apps/educai/api/v1/admin/speech/test"),{speechApiEndpoint:this.settings.speechApiEndpoint||null,speechApiKey:this.settings.speechApiKey||null,speechModel:(this.settings.speechModel||"").trim()||null});this.speechTestResult={success:t.data?.success??!1,error:t.data?.error??null},this.speechTestResult.success?p("Speech connection successful"):n(this.speechTestResult.error||"Speech connection test failed")}catch(t){console.error("Speech test failed",t),this.speechTestResult={success:!1,error:t.response?.data?.error||t.message||"Connection failed"},n("Failed to test speech connection")}finally{this.speechTesting=!1}},async loadRateLimitStatus(){this.rateLimitLoading=!0;try{const t=await c.get(r.generateUrl("/apps/educai/api/v1/admin/ratelimit/status"));this.rateLimitStatus=t.data??null}catch(t){console.error("Failed to load rate limit status",t),this.rateLimitStatus=null}finally{this.rateLimitLoading=!1}},async processQueueNow(){this.queueProcessing=!0;try{const t=await c.post(r.generateUrl("/apps/educai/api/v1/admin/ratelimit/process"));if(t.data?.success){const e=t.data.processed||0,i=t.data.remaining||0;p(`Processed ${e} request(s). ${i} remaining.`),this.loadRateLimitStatus()}else n(t.data?.error||"Failed to process queue")}catch(t){console.error("Failed to process queue",t),n(t.response?.data?.error||"Failed to process queue")}finally{this.queueProcessing=!1}},async loadAllBots(){this.loadingAllBots=!0;try{const t=await c.get(r.generateUrl("/apps/educai/api/v1/admin/bots"));this.allBots=Array.isArray(t.data)?t.data:[]}catch(t){console.error("Failed to load all bots",t),n("Failed to load bots")}finally{this.loadingAllBots=!1}},editBotAdmin(t){this.adminEditingBot=t},closeAdminEditor(){this.adminEditingBot=null},async saveBotAdmin(t){if(!t?.id){n("Bot id is required");return}try{await c.put(r.generateUrl(`/apps/educai/api/v1/admin/bots/${t.id}`),t),p("Bot updated"),this.adminEditingBot=null,this.loadAllBots()}catch(e){console.error("Failed to update bot as admin",e),n(e.response?.data?.error||"Failed to update bot")}},async deleteBotAdmin(t){if(!(!t||!t.id)&&confirm(`Delete bot "${t.bot_name}"? This cannot be undone.`))try{await c.delete(r.generateUrl(`/apps/educai/api/v1/bots/${t.id}`)),p("Bot deleted"),this.loadAllBots()}catch(e){console.error("Failed to delete bot as admin",e),n(e.response?.data?.error||"Failed to delete bot")}},formatVisibility(t){return t==="global"?"Global":t==="personal"?"Personal":t==="teams"?"Teams":"Groups"}}};var M=function(){var t=this,e=t._self._c;return e("div",{staticClass:"admin-settings"},[t._m(0),e("form",{staticClass:"settings-body",attrs:{autocomplete:"off"},on:{submit:function(i){return i.preventDefault(),t.saveSettings.apply(null,arguments)}}},[e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("appearance")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("appearance")),"aria-controls":t.sectionId("appearance")},on:{click:function(i){return t.toggleSection("appearance")}}},[t._m(1),e("span",{staticClass:"accordion-meta"},[t._v(t._s(t.appIconSummary))]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("appearance"),expression:"isSectionOpen('appearance')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("appearance")}},[e("div",{staticClass:"app-icon-config"},[e("div",{staticClass:"icon-mode-tabs",attrs:{"aria-label":"App icon mode"}},[e("button",{staticClass:"icon-mode-tab",class:{"icon-mode-tab--active":t.settings.appIconMode==="default"},attrs:{type:"button"},on:{click:function(i){return t.setAppIconMode("default")}}},[t._v(" Default ")]),e("button",{staticClass:"icon-mode-tab",class:{"icon-mode-tab--active":t.settings.appIconMode==="custom"},attrs:{type:"button"},on:{click:function(i){return t.setAppIconMode("custom")}}},[t._v(" Custom ")])]),e("div",{staticClass:"app-icon-preview-grid"},[e("div",{staticClass:"app-icon-preview-card app-icon-preview-card--light"},[e("span",{staticClass:"app-icon-preview-card__label"},[t._v("Black on light")]),e("span",{staticClass:"app-icon-preview-card__frame"},[t.appIconBlackPreviewUrl?e("img",{attrs:{src:t.appIconBlackPreviewUrl,alt:t.APP_DISPLAY_NAME+" black app icon preview"}}):e("span",{staticClass:"app-icon-preview-card__missing"},[t._v("Missing")])])]),e("div",{staticClass:"app-icon-preview-card app-icon-preview-card--dark"},[e("span",{staticClass:"app-icon-preview-card__label"},[t._v("White on dark")]),e("span",{staticClass:"app-icon-preview-card__frame"},[t.appIconWhitePreviewUrl?e("img",{attrs:{src:t.appIconWhitePreviewUrl,alt:t.APP_DISPLAY_NAME+" white app icon preview"}}):e("span",{staticClass:"app-icon-preview-card__missing"},[t._v("Missing")])])])]),t.settings.appIconMode==="default"?e("div",{staticClass:"app-icon-mode-panel"},[e("p",{staticClass:"hint"},[t._v(" Uses the bundled Talk AI icon pair: dark icon for light settings surfaces, white icon for app navigation. ")])]):e("div",{staticClass:"app-icon-mode-panel"},[e("div",{staticClass:"form-grid"},[e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"app-icon-black-url"}},[t._v("Black icon")]),e("div",{staticClass:"app-icon-input-row"},[e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.appIconBlackUrl,expression:"settings.appIconBlackUrl"}],attrs:{id:"app-icon-black-url",type:"text",name:"educai-app-icon-black-url",autocomplete:"off",placeholder:"https://example.org/educai-black.svg or /apps/theming/img/educai-black.svg"},domProps:{value:t.settings.appIconBlackUrl},on:{input:function(i){i.target.composing||t.$set(t.settings,"appIconBlackUrl",i.target.value)}}}),e("button",{staticClass:"button",attrs:{type:"button",disabled:t.uploadingAppIconVariant==="black"},on:{click:function(i){return t.triggerAppIconUpload("black")}}},[t._v(" "+t._s(t.uploadingAppIconVariant==="black"?"Uploading...":"Upload SVG")+" ")]),e("input",{ref:"appIconBlackFileInput",staticClass:"app-icon-file-input",attrs:{type:"file",accept:"image/svg+xml,.svg"},on:{change:function(i){return t.uploadAppIconSvg("black",i)}}})]),e("p",{staticClass:"hint"},[t._v(" Used on light settings surfaces. Enter a URL/path or upload an SVG from your computer. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"app-icon-white-url"}},[t._v("White icon")]),e("div",{staticClass:"app-icon-input-row"},[e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.appIconWhiteUrl,expression:"settings.appIconWhiteUrl"}],attrs:{id:"app-icon-white-url",type:"text",name:"educai-app-icon-white-url",autocomplete:"off",placeholder:"https://example.org/educai-white.svg or /apps/theming/img/educai-white.svg"},domProps:{value:t.settings.appIconWhiteUrl},on:{input:function(i){i.target.composing||t.$set(t.settings,"appIconWhiteUrl",i.target.value)}}}),e("button",{staticClass:"button",attrs:{type:"button",disabled:t.uploadingAppIconVariant==="white"},on:{click:function(i){return t.triggerAppIconUpload("white")}}},[t._v(" "+t._s(t.uploadingAppIconVariant==="white"?"Uploading...":"Upload SVG")+" ")]),e("input",{ref:"appIconWhiteFileInput",staticClass:"app-icon-file-input",attrs:{type:"file",accept:"image/svg+xml,.svg"},on:{change:function(i){return t.uploadAppIconSvg("white",i)}}})]),e("p",{staticClass:"hint"},[t._v(" Used in the dark app navigation/header context. Enter a URL/path or upload an SVG from your computer. ")])])])]),e("div",{staticClass:"button-row app-icon-actions"},[e("button",{staticClass:"button",attrs:{type:"button"},on:{click:t.resetAppIconConfig}},[t._v(" Reset to default icon ")])])])])]),e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("essentials")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("essentials")),"aria-controls":t.sectionId("essentials")},on:{click:function(i){return t.toggleSection("essentials")}}},[t._m(2),e("span",{staticClass:"accordion-meta"},[t._v(t._s(t.essentialsSummary))]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("essentials"),expression:"isSectionOpen('essentials')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("essentials")}},[e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"default-temperature"}},[t._v("Default Temperature")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.defaultTemperature,expression:"settings.defaultTemperature",modifiers:{number:!0}}],attrs:{id:"default-temperature",type:"number",min:"0",max:"1",step:"0.05",placeholder:"0.20"},domProps:{value:t.settings.defaultTemperature},on:{input:function(i){i.target.composing||t.$set(t.settings,"defaultTemperature",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Controls how deterministic or creative bots are by default. Lower values are better for agentic, tool-using, RAG, and workflow bots. Higher values can be useful for creative writing or brainstorming bots. ")]),e("div",{staticClass:"temperature-presets"},[e("button",{staticClass:"button",attrs:{type:"button"},on:{click:function(i){return t.applyDefaultTemperaturePreset(.2)}}},[t._v(" Precise 0.20 ")]),e("button",{staticClass:"button",attrs:{type:"button"},on:{click:function(i){return t.applyDefaultTemperaturePreset(.4)}}},[t._v(" Balanced 0.40 ")]),e("button",{staticClass:"button",attrs:{type:"button"},on:{click:function(i){return t.applyDefaultTemperaturePreset(.6)}}},[t._v(" Creative 0.60 ")])])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"webhook-secret"}},[t._v("Webhook Secret")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.webhookSecret,expression:"settings.webhookSecret"}],attrs:{id:"webhook-secret",type:"password",name:"educai-webhook-secret",autocomplete:"new-password",placeholder:"your-webhook-secret"},domProps:{value:t.settings.webhookSecret},on:{input:function(i){i.target.composing||t.$set(t.settings,"webhookSecret",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Secret used to verify webhook requests from Nextcloud Talk. ")])])])]),e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("modelEndpoints")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("modelEndpoints")),"aria-controls":t.sectionId("modelEndpoints")},on:{click:function(i){return t.toggleSection("modelEndpoints")}}},[t._m(3),e("span",{staticClass:"accordion-meta"},[t._v(t._s(t.modelEndpointsSummary))]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("modelEndpoints"),expression:"isSectionOpen('modelEndpoints')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("modelEndpoints")}},[t._m(4),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"api-endpoint"}},[t._v("Primary API Endpoint")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.apiEndpoint,expression:"settings.apiEndpoint"}],attrs:{id:"api-endpoint",type:"url",name:"educai-api-endpoint",autocomplete:"off",placeholder:"https://chat-ai.academiccloud.de/v1/chat/completions",required:""},domProps:{value:t.settings.apiEndpoint},on:{input:function(i){i.target.composing||t.$set(t.settings,"apiEndpoint",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" AcademicCloud (GWDG) or OpenAI-compatible API endpoint URL. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"api-key"}},[t._v("Primary API Key")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.apiKey,expression:"settings.apiKey"}],attrs:{id:"api-key",type:"password",name:"educai-api-key",autocomplete:"new-password",placeholder:"sk-..."},domProps:{value:t.settings.apiKey},on:{input:function(i){i.target.composing||t.$set(t.settings,"apiKey",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Used for primary endpoint requests. Stored securely and never displayed back. ")])]),t._m(5),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"secondary-api-endpoint"}},[t._v("Secondary API Endpoint")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.secondaryApiEndpoint,expression:"settings.secondaryApiEndpoint"}],attrs:{id:"secondary-api-endpoint",type:"url",name:"educai-secondary-api-endpoint",autocomplete:"off",placeholder:"https://example.org/v1/chat/completions"},domProps:{value:t.settings.secondaryApiEndpoint},on:{input:function(i){i.target.composing||t.$set(t.settings,"secondaryApiEndpoint",i.target.value)}}})]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"secondary-api-key"}},[t._v("Secondary API Key")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.secondaryApiKey,expression:"settings.secondaryApiKey"}],attrs:{id:"secondary-api-key",type:"password",name:"educai-secondary-api-key",autocomplete:"new-password",placeholder:"sk-..."},domProps:{value:t.settings.secondaryApiKey},on:{input:function(i){i.target.composing||t.$set(t.settings,"secondaryApiKey",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Leave blank to keep an already stored secondary key. ")])]),t._m(6),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"default-model"}},[t._v("Default Model")]),t.availableModelOptions.length>0?e("select",{directives:[{name:"model",rawName:"v-model",value:t.settings.defaultModel,expression:"settings.defaultModel"}],attrs:{id:"default-model",disabled:t.settings.allowMultipleModels},on:{change:function(i){var s=Array.prototype.filter.call(i.target.options,function(a){return a.selected}).map(function(a){var l="_value"in a?a._value:a.value;return l});t.$set(t.settings,"defaultModel",i.target.multiple?s:s[0])}}},t._l(t.availableModelOptionsWithCurrent(t.settings.defaultModel),function(i){return e("option",{key:i.id,domProps:{value:i.id}},[t._v(" "+t._s(i.label)+" ")])}),0):e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.defaultModel,expression:"settings.defaultModel"}],attrs:{id:"default-model",type:"text",disabled:t.settings.allowMultipleModels,placeholder:"primary:llama-3.3-70b-instruct"},domProps:{value:t.settings.defaultModel},on:{input:function(i){i.target.composing||t.$set(t.settings,"defaultModel",i.target.value)}}}),t.settings.allowMultipleModels?e("p",{staticClass:"hint"},[t._v(" Disabled because Multiple Models mode is enabled. ")]):e("p",{staticClass:"hint"},[t._v(" Legacy unprefixed model names continue to use the primary endpoint. ")])]),e("div",{staticClass:"form-group"},[e("label",{staticClass:"checkbox"},[e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.allowMultipleModels,expression:"settings.allowMultipleModels"}],attrs:{type:"checkbox"},domProps:{checked:Array.isArray(t.settings.allowMultipleModels)?t._i(t.settings.allowMultipleModels,null)>-1:t.settings.allowMultipleModels},on:{change:[function(i){var s=t.settings.allowMultipleModels,a=i.target,l=!!a.checked;if(Array.isArray(s)){var d=null,o=t._i(s,d);a.checked?o<0&&t.$set(t.settings,"allowMultipleModels",s.concat([d])):o>-1&&t.$set(t.settings,"allowMultipleModels",s.slice(0,o).concat(s.slice(o+1)))}else t.$set(t.settings,"allowMultipleModels",l)},t.onToggleMultiple]}}),t._v(" Allow Multiple Models (per-bot selection) ")]),e("p",{staticClass:"hint"},[t._v(" If enabled, choose which models are available for bots. Users will select one when creating a bot. ")])]),t.settings.allowMultipleModels?e("div",{staticClass:"form-group"},[e("label",[t._v("Allowed Models")]),t.loadingModels?e("div",{staticClass:"hint"},[t._v(" Loading models… ")]):e("div",[t.modelLoadError?e("div",{staticClass:"hint",staticStyle:{color:"var(--color-error)"}},[t._v(" "+t._s(t.modelLoadError)+" ")]):t._e(),t.availableModelOptions.length===0?e("div",{staticClass:"hint"},[t._v(" No models loaded. "),e("button",{staticClass:"button",attrs:{type:"button"},on:{click:t.loadModels}},[t._v(" Load models ")])]):t._e(),e("select",{directives:[{name:"model",rawName:"v-model",value:t.settings.allowedModels,expression:"settings.allowedModels"}],attrs:{multiple:"",size:t.modelSelectSize},on:{change:function(i){var s=Array.prototype.filter.call(i.target.options,function(a){return a.selected}).map(function(a){var l="_value"in a?a._value:a.value;return l});t.$set(t.settings,"allowedModels",i.target.multiple?s:s[0])}}},t._l(t.availableModelOptionsWithCurrent(t.settings.allowedModels),function(i){return e("option",{key:i.id,domProps:{value:i.id}},[t._v(" "+t._s(i.label)+" ")])}),0),e("p",{staticClass:"hint"},[t._v(" Hold Cmd/Ctrl to select multiple. Save after selection. ")])])]):t._e()])]),e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("fallbackTimeouts")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("fallbackTimeouts")),"aria-controls":t.sectionId("fallbackTimeouts")},on:{click:function(i){return t.toggleSection("fallbackTimeouts")}}},[t._m(7),e("span",{staticClass:"accordion-meta"},[t._v(t._s(t.fallbackTimeoutSummary))]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("fallbackTimeouts"),expression:"isSectionOpen('fallbackTimeouts')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("fallbackTimeouts")}},[e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"fallback-model"}},[t._v("Fallback Model")]),t.availableModelOptions.length>0?e("select",{directives:[{name:"model",rawName:"v-model",value:t.settings.fallbackModel,expression:"settings.fallbackModel"}],attrs:{id:"fallback-model"},on:{change:function(i){var s=Array.prototype.filter.call(i.target.options,function(a){return a.selected}).map(function(a){var l="_value"in a?a._value:a.value;return l});t.$set(t.settings,"fallbackModel",i.target.multiple?s:s[0])}}},[e("option",{attrs:{value:""}},[t._v("No fallback")]),t._l(t.availableModelOptionsWithCurrent(t.settings.fallbackModel),function(i){return e("option",{key:i.id,domProps:{value:i.id}},[t._v(" "+t._s(i.label)+" ")])})],2):e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.fallbackModel,expression:"settings.fallbackModel"}],attrs:{id:"fallback-model",type:"text",placeholder:"secondary:qwen3-coder-next"},domProps:{value:t.settings.fallbackModel},on:{input:function(i){i.target.composing||t.$set(t.settings,"fallbackModel",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Used once after timeout or connection failures before the request fails finally. ")])]),e("div",{staticClass:"form-grid form-grid--three"},[e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"llm-chat-timeout"}},[t._v("Chat Timeout (seconds)")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.llmChatTimeout,expression:"settings.llmChatTimeout",modifiers:{number:!0}}],attrs:{id:"llm-chat-timeout",type:"number",min:"1",step:"1"},domProps:{value:t.settings.llmChatTimeout},on:{input:function(i){i.target.composing||t.$set(t.settings,"llmChatTimeout",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Maximum time to wait for a regular, non-streaming LLM response. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"llm-stream-timeout"}},[t._v("Streaming Timeout (seconds)")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.llmStreamTimeout,expression:"settings.llmStreamTimeout",modifiers:{number:!0}}],attrs:{id:"llm-stream-timeout",type:"number",min:"1",step:"1"},domProps:{value:t.settings.llmStreamTimeout},on:{input:function(i){i.target.composing||t.$set(t.settings,"llmStreamTimeout",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Maximum time to keep a streaming response open while the model is generating. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"llm-models-timeout"}},[t._v("Model-List Timeout (seconds)")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.llmModelsTimeout,expression:"settings.llmModelsTimeout",modifiers:{number:!0}}],attrs:{id:"llm-models-timeout",type:"number",min:"1",step:"1"},domProps:{value:t.settings.llmModelsTimeout},on:{input:function(i){i.target.composing||t.$set(t.settings,"llmModelsTimeout",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}})])])])]),e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("rag")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("rag")),"aria-controls":t.sectionId("rag")},on:{click:function(i){return t.toggleSection("rag")}}},[t._m(8),e("span",{staticClass:"accordion-meta",class:{"accordion-meta--warning":t.hasPendingEmbeddingConfigChange}},[t._v(" "+t._s(t.ragSummary)+" ")]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("rag"),expression:"isSectionOpen('rag')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("rag")}},[t._m(9),e("div",{staticClass:"form-group"},[e("label",{staticClass:"checkbox"},[e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.ragEnabled,expression:"settings.ragEnabled"}],attrs:{type:"checkbox"},domProps:{checked:Array.isArray(t.settings.ragEnabled)?t._i(t.settings.ragEnabled,null)>-1:t.settings.ragEnabled},on:{change:function(i){var s=t.settings.ragEnabled,a=i.target,l=!!a.checked;if(Array.isArray(s)){var d=null,o=t._i(s,d);a.checked?o<0&&t.$set(t.settings,"ragEnabled",s.concat([d])):o>-1&&t.$set(t.settings,"ragEnabled",s.slice(0,o).concat(s.slice(o+1)))}else t.$set(t.settings,"ragEnabled",l)}}}),t._v(" Enable Retrieval-Augmented Generation ")]),e("p",{staticClass:"hint"},[t._v(" When enabled, bots can index approved files and surface relevant snippets in conversations. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"embedding-endpoint"}},[t._v("Embedding API Endpoint")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.embeddingApiEndpoint,expression:"settings.embeddingApiEndpoint"}],attrs:{id:"embedding-endpoint",type:"url",name:"educai-embedding-endpoint",autocomplete:"off",placeholder:"https://chat-ai.academiccloud.de/v1/embeddings"},domProps:{value:t.settings.embeddingApiEndpoint},on:{input:function(i){i.target.composing||t.$set(t.settings,"embeddingApiEndpoint",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Optional. Leave blank to reuse the chat completion endpoint. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"embedding-key"}},[t._v("Embedding API Key")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.embeddingApiKey,expression:"settings.embeddingApiKey"}],attrs:{id:"embedding-key",type:"password",name:"educai-embedding-key",autocomplete:"new-password",placeholder:"sk-..."},domProps:{value:t.settings.embeddingApiKey},on:{input:function(i){i.target.composing||t.$set(t.settings,"embeddingApiKey",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Optional. Provide only if embeddings require a different credential. Stored securely and never displayed back. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"embedding-model"}},[t._v("Embedding Model")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.embeddingModel,expression:"settings.embeddingModel"}],attrs:{id:"embedding-model",type:"text",placeholder:"multilingual-e5-large-instruct"},domProps:{value:t.settings.embeddingModel},on:{input:function(i){i.target.composing||t.$set(t.settings,"embeddingModel",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Set the provider-specific embedding model identifier (e.g., multilingual-e5-large-instruct, e5-mistral-7b-instruct). ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"embedding-rate-limit-mode"}},[t._v("Embedding Rate Limit Mode")]),e("select",{directives:[{name:"model",rawName:"v-model",value:t.settings.embeddingRateLimitMode,expression:"settings.embeddingRateLimitMode"}],attrs:{id:"embedding-rate-limit-mode"},on:{change:function(i){var s=Array.prototype.filter.call(i.target.options,function(a){return a.selected}).map(function(a){var l="_value"in a?a._value:a.value;return l});t.$set(t.settings,"embeddingRateLimitMode",i.target.multiple?s:s[0])}}},[e("option",{attrs:{value:"inherit"}},[t._v(" Inherit Chat Limits ")]),e("option",{attrs:{value:"disabled"}},[t._v(" Disabled ")]),e("option",{attrs:{value:"custom"}},[t._v(" Custom ")])]),t._m(10)]),t.settings.embeddingRateLimitMode==="custom"?e("div",{staticClass:"form-grid"},[e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"embedding-rate-limit-second"}},[t._v("Embedding Requests per Second")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.embeddingRateLimitSecond,expression:"settings.embeddingRateLimitSecond",modifiers:{number:!0}}],attrs:{id:"embedding-rate-limit-second",type:"number",min:"1",placeholder:"Optional"},domProps:{value:t.settings.embeddingRateLimitSecond},on:{input:function(i){i.target.composing||t.$set(t.settings,"embeddingRateLimitSecond",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Optional. Leave blank if the embedding provider does not expose second-level headers. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"embedding-rate-limit-minute"}},[t._v("Embedding Requests per Minute")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.embeddingRateLimitMinute,expression:"settings.embeddingRateLimitMinute",modifiers:{number:!0}}],attrs:{id:"embedding-rate-limit-minute",type:"number",min:"1",placeholder:"100"},domProps:{value:t.settings.embeddingRateLimitMinute},on:{input:function(i){i.target.composing||t.$set(t.settings,"embeddingRateLimitMinute",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Default for GWDG embeddings: 100/minute. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"embedding-rate-limit-hour"}},[t._v("Embedding Requests per Hour")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.embeddingRateLimitHour,expression:"settings.embeddingRateLimitHour",modifiers:{number:!0}}],attrs:{id:"embedding-rate-limit-hour",type:"number",min:"1",placeholder:"2000"},domProps:{value:t.settings.embeddingRateLimitHour},on:{input:function(i){i.target.composing||t.$set(t.settings,"embeddingRateLimitHour",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Default for GWDG embeddings: 2000/hour. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"embedding-rate-limit-day"}},[t._v("Embedding Requests per Day")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.embeddingRateLimitDay,expression:"settings.embeddingRateLimitDay",modifiers:{number:!0}}],attrs:{id:"embedding-rate-limit-day",type:"number",min:"1",placeholder:"4000"},domProps:{value:t.settings.embeddingRateLimitDay},on:{input:function(i){i.target.composing||t.$set(t.settings,"embeddingRateLimitDay",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Default for GWDG embeddings: 4000/day. ")])])]):t._e(),t.hasPendingEmbeddingConfigChange?e("div",{staticClass:"embedding-warning"},[e("strong",[t._v("Embedding settings changed.")]),t._m(11)]):t._e(),e("div",{staticClass:"form-grid"},[e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"rag-chunk-size"}},[t._v("Chunk Size (tokens)")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.ragChunkSize,expression:"settings.ragChunkSize",modifiers:{number:!0}}],attrs:{id:"rag-chunk-size",type:"number",min:"100",step:"10",placeholder:"750"},domProps:{value:t.settings.ragChunkSize},on:{input:function(i){i.target.composing||t.$set(t.settings,"ragChunkSize",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Controls how much text each embedding covers. Larger chunks reduce recall but speed ingestion. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"rag-chunk-overlap"}},[t._v("Chunk Overlap (tokens)")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.ragChunkOverlap,expression:"settings.ragChunkOverlap",modifiers:{number:!0}}],attrs:{id:"rag-chunk-overlap",type:"number",min:"0",step:"5",placeholder:"50"},domProps:{value:t.settings.ragChunkOverlap},on:{input:function(i){i.target.composing||t.$set(t.settings,"ragChunkOverlap",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Overlap provides continuity between adjacent chunks. Increase for long-form documents. ")])])])])]),e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("docling")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("docling")),"aria-controls":t.sectionId("docling")},on:{click:function(i){return t.toggleSection("docling")}}},[t._m(12),e("span",{staticClass:"accordion-meta"},[t._v(t._s(t.doclingSummary))]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("docling"),expression:"isSectionOpen('docling')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("docling")}},[t._m(13),e("div",{staticClass:"form-group"},[e("label",{staticClass:"checkbox"},[e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.doclingEnabled,expression:"settings.doclingEnabled"}],attrs:{type:"checkbox"},domProps:{checked:Array.isArray(t.settings.doclingEnabled)?t._i(t.settings.doclingEnabled,null)>-1:t.settings.doclingEnabled},on:{change:function(i){var s=t.settings.doclingEnabled,a=i.target,l=!!a.checked;if(Array.isArray(s)){var d=null,o=t._i(s,d);a.checked?o<0&&t.$set(t.settings,"doclingEnabled",s.concat([d])):o>-1&&t.$set(t.settings,"doclingEnabled",s.slice(0,o).concat(s.slice(o+1)))}else t.$set(t.settings,"doclingEnabled",l)}}}),t._v(" Enable Document Conversion ")]),e("p",{staticClass:"hint"},[t._v(" When enabled, PDF and Office documents attached to bots will be automatically converted to text for indexing. Uses the dedicated Docling API key when configured, otherwise falls back to the main API key. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"docling-endpoint"}},[t._v("Docling API Endpoint")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.doclingApiEndpoint,expression:"settings.doclingApiEndpoint"}],attrs:{id:"docling-endpoint",type:"url",name:"educai-docling-endpoint",autocomplete:"off",placeholder:"https://chat-ai.academiccloud.de/v1/documents/convert"},domProps:{value:t.settings.doclingApiEndpoint},on:{input:function(i){i.target.composing||t.$set(t.settings,"doclingApiEndpoint",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Optional. Leave blank to use the default Academic Cloud endpoint. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"docling-key"}},[t._v("Docling API Key")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.doclingApiKey,expression:"settings.doclingApiKey"}],attrs:{id:"docling-key",type:"password",name:"educai-docling-key",autocomplete:"new-password",placeholder:"sk-..."},domProps:{value:t.settings.doclingApiKey},on:{input:function(i){i.target.composing||t.$set(t.settings,"doclingApiKey",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Optional. Provide only if Docling requires a different credential. "+t._s(t.doclingKeyHint)+" ")])]),e("div",{staticClass:"button-row"},[e("button",{staticClass:"button",attrs:{type:"button",disabled:t.doclingTesting},on:{click:t.testDoclingConnection}},[t._v(" "+t._s(t.doclingTesting?"Testing…":"Test Connection")+" ")]),t.doclingTestResult?e("span",{class:t.doclingTestResult.success?"success-text":"error-text"},[t._v(" "+t._s(t.doclingTestResult.success?"✓ Connection successful":"✗ "+t.doclingTestResult.error)+" ")]):t._e()]),t._m(14)])]),e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("media")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("media")),"aria-controls":t.sectionId("media")},on:{click:function(i){return t.toggleSection("media")}}},[t._m(15),e("span",{staticClass:"accordion-meta"},[t._v(t._s(t.mediaSummary))]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("media"),expression:"isSectionOpen('media')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("media")}},[t._m(16),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"vision-endpoint"}},[t._v("Vision API Endpoint")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.visionApiEndpoint,expression:"settings.visionApiEndpoint"}],attrs:{id:"vision-endpoint",type:"url",name:"educai-vision-endpoint",autocomplete:"off",placeholder:"https://chat-ai.academiccloud.de/v1/chat/completions"},domProps:{value:t.settings.visionApiEndpoint},on:{input:function(i){i.target.composing||t.$set(t.settings,"visionApiEndpoint",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Optional. Leave blank to reuse the main chat completion endpoint. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"vision-key"}},[t._v("Vision API Key")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.visionApiKey,expression:"settings.visionApiKey"}],attrs:{id:"vision-key",type:"password",name:"educai-vision-key",autocomplete:"new-password",placeholder:"sk-..."},domProps:{value:t.settings.visionApiKey},on:{input:function(i){i.target.composing||t.$set(t.settings,"visionApiKey",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Optional. Leave blank to reuse the main API key. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"vision-model"}},[t._v("Vision Model")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.visionModel,expression:"settings.visionModel"}],attrs:{id:"vision-model",type:"text",placeholder:"llama-4-scout-17b-16e-instruct"},domProps:{value:t.settings.visionModel},on:{input:function(i){i.target.composing||t.$set(t.settings,"visionModel",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Set a model identifier that supports image inputs. ")])]),e("div",{staticClass:"button-row"},[e("button",{staticClass:"button",attrs:{type:"button",disabled:t.visionTesting},on:{click:t.testVisionConnection}},[t._v(" "+t._s(t.visionTesting?"Testing…":"Test Vision")+" ")]),t.visionTestResult?e("span",{class:t.visionTestResult.success?"success-text":"error-text"},[t._v(" "+t._s(t.visionTestResult.success?"✓ Vision connection successful":"✗ "+t.visionTestResult.error)+" ")]):t._e()]),e("div",{staticClass:"section-divider",attrs:{role:"presentation"}}),t._m(17),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"speech-endpoint"}},[t._v("Speech API Endpoint")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.speechApiEndpoint,expression:"settings.speechApiEndpoint"}],attrs:{id:"speech-endpoint",type:"url",name:"educai-speech-endpoint",autocomplete:"off",placeholder:"https://chat-ai.academiccloud.de/v1/audio/transcriptions"},domProps:{value:t.settings.speechApiEndpoint},on:{input:function(i){i.target.composing||t.$set(t.settings,"speechApiEndpoint",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Optional. Leave blank to derive an OpenAI-compatible transcription endpoint from the main chat endpoint. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"speech-key"}},[t._v("Speech API Key")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.speechApiKey,expression:"settings.speechApiKey"}],attrs:{id:"speech-key",type:"password",name:"educai-speech-key",autocomplete:"new-password",placeholder:"sk-..."},domProps:{value:t.settings.speechApiKey},on:{input:function(i){i.target.composing||t.$set(t.settings,"speechApiKey",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Optional. Leave blank to reuse the main API key. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"speech-model"}},[t._v("Speech Model")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.speechModel,expression:"settings.speechModel"}],attrs:{id:"speech-model",type:"text",placeholder:"whisper-large-v3"},domProps:{value:t.settings.speechModel},on:{input:function(i){i.target.composing||t.$set(t.settings,"speechModel",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" Set the transcription model identifier, for example a Whisper-compatible model. ")])]),e("div",{staticClass:"button-row"},[e("button",{staticClass:"button",attrs:{type:"button",disabled:t.speechTesting},on:{click:t.testSpeechConnection}},[t._v(" "+t._s(t.speechTesting?"Testing…":"Test Speech")+" ")]),t.speechTestResult?e("span",{class:t.speechTestResult.success?"success-text":"error-text"},[t._v(" "+t._s(t.speechTestResult.success?"✓ Speech connection successful":"✗ "+t.speechTestResult.error)+" ")]):t._e()])])]),e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("limits")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("limits")),"aria-controls":t.sectionId("limits")},on:{click:function(i){return t.toggleSection("limits")}}},[t._m(18),e("span",{staticClass:"accordion-meta"},[t._v(t._s(t.rateLimitSummary))]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("limits"),expression:"isSectionOpen('limits')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("limits")}},[t._m(19),e("div",{staticClass:"form-group"},[e("label",{staticClass:"checkbox"},[e("input",{directives:[{name:"model",rawName:"v-model",value:t.settings.rateLimitEnabled,expression:"settings.rateLimitEnabled"}],attrs:{type:"checkbox"},domProps:{checked:Array.isArray(t.settings.rateLimitEnabled)?t._i(t.settings.rateLimitEnabled,null)>-1:t.settings.rateLimitEnabled},on:{change:function(i){var s=t.settings.rateLimitEnabled,a=i.target,l=!!a.checked;if(Array.isArray(s)){var d=null,o=t._i(s,d);a.checked?o<0&&t.$set(t.settings,"rateLimitEnabled",s.concat([d])):o>-1&&t.$set(t.settings,"rateLimitEnabled",s.slice(0,o).concat(s.slice(o+1)))}else t.$set(t.settings,"rateLimitEnabled",l)}}}),t._v(" Enable Rate Limit Queue ")]),e("p",{staticClass:"hint"},[t._v(" When enabled, requests exceeding rate limits will be queued instead of failing. A background job will process queued requests as capacity becomes available. ")])]),t.settings.rateLimitEnabled?e("div",{staticClass:"form-grid"},[e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"rate-limit-second"}},[t._v("Requests per Second")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.rateLimitSecond,expression:"settings.rateLimitSecond",modifiers:{number:!0}}],attrs:{id:"rate-limit-second",type:"number",min:"1",placeholder:"Optional"},domProps:{value:t.settings.rateLimitSecond},on:{input:function(i){i.target.composing||t.$set(t.settings,"rateLimitSecond",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Optional. Leave blank for providers like GWDG that only expose minute/hour/day headers. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"rate-limit-minute"}},[t._v("Requests per Minute")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.rateLimitMinute,expression:"settings.rateLimitMinute",modifiers:{number:!0}}],attrs:{id:"rate-limit-minute",type:"number",min:"1",placeholder:"30"},domProps:{value:t.settings.rateLimitMinute},on:{input:function(i){i.target.composing||t.$set(t.settings,"rateLimitMinute",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Maximum requests per minute (GWDG default: 30) ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"rate-limit-hour"}},[t._v("Requests per Hour")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.rateLimitHour,expression:"settings.rateLimitHour",modifiers:{number:!0}}],attrs:{id:"rate-limit-hour",type:"number",min:"1",placeholder:"200"},domProps:{value:t.settings.rateLimitHour},on:{input:function(i){i.target.composing||t.$set(t.settings,"rateLimitHour",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Maximum requests per hour (GWDG default: 200) ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"rate-limit-day"}},[t._v("Requests per Day")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.rateLimitDay,expression:"settings.rateLimitDay",modifiers:{number:!0}}],attrs:{id:"rate-limit-day",type:"number",min:"1",placeholder:"1000"},domProps:{value:t.settings.rateLimitDay},on:{input:function(i){i.target.composing||t.$set(t.settings,"rateLimitDay",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Maximum requests per day (GWDG default: 1000) ")])])]):t._e(),t.settings.rateLimitEnabled?e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"rate-limit-queue-message"}},[t._v("Queue Message")]),e("textarea",{directives:[{name:"model",rawName:"v-model",value:t.settings.rateLimitQueueMessage,expression:"settings.rateLimitQueueMessage"}],attrs:{id:"rate-limit-queue-message",rows:"4",placeholder:"⏳ Your request has been queued. Position: {position}, estimated wait: ~{wait} seconds."},domProps:{value:t.settings.rateLimitQueueMessage},on:{input:function(i){i.target.composing||t.$set(t.settings,"rateLimitQueueMessage",i.target.value)}}}),t._v(" "),t._m(20)]):t._e(),t.settings.rateLimitEnabled?e("div",{staticClass:"rate-limit-status"},[e("div",{staticClass:"button-row"},[e("button",{staticClass:"button",attrs:{type:"button",disabled:t.rateLimitLoading},on:{click:t.loadRateLimitStatus}},[t._v(" "+t._s(t.rateLimitLoading?"Loading…":"Refresh Status")+" ")]),e("button",{staticClass:"button primary",attrs:{type:"button",disabled:t.queueProcessing||!t.chatQueueStats.pending},on:{click:t.processQueueNow}},[t._v(" "+t._s(t.queueProcessing?"Processing…":"Process Queue Now")+" ")])]),t.rateLimitStatus?e("div",{staticClass:"status-grid"},[e("div",{staticClass:"status-card"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.chatQueueStats.pending))]),e("div",{staticClass:"status-label"},[t._v("Queued")])]),e("div",{staticClass:"status-card"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.chatQueueStats.processing))]),e("div",{staticClass:"status-label"},[t._v("Processing")])])]):t._e(),t.rateLimitStatus?e("p",{staticClass:"hint rate-limit-hint"},[t._v(" Chat requests and embeddings are tracked separately. The cards below show the last observed provider headers per endpoint. ")]):t._e(),t.rateLimitStatus?e("div",{staticClass:"rate-limit-subsection"},[t._m(21),e("div",{staticClass:"status-grid status-grid--compact"},[e("div",{staticClass:"status-card",class:t.chatCanProcess?"status-ok":"status-limited"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.chatCanProcess?"✓":"⏳"))]),e("div",{staticClass:"status-label"},[t._v(t._s(t.chatCanProcess?"Available":"Rate Limited"))])]),t.chatRateLimitState?.limit_minute?e("div",{staticClass:"status-card"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.chatRateLimitState.remaining_minute)+"/"+t._s(t.chatRateLimitState.limit_minute))]),e("div",{staticClass:"status-label"},[t._v("Per Minute")])]):t._e(),t.chatRateLimitState?.limit_second?e("div",{staticClass:"status-card"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.chatRateLimitState.remaining_second)+"/"+t._s(t.chatRateLimitState.limit_second))]),e("div",{staticClass:"status-label"},[t._v("Per Second")])]):t._e(),t.chatRateLimitState?.limit_hour?e("div",{staticClass:"status-card"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.chatRateLimitState.remaining_hour)+"/"+t._s(t.chatRateLimitState.limit_hour))]),e("div",{staticClass:"status-label"},[t._v("Per Hour")])]):t._e(),t.chatRateLimitState?.limit_day?e("div",{staticClass:"status-card"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.chatRateLimitState.remaining_day)+"/"+t._s(t.chatRateLimitState.limit_day))]),e("div",{staticClass:"status-label"},[t._v("Per Day")])]):t._e()])]):t._e(),t.rateLimitStatus?e("div",{staticClass:"rate-limit-subsection"},[t._m(22),t.embeddingRateLimitModeStatus==="disabled"?e("p",{staticClass:"hint"},[t._v(" Embedding rate limiting is disabled. Requests go straight to the configured embedding endpoint. ")]):t.embeddingStatusSource==="configured"?e("p",{staticClass:"hint"},[t._v(" "+t._s(t.embeddingConfiguredHint)+" ")]):t.embeddingStatusSource==="observed"?e("p",{staticClass:"hint"},[t._v(" Showing the last observed embedding endpoint headers. ")]):t._e(),t.embeddingRateLimitModeStatus!=="disabled"&&t.embeddingRateLimitState?e("div",{staticClass:"status-grid status-grid--compact"},[e("div",{staticClass:"status-card",class:t.embeddingCanProcess?"status-ok":"status-limited"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.embeddingCanProcess?"✓":"⏳"))]),e("div",{staticClass:"status-label"},[t._v(t._s(t.embeddingCanProcess?"Available":"Rate Limited"))])]),t.embeddingRateLimitState.limit_minute?e("div",{staticClass:"status-card"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.embeddingRateLimitState.remaining_minute)+"/"+t._s(t.embeddingRateLimitState.limit_minute))]),e("div",{staticClass:"status-label"},[t._v("Per Minute")])]):t._e(),t.embeddingRateLimitState.limit_second?e("div",{staticClass:"status-card"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.embeddingRateLimitState.remaining_second)+"/"+t._s(t.embeddingRateLimitState.limit_second))]),e("div",{staticClass:"status-label"},[t._v("Per Second")])]):t._e(),t.embeddingRateLimitState.limit_hour?e("div",{staticClass:"status-card"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.embeddingRateLimitState.remaining_hour)+"/"+t._s(t.embeddingRateLimitState.limit_hour))]),e("div",{staticClass:"status-label"},[t._v("Per Hour")])]):t._e(),t.embeddingRateLimitState.limit_day?e("div",{staticClass:"status-card"},[e("div",{staticClass:"status-value"},[t._v(t._s(t.embeddingRateLimitState.remaining_day)+"/"+t._s(t.embeddingRateLimitState.limit_day))]),e("div",{staticClass:"status-label"},[t._v("Per Day")])]):t._e()]):t.embeddingRateLimitModeStatus!=="disabled"?e("p",{staticClass:"hint"},[t._v(" No embedding rate-limit headers observed yet. ")]):t._e()]):t._e()]):t._e()])]),e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("memory")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("memory")),"aria-controls":t.sectionId("memory")},on:{click:function(i){return t.toggleSection("memory")}}},[t._m(23),e("span",{staticClass:"accordion-meta"},[t._v(t._s(t.memorySummary))]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("memory"),expression:"isSectionOpen('memory')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("memory")}},[t._m(24),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"conversation-context-tokens"}},[t._v("Context Token Limit")]),e("input",{directives:[{name:"model",rawName:"v-model.number",value:t.settings.conversationContextTokens,expression:"settings.conversationContextTokens",modifiers:{number:!0}}],attrs:{id:"conversation-context-tokens",type:"number",min:"1000",step:"500",placeholder:"8000"},domProps:{value:t.settings.conversationContextTokens},on:{input:function(i){i.target.composing||t.$set(t.settings,"conversationContextTokens",t._n(i.target.value))},blur:function(i){return t.$forceUpdate()}}}),e("p",{staticClass:"hint"},[t._v(" Maximum tokens of conversation history to include. Default: 8000 (safe for most models). Higher values provide more context but may exceed model limits. Common limits: Llama 3.1/3.3: 128K, Qwen 3: 32K-128K, Mistral: 32K-128K. ")])])])]),e("button",{staticClass:"button primary",attrs:{type:"submit",disabled:t.saving}},[t._v(" "+t._s(t.saving?"Saving...":"Save Settings")+" ")])]),e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("tools")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("tools")),"aria-controls":t.sectionId("tools")},on:{click:function(i){return t.toggleSection("tools")}}},[t._m(25),e("span",{staticClass:"accordion-meta"},[t._v(t._s(t.toolsSummary))]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("tools"),expression:"isSectionOpen('tools')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("tools")}},[e("div",{staticClass:"section-heading"},[e("h4",[t._v("Agent Tool Registry")]),e("button",{staticClass:"button primary",attrs:{type:"button"},on:{click:t.openCreateTool}},[t._v(" Register Tool ")])]),e("p",{staticClass:"hint section-body"},[t._v(" Register Model Context Protocol endpoints. Enabled tools become available for bot creators to attach to their assistants. ")]),t.loadingTools?e("div",{staticClass:"hint section-body"},[t._v(" Loading tools… ")]):[t.tools.length===0?e("div",{staticClass:"hint section-body"},[t._v(" No tools registered yet. ")]):e("div",{staticClass:"tool-list section-body"},t._l(t.tools,function(i){return e("div",{key:i.id,staticClass:"tool-row",class:{"tool-row--disabled":!i.enabled}},[e("div",{staticClass:"tool-info"},[e("div",{staticClass:"tool-name"},[e("span",{staticClass:"tool-name__label"},[t._v(t._s(i.name))]),e("span",{staticClass:"badge",class:i.enabled?"badge-success":"badge-muted"},[t._v(" "+t._s(i.enabled?"Enabled":"Disabled")+" ")])]),i.description?e("p",{staticClass:"hint"},[t._v(" "+t._s(i.description)+" ")]):t._e(),e("p",{staticClass:"hint"},[t._v(" Endpoint: "),e("code",[t._v(t._s(i.mcp_endpoint_url))])])]),e("div",{staticClass:"tool-actions"},[e("button",{staticClass:"button",attrs:{type:"button",disabled:t.toolSaving},on:{click:function(s){return t.toggleToolEnabled(i)}}},[t._v(" "+t._s(i.enabled?"Disable":"Enable")+" ")]),e("button",{staticClass:"button",attrs:{type:"button",disabled:t.toolSaving},on:{click:function(s){return t.editTool(i)}}},[t._v(" Edit ")]),e("button",{staticClass:"button danger",attrs:{type:"button",disabled:t.toolSaving},on:{click:function(s){return t.deleteTool(i)}}},[t._v(" Delete ")])])])}),0)],t.toolFormVisible?e("div",{staticClass:"tool-form"},[e("h4",[t._v(t._s(t.toolForm.id?"Edit Tool":"Register Tool"))]),e("form",{on:{submit:function(i){return i.preventDefault(),t.saveTool.apply(null,arguments)}}},[e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"tool-name"}},[t._v("Tool Name")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.toolForm.name,expression:"toolForm.name"}],attrs:{id:"tool-name",type:"text",required:""},domProps:{value:t.toolForm.name},on:{input:function(i){i.target.composing||t.$set(t.toolForm,"name",i.target.value)}}})]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"tool-endpoint"}},[t._v("MCP Endpoint URL")]),e("input",{directives:[{name:"model",rawName:"v-model",value:t.toolForm.mcpEndpointUrl,expression:"toolForm.mcpEndpointUrl"}],attrs:{id:"tool-endpoint",type:"url",name:"educai-mcp-endpoint",autocomplete:"off",required:""},domProps:{value:t.toolForm.mcpEndpointUrl},on:{input:function(i){i.target.composing||t.$set(t.toolForm,"mcpEndpointUrl",i.target.value)}}}),e("p",{staticClass:"hint"},[t._v(" HTTP or WebSocket URL exposing the MCP server. ")])]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"tool-description"}},[t._v("Description")]),e("textarea",{directives:[{name:"model",rawName:"v-model",value:t.toolForm.description,expression:"toolForm.description"}],attrs:{id:"tool-description",rows:"3",placeholder:"Short human-readable summary"},domProps:{value:t.toolForm.description},on:{input:function(i){i.target.composing||t.$set(t.toolForm,"description",i.target.value)}}})]),e("div",{staticClass:"form-group"},[e("label",{attrs:{for:"tool-auth"}},[t._v("Authentication JSON")]),e("textarea",{directives:[{name:"model",rawName:"v-model",value:t.toolForm.authentication,expression:"toolForm.authentication"}],attrs:{id:"tool-auth",rows:"4",placeholder:'{"headers":{"Authorization":"Bearer <token>"}}'},domProps:{value:t.toolForm.authentication},on:{input:function(i){i.target.composing||t.$set(t.toolForm,"authentication",i.target.value)}}}),t._v(" "),e("p",{staticClass:"hint"},[t._v(" Provide JSON describing auth headers or tokens. "+t._s(t.toolHasStoredAuth?"Stored credentials are kept unless you enter new values (re-enter to test or rotate).":"Leave blank when no authentication is required.")+" ")])]),e("div",{staticClass:"form-group"},[e("label",{staticClass:"checkbox"},[e("input",{directives:[{name:"model",rawName:"v-model",value:t.toolForm.enabled,expression:"toolForm.enabled"}],attrs:{type:"checkbox"},domProps:{checked:Array.isArray(t.toolForm.enabled)?t._i(t.toolForm.enabled,null)>-1:t.toolForm.enabled},on:{change:function(i){var s=t.toolForm.enabled,a=i.target,l=!!a.checked;if(Array.isArray(s)){var d=null,o=t._i(s,d);a.checked?o<0&&t.$set(t.toolForm,"enabled",s.concat([d])):o>-1&&t.$set(t.toolForm,"enabled",s.slice(0,o).concat(s.slice(o+1)))}else t.$set(t.toolForm,"enabled",l)}}}),t._v(" Enabled by default ")])]),e("div",{staticClass:"button-row"},[e("button",{staticClass:"button primary",attrs:{type:"submit",disabled:t.toolSaving}},[t._v(" "+t._s(t.toolForm.id?"Save Changes":"Create Tool")+" ")]),e("button",{staticClass:"button",attrs:{type:"button",disabled:t.toolSaving},on:{click:t.closeToolForm}},[t._v(" Cancel ")]),e("button",{staticClass:"button",attrs:{type:"button",disabled:t.toolTesting},on:{click:t.testToolConnection}},[t._v(" "+t._s(t.toolTesting?"Testing…":"Test Connection")+" ")])])]),t.toolTestResult?e("pre",{staticClass:"tool-test-result"},[t._v(" "+t._s(t.toolTestResult)+`
`)]):t._e()]):t._e()],2)]),e("section",{staticClass:"accordion-section",class:{"accordion-section--open":t.isSectionOpen("bots")}},[e("button",{staticClass:"accordion-header",attrs:{type:"button","aria-expanded":String(t.isSectionOpen("bots")),"aria-controls":t.sectionId("bots")},on:{click:function(i){return t.toggleSection("bots")}}},[t._m(26),e("span",{staticClass:"accordion-meta"},[t._v(t._s(t.botsSummary))]),e("span",{staticClass:"accordion-chevron",attrs:{"aria-hidden":"true"}},[t._v("")])]),e("div",{directives:[{name:"show",rawName:"v-show",value:t.isSectionOpen("bots"),expression:"isSectionOpen('bots')"}],staticClass:"accordion-panel",attrs:{id:t.sectionId("bots")}},[e("div",{staticClass:"section-heading"},[e("h4",[t._v("All Bots")]),e("div",{staticClass:"actions"},[e("button",{staticClass:"button",attrs:{type:"button",disabled:t.loadingAllBots},on:{click:t.loadAllBots}},[t._v(" "+t._s(t.loadingAllBots?"Loading…":"Refresh")+" ")])])]),t.loadingAllBots?e("div",{staticClass:"hint"},[t._v("Loading bots…")]):t.allBots.length===0?e("div",{staticClass:"hint"},[t._v("No bots found.")]):e("div",{staticClass:"bot-table"},[t._m(27),t._l(t.allBots,function(i){return e("div",{key:i.id,staticClass:"bot-row"},[e("span",[t._v(t._s(i.bot_name))]),e("span",[t._v(t._s(i.mention_name))]),e("span",[t._v(t._s(i.user_id))]),e("span",[t._v(t._s(t.formatVisibility(i.visibility)))]),e("span",{staticClass:"status-pill",class:`status-${i.approval_status||"approved"}`},[t._v(" "+t._s(i.approval_status||"approved")+" ")]),e("span",{staticClass:"actions-col"},[e("button",{staticClass:"button",attrs:{type:"button"},on:{click:function(s){return t.editBotAdmin(i)}}},[t._v("Edit")]),e("button",{staticClass:"button danger",attrs:{type:"button"},on:{click:function(s){return t.deleteBotAdmin(i)}}},[t._v("Delete")])])])})],2)])]),t.adminEditingBot?e("BotForm",{attrs:{bot:t.adminEditingBot,"user-permissions":{isAdmin:!0,isGroupAdmin:!0,isTeamAdmin:!0,hasApprovalRights:!0,adminGroups:[],adminTeams:[]}},on:{save:t.saveBotAdmin,cancel:t.closeAdminEditor}}):t._e()],1)},k=[function(){var t=this,e=t._self._c;return e("h3",{staticClass:"settings-title"},[e("span",{staticClass:"icon-settings"}),t._v(" Administrator Settings ")])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("Appearance")]),e("span",{staticClass:"accordion-description"},[t._v("App icon variants for settings and navigation surfaces.")])])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("Essentials")]),e("span",{staticClass:"accordion-description"},[t._v("Default creativity and Talk webhook secret.")])])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("Model Endpoints")]),e("span",{staticClass:"accordion-description"},[t._v("Primary and optional secondary OpenAI-compatible providers.")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"section-heading"},[e("h4",[t._v("Primary Endpoint")]),e("p",{staticClass:"hint"},[t._v(" Used for existing raw model names and as the default provider. ")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"section-heading"},[e("h4",[t._v("Secondary Endpoint")]),e("p",{staticClass:"hint"},[t._v(" Optional. When configured, bots can choose models from both endpoints. ")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"section-heading"},[e("h4",[t._v("Model Selection")])])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("Fallback & Timeouts")]),e("span",{staticClass:"accordion-description"},[t._v("One retry model and LLM request time limits.")])])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("RAG & Embeddings")]),e("span",{staticClass:"accordion-description"},[t._v("Knowledge retrieval, embedding provider, limits, and chunking.")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"section-heading"},[e("h4",[t._v("Retrieval-Augmented Generation")]),e("p",{staticClass:"hint"},[t._v(" Configure embeddings and chunking parameters used when bots index Nextcloud files. ")])])},function(){var t=this,e=t._self._c;return e("p",{staticClass:"hint"},[t._v(" Use "),e("code",[t._v("inherit")]),t._v(" to follow chat queue limits, "),e("code",[t._v("disabled")]),t._v(" for self-hosted embedding endpoints without throttling, or "),e("code",[t._v("custom")]),t._v(" for dedicated embedding quotas. ")])},function(){var t=this,e=t._self._c;return e("p",[t._v(" Save first, then run "),e("code",[t._v("Reindex All Embeddings")]),t._v(" to rebuild bot vectors for the active embedding model. ")])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("Document Conversion")]),e("span",{staticClass:"accordion-description"},[t._v("Docling conversion for PDF and Office ingestion.")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"section-heading"},[e("h4",[t._v("Document Conversion (Docling)")]),e("p",{staticClass:"hint"},[t._v(" Enable conversion of PDF, DOCX, PPTX and other binary documents to text for RAG ingestion. This uses the Docling API to extract content from documents that cannot be read as plain text. ")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"supported-formats"},[e("p",{staticClass:"hint"},[e("strong",[t._v("Supported formats:")]),t._v(" PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, PNG, JPG, TIFF, BMP ")])])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("Media Tools")]),e("span",{staticClass:"accordion-description"},[t._v("Image understanding and speech-to-text models.")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"section-heading"},[e("h4",[t._v("Image Understanding")]),e("p",{staticClass:"hint"},[t._v(" Configure a multimodal model for image attachments uploaded in Nextcloud Talk. Bots only get image understanding when the built-in image tool is enabled for that bot. ")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"section-heading"},[e("h4",[t._v("Speech To Text")]),e("p",{staticClass:"hint"},[t._v(" Configure a transcription model for audio and voice-message attachments uploaded in Nextcloud Talk. Bots only get this ability when the built-in audio tool is enabled for that bot. ")])])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("Rate Limits")]),e("span",{staticClass:"accordion-description"},[t._v("LLM queue limits, live counters, and queue processing.")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"section-heading"},[e("h4",[t._v("LLM Rate Limiting Queue")]),e("p",{staticClass:"hint"},[t._v(" When enabled, requests that exceed the LLM provider's rate limit will be queued and processed automatically via background jobs. Configure limits based on your provider's quota. For GWDG, use 30/minute, 200/hour, and 1000/day. Leave per-second empty if your provider does not expose a second-level window. ")])])},function(){var t=this,e=t._self._c;return e("p",{staticClass:"hint"},[t._v(" Message shown to users when their request is queued. Use "),e("code",[t._v("{position}")]),t._v(" for queue position and "),e("code",[t._v("{wait}")]),t._v(" for estimated wait time in seconds. Leave empty for default message. ")])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"rate-limit-subsection__header"},[e("h5",[t._v("Chat / Queue")]),e("p",{staticClass:"hint"},[t._v("Used for bot replies and queued requests.")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"rate-limit-subsection__header"},[e("h5",[t._v("Embeddings")]),e("p",{staticClass:"hint"},[t._v("Embeddings are tracked independently from the chat queue.")])])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("Conversation Memory")]),e("span",{staticClass:"accordion-description"},[t._v("Context history budget for bot replies.")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"section-heading"},[e("h4",[t._v("Conversation Memory")]),e("p",{staticClass:"hint"},[t._v(" Configure how much conversation history is sent to the LLM for context. Using token-based limits prevents context window overflow with large messages. ")])])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("Agent Tools")]),e("span",{staticClass:"accordion-description"},[t._v("Model Context Protocol tool registry.")])])},function(){var t=this,e=t._self._c;return e("span",{staticClass:"accordion-heading"},[e("span",{staticClass:"accordion-title"},[t._v("All Bots")]),e("span",{staticClass:"accordion-description"},[t._v("Admin overview and direct bot maintenance.")])])},function(){var t=this,e=t._self._c;return e("div",{staticClass:"bot-row bot-row--header"},[e("span",[t._v("Name")]),e("span",[t._v("Mention")]),e("span",[t._v("Owner")]),e("span",[t._v("Visibility")]),e("span",[t._v("Status")]),e("span",{staticClass:"actions-col"},[t._v("Actions")])])}],E=C(A,M,k,!1,null,"f1e4250d");const L=E.exports,T=w.extend(L);new T().$mount("#educai-admin-root"),f();
//# sourceMappingURL=educai-admin.mjs.map