Files
nuqloud-ai/js/educai-personal.mjs.map
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

1 line
71 KiB
Plaintext

{"version":3,"file":"educai-personal.mjs","sources":["../src/views/PersonalActivity.vue","../src/components/BotCard.vue","../src/views/PersonalBots.vue","../src/views/PersonalSettings.vue","../src/personal.js"],"sourcesContent":["<template>\n\t<section class=\"activity-view\">\n\t\t<div class=\"activity-title-row\">\n\t\t\t<div>\n\t\t\t\t<h3>Activity</h3>\n\t\t\t\t<p>This page shows {{ APP_DISPLAY_NAME }} interactions you started. Detailed tool inputs and results are visible only to you.</p>\n\t\t\t</div>\n\t\t\t<div class=\"activity-actions\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"button\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"loadTraces\">\n\t\t\t\t\t<span class=\"icon-history\" />\n\t\t\t\t\tRefresh\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"button error\"\n\t\t\t\t\t:disabled=\"traces.length === 0 || clearing\"\n\t\t\t\t\t@click=\"clearActivity\">\n\t\t\t\t\t<span class=\"icon-delete\" />\n\t\t\t\t\tClear my activity\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<p class=\"privacy-note\">\n\t\t\tOnly interactions started by your account are shown here. We take privacy seriously: your trace history is private to your account and cannot be viewed by anyone else.\n\t\t</p>\n\n\t\t<div class=\"filters\">\n\t\t\t<label>\n\t\t\t\t<span>Search</span>\n\t\t\t\t<input\n\t\t\t\t\tv-model.trim=\"filters.q\"\n\t\t\t\t\ttype=\"search\"\n\t\t\t\t\tplaceholder=\"Message, bot, or error\"\n\t\t\t\t\t@keyup.enter=\"applyFilters\">\n\t\t\t</label>\n\t\t\t<label>\n\t\t\t\t<span>Status</span>\n\t\t\t\t<select v-model=\"filters.status\" @change=\"applyFilters\">\n\t\t\t\t\t<option value=\"\">Any status</option>\n\t\t\t\t\t<option value=\"running\">Running</option>\n\t\t\t\t\t<option value=\"success\">Success</option>\n\t\t\t\t\t<option value=\"error\">Error</option>\n\t\t\t\t\t<option value=\"partial\">Partial</option>\n\t\t\t\t</select>\n\t\t\t</label>\n\t\t\t<label>\n\t\t\t\t<span>Bot mention name</span>\n\t\t\t\t<input\n\t\t\t\t\tv-model.trim=\"filters.botMentionName\"\n\t\t\t\t\ttype=\"search\"\n\t\t\t\t\t@keyup.enter=\"applyFilters\">\n\t\t\t</label>\n\t\t\t<label>\n\t\t\t\t<span>From</span>\n\t\t\t\t<input\n\t\t\t\t\tv-model=\"filters.fromDate\"\n\t\t\t\t\ttype=\"date\"\n\t\t\t\t\t@change=\"applyFilters\">\n\t\t\t</label>\n\t\t\t<label>\n\t\t\t\t<span>To</span>\n\t\t\t\t<input\n\t\t\t\t\tv-model=\"filters.toDate\"\n\t\t\t\t\ttype=\"date\"\n\t\t\t\t\t@change=\"applyFilters\">\n\t\t\t</label>\n\t\t\t<label class=\"checkbox-label\">\n\t\t\t\t<input v-model=\"filters.onlyErrors\" type=\"checkbox\" @change=\"applyFilters\">\n\t\t\t\t<span>Only errors</span>\n\t\t\t</label>\n\t\t\t<label class=\"checkbox-label\">\n\t\t\t\t<input v-model=\"filters.onlyWithTools\" type=\"checkbox\" @change=\"applyFilters\">\n\t\t\t\t<span>Only with tools</span>\n\t\t\t</label>\n\t\t\t<button type=\"button\" class=\"button primary\" @click=\"applyFilters\">\n\t\t\t\t<span class=\"icon-search\" />\n\t\t\t\tApply\n\t\t\t</button>\n\t\t</div>\n\n\t\t<div v-if=\"error\" class=\"error-banner\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<div v-if=\"loading\" class=\"loading\">\n\t\t\t<span class=\"icon-loading\" />\n\t\t\t<p>Loading activity...</p>\n\t\t</div>\n\n\t\t<div v-else-if=\"traces.length === 0\" class=\"empty-state\">\n\t\t\t<span class=\"icon-comment\" />\n\t\t\t<h3>No activity yet</h3>\n\t\t\t<p>Use a bot in Nextcloud Talk and your own trace history will appear here.</p>\n\t\t</div>\n\n\t\t<div v-else class=\"activity-layout\">\n\t\t\t<div class=\"trace-list\" role=\"list\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"trace in traces\"\n\t\t\t\t\t:key=\"trace.id\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"trace-row\"\n\t\t\t\t\t:class=\"{ selected: selectedTraceId === trace.id }\"\n\t\t\t\t\t@click=\"selectTrace(trace.id)\">\n\t\t\t\t\t<span class=\"trace-main\">\n\t\t\t\t\t\t<span class=\"trace-meta\">\n\t\t\t\t\t\t\t<span>{{ formatDate(trace.started_at) }}</span>\n\t\t\t\t\t\t\t<span>{{ trace.bot_mention_name || APP_DISPLAY_NAME }}</span>\n\t\t\t\t\t\t\t<span class=\"status-badge\" :class=\"trace.status\">{{ formatStatus(trace.status) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"trace-preview\">{{ trace.user_message_preview || 'No message preview' }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"trace-counts\">\n\t\t\t\t\t\t<span>{{ trace.tool_call_count }} tools</span>\n\t\t\t\t\t\t<span>{{ formatDuration(trace.duration_ms) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</button>\n\n\t\t\t\t<div class=\"pagination\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"button\"\n\t\t\t\t\t\t:disabled=\"offset === 0\"\n\t\t\t\t\t\t@click=\"previousPage\">\n\t\t\t\t\t\tPrevious\n\t\t\t\t\t</button>\n\t\t\t\t\t<span>{{ offset + 1 }}-{{ Math.min(offset + limit, total) }} of {{ total }}</span>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"button\"\n\t\t\t\t\t\t:disabled=\"offset + limit >= total\"\n\t\t\t\t\t\t@click=\"nextPage\">\n\t\t\t\t\t\tNext\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<aside class=\"trace-detail\">\n\t\t\t\t<div v-if=\"detailLoading\" class=\"loading-small\">\n\t\t\t\t\t<span class=\"icon-loading\" />\n\t\t\t\t\t<span>Loading trace details...</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-else-if=\"detailError\" class=\"error-banner\">\n\t\t\t\t\t{{ detailError }}\n\t\t\t\t</div>\n\t\t\t\t<div v-else-if=\"selectedTrace\">\n\t\t\t\t\t<div class=\"detail-header\">\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<h4>{{ selectedTrace.bot_mention_name || (APP_DISPLAY_NAME + ' run') }}</h4>\n\t\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\t\t{{ formatDate(selectedTrace.started_at) }} - {{ formatStatus(selectedTrace.status) }} - {{ selectedTrace.event_count }} events\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"detail-actions\">\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclass=\"button\"\n\t\t\t\t\t\t\t\t@click=\"exportSelectedTrace\">\n\t\t\t\t\t\t\t\t<span class=\"icon-download\" />\n\t\t\t\t\t\t\t\tExport JSON\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclass=\"button error\"\n\t\t\t\t\t\t\t\t@click=\"deleteSelectedTrace\">\n\t\t\t\t\t\t\t\t<span class=\"icon-delete\" />\n\t\t\t\t\t\t\t\tDelete\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div class=\"run-summary\">\n\t\t\t\t\t\t<div><strong>Status</strong><span>{{ formatStatus(selectedTrace.status) }}</span></div>\n\t\t\t\t\t\t<div><strong>Duration</strong><span>{{ formatDuration(selectedTrace.duration_ms) }}</span></div>\n\t\t\t\t\t\t<div><strong>Tools</strong><span>{{ selectedTrace.tool_call_count }}</span></div>\n\t\t\t\t\t\t<div><strong>Tokens</strong><span>{{ formatTokens(selectedTrace.total_token_count) }}</span></div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<ol class=\"event-list\">\n\t\t\t\t\t\t<li v-for=\"event in events\" :key=\"event.id\" class=\"event-item\">\n\t\t\t\t\t\t\t<div class=\"event-header\">\n\t\t\t\t\t\t\t\t<span class=\"event-type\">{{ formatEventType(event.event_type) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"event.tool_name\" class=\"event-tool\">{{ event.tool_name }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"event.status\" class=\"status-badge\" :class=\"event.status\">{{ formatStatus(event.status) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"event-time\">{{ formatDate(event.created_at) }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<p v-if=\"event.error_message\" class=\"event-error\">\n\t\t\t\t\t\t\t\t{{ event.error_message }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<details v-if=\"getRawLlmPayload(event)\" class=\"raw-llm-payload\">\n\t\t\t\t\t\t\t\t<summary>Raw LLM payload</summary>\n\t\t\t\t\t\t\t\t<pre>{{ formatJson(getRawLlmPayload(event)) }}</pre>\n\t\t\t\t\t\t\t</details>\n\t\t\t\t\t\t\t<div v-if=\"event.payload_preview\" class=\"event-block\">\n\t\t\t\t\t\t\t\t<strong>Payload</strong>\n\t\t\t\t\t\t\t\t<pre>{{ event.payload_preview }}</pre>\n\t\t\t\t\t\t\t\t<details v-if=\"event.payload_json\">\n\t\t\t\t\t\t\t\t\t<summary>Show JSON</summary>\n\t\t\t\t\t\t\t\t\t<pre>{{ formatJson(event.payload_json) }}</pre>\n\t\t\t\t\t\t\t\t</details>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div v-if=\"event.result_preview\" class=\"event-block\">\n\t\t\t\t\t\t\t\t<strong>Result</strong>\n\t\t\t\t\t\t\t\t<pre>{{ event.result_preview }}</pre>\n\t\t\t\t\t\t\t\t<details v-if=\"event.result_json\">\n\t\t\t\t\t\t\t\t\t<summary>Show JSON</summary>\n\t\t\t\t\t\t\t\t\t<pre>{{ formatJson(event.result_json) }}</pre>\n\t\t\t\t\t\t\t\t</details>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div v-if=\"event.duration_ms !== null && event.duration_ms !== undefined\" class=\"event-duration\">\n\t\t\t\t\t\t\t\t{{ formatDuration(event.duration_ms) }}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ol>\n\t\t\t\t</div>\n\t\t\t\t<div v-else class=\"empty-detail\">\n\t\t\t\t\tSelect an activity run to inspect its timeline.\n\t\t\t\t</div>\n\t\t\t</aside>\n\t\t</div>\n\t</section>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { APP_DISPLAY_NAME } from '../branding.js'\nimport { showError, showSuccess } from '@nextcloud/dialogs'\nimport { generateUrl } from '@nextcloud/router'\n\nexport default {\n\tname: 'PersonalActivity',\n\n\tdata() {\n\t\treturn {\n\t\t\tAPP_DISPLAY_NAME,\n\t\t\ttraces: [],\n\t\t\ttotal: 0,\n\t\t\tlimit: 25,\n\t\t\toffset: 0,\n\t\t\tloading: false,\n\t\t\tclearing: false,\n\t\t\terror: '',\n\t\t\tselectedTraceId: null,\n\t\t\tselectedTrace: null,\n\t\t\tevents: [],\n\t\t\tdetailLoading: false,\n\t\t\tdetailError: '',\n\t\t\tfilters: {\n\t\t\t\tq: '',\n\t\t\t\tstatus: '',\n\t\t\t\tbotMentionName: '',\n\t\t\t\tfromDate: '',\n\t\t\t\ttoDate: '',\n\t\t\t\tonlyErrors: false,\n\t\t\t\tonlyWithTools: false,\n\t\t\t},\n\t\t}\n\t},\n\n\tmounted() {\n\t\tthis.loadTraces()\n\t},\n\n\tmethods: {\n\t\tasync loadTraces() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst params = {\n\t\t\t\t\tlimit: this.limit,\n\t\t\t\t\toffset: this.offset,\n\t\t\t\t}\n\t\t\t\tif (this.filters.q) {\n\t\t\t\t\tparams.q = this.filters.q\n\t\t\t\t}\n\t\t\t\tif (this.filters.status) {\n\t\t\t\t\tparams.status = this.filters.status\n\t\t\t\t}\n\t\t\t\tif (this.filters.botMentionName) {\n\t\t\t\t\tparams.botMentionName = this.filters.botMentionName\n\t\t\t\t}\n\t\t\t\tif (this.filters.fromDate) {\n\t\t\t\t\tparams.from = this.startOfDay(this.filters.fromDate)\n\t\t\t\t}\n\t\t\t\tif (this.filters.toDate) {\n\t\t\t\t\tparams.to = this.endOfDay(this.filters.toDate)\n\t\t\t\t}\n\t\t\t\tif (this.filters.onlyErrors) {\n\t\t\t\t\tparams.onlyErrors = '1'\n\t\t\t\t}\n\t\t\t\tif (this.filters.onlyWithTools) {\n\t\t\t\t\tparams.onlyWithTools = '1'\n\t\t\t\t}\n\n\t\t\t\tconst response = await axios.get(generateUrl('/apps/educai/api/v1/traces'), { params })\n\t\t\t\tthis.traces = response.data.traces || []\n\t\t\t\tthis.total = response.data.total || 0\n\t\t\t\tthis.limit = response.data.limit || this.limit\n\t\t\t\tthis.offset = response.data.offset || this.offset\n\t\t\t\tif (this.traces.length > 0 && !this.selectedTraceId) {\n\t\t\t\t\tawait this.selectTrace(this.traces[0].id)\n\t\t\t\t} else if (this.selectedTraceId && !this.traces.some((trace) => trace.id === this.selectedTraceId)) {\n\t\t\t\t\tthis.clearSelection()\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthis.error = error.response?.data?.error || 'Failed to load traces'\n\t\t\t\tshowError(this.error)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tapplyFilters() {\n\t\t\tthis.offset = 0\n\t\t\tthis.clearSelection()\n\t\t\tthis.loadTraces()\n\t\t},\n\n\t\tasync selectTrace(id) {\n\t\t\tthis.selectedTraceId = id\n\t\t\tthis.detailLoading = true\n\t\t\tthis.detailError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await axios.get(generateUrl(`/apps/educai/api/v1/traces/${id}`))\n\t\t\t\tthis.selectedTrace = response.data.trace\n\t\t\t\tthis.events = response.data.events || []\n\t\t\t} catch (error) {\n\t\t\t\tthis.detailError = error.response?.data?.error || 'Trace details unavailable'\n\t\t\t\tshowError(this.detailError)\n\t\t\t} finally {\n\t\t\t\tthis.detailLoading = false\n\t\t\t}\n\t\t},\n\n\t\tasync deleteSelectedTrace() {\n\t\t\tif (!this.selectedTrace || !confirm('Delete this activity trace?')) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait axios.delete(generateUrl(`/apps/educai/api/v1/traces/${this.selectedTrace.id}`))\n\t\t\t\tshowSuccess('Trace deleted')\n\t\t\t\tthis.clearSelection()\n\t\t\t\tawait this.loadTraces()\n\t\t\t} catch (error) {\n\t\t\t\tshowError(error.response?.data?.error || 'Deletion failed')\n\t\t\t}\n\t\t},\n\n\t\tasync clearActivity() {\n\t\t\tif (!confirm(`Clear all of your ${APP_DISPLAY_NAME} activity traces?`)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.clearing = true\n\t\t\ttry {\n\t\t\t\tawait axios.delete(generateUrl('/apps/educai/api/v1/traces'))\n\t\t\t\tshowSuccess('Activity cleared')\n\t\t\t\tthis.clearSelection()\n\t\t\t\tawait this.loadTraces()\n\t\t\t} catch (error) {\n\t\t\t\tshowError(error.response?.data?.error || 'Deletion failed')\n\t\t\t} finally {\n\t\t\t\tthis.clearing = false\n\t\t\t}\n\t\t},\n\n\t\texportSelectedTrace() {\n\t\t\tif (!this.selectedTrace) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst payload = {\n\t\t\t\ttrace: this.selectedTrace,\n\t\t\t\tevents: this.events,\n\t\t\t}\n\t\t\tconst blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' })\n\t\t\tconst url = URL.createObjectURL(blob)\n\t\t\tconst link = document.createElement('a')\n\t\t\tlink.href = url\n\t\t\tlink.download = `educai-trace-${this.selectedTrace.id}.json`\n\t\t\tdocument.body.appendChild(link)\n\t\t\tlink.click()\n\t\t\tdocument.body.removeChild(link)\n\t\t\tURL.revokeObjectURL(url)\n\t\t},\n\n\t\tnextPage() {\n\t\t\tthis.offset += this.limit\n\t\t\tthis.loadTraces()\n\t\t},\n\n\t\tpreviousPage() {\n\t\t\tthis.offset = Math.max(0, this.offset - this.limit)\n\t\t\tthis.loadTraces()\n\t\t},\n\n\t\tclearSelection() {\n\t\t\tthis.selectedTraceId = null\n\t\t\tthis.selectedTrace = null\n\t\t\tthis.events = []\n\t\t\tthis.detailError = ''\n\t\t},\n\n\t\tformatDate(timestamp) {\n\t\t\tif (!timestamp) {\n\t\t\t\treturn '-'\n\t\t\t}\n\t\t\treturn new Date(timestamp * 1000).toLocaleString()\n\t\t},\n\n\t\tformatDuration(durationMs) {\n\t\t\tif (durationMs === null || durationMs === undefined) {\n\t\t\t\treturn '-'\n\t\t\t}\n\t\t\tif (durationMs < 1000) {\n\t\t\t\treturn `${durationMs} ms`\n\t\t\t}\n\t\t\treturn `${(durationMs / 1000).toFixed(1)} s`\n\t\t},\n\n\t\tformatTokens(value) {\n\t\t\tif (value === null || value === undefined || value === '') {\n\t\t\t\treturn '-'\n\t\t\t}\n\t\t\tconst tokens = Number(value)\n\t\t\tif (!Number.isFinite(tokens)) {\n\t\t\t\treturn '-'\n\t\t\t}\n\t\t\treturn `${tokens.toLocaleString()} ${tokens === 1 ? 'token' : 'tokens'}`\n\t\t},\n\n\t\tformatStatus(status) {\n\t\t\tif (!status) {\n\t\t\t\treturn 'Unknown'\n\t\t\t}\n\t\t\treturn status.charAt(0).toUpperCase() + status.slice(1)\n\t\t},\n\n\t\tformatEventType(type) {\n\t\t\treturn (type || '').replaceAll('_', ' ')\n\t\t},\n\n\t\tgetRawLlmPayload(event) {\n\t\t\tif (!event || event.event_type !== 'llm_request' || !event.payload_json) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn event.payload_json.request_payload || null\n\t\t},\n\n\t\tformatJson(value) {\n\t\t\treturn JSON.stringify(value, null, 2)\n\t\t},\n\n\t\tstartOfDay(dateValue) {\n\t\t\tconst date = new Date(`${dateValue}T00:00:00`)\n\t\t\treturn Math.floor(date.getTime() / 1000)\n\t\t},\n\n\t\tendOfDay(dateValue) {\n\t\t\tconst date = new Date(`${dateValue}T23:59:59`)\n\t\t\treturn Math.floor(date.getTime() / 1000)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.activity-view {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.activity-title-row,\n.detail-header {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: space-between;\n\tgap: 16px;\n}\n\n.activity-title-row h3,\n.detail-header h4 {\n\tmargin: 0 0 4px;\n}\n\n.activity-title-row p,\n.detail-header p,\n.privacy-note {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.activity-actions,\n.detail-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n\n.privacy-note {\n\tpadding: 10px 12px;\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n}\n\n.filters {\n\tdisplay: grid;\n\tgrid-template-columns: minmax(180px, 1fr) minmax(140px, 180px) minmax(150px, 190px) minmax(130px, 150px) minmax(130px, 150px) auto auto auto;\n\tgap: 12px;\n\talign-items: end;\n}\n\n.filters label {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tfont-weight: 600;\n}\n\n.filters input,\n.filters select {\n\tmin-height: 36px;\n}\n\n.checkbox-label {\n\tflex-direction: row !important;\n\talign-items: center;\n\tmin-height: 36px;\n}\n\n.error-banner {\n\tpadding: 10px 12px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-error);\n\tcolor: var(--color-error-text);\n}\n\n.loading,\n.empty-state,\n.empty-detail,\n.loading-small {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tmin-height: 120px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.empty-state {\n\tflex-direction: column;\n}\n\n.activity-layout {\n\tdisplay: grid;\n\tgrid-template-columns: minmax(320px, 0.9fr) minmax(420px, 1.3fr);\n\tgap: 16px;\n\talign-items: start;\n}\n\n.trace-list,\n.trace-detail {\n\tmin-width: 0;\n}\n\n.trace-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\twidth: 100%;\n\tmin-height: 72px;\n\tmargin-bottom: 8px;\n\tpadding: 10px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\ttext-align: left;\n\tcursor: pointer;\n}\n\n.trace-row.selected,\n.trace-row:hover {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n}\n\n.trace-main {\n\tmin-width: 0;\n}\n\n.trace-meta,\n.trace-counts,\n.event-header {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.trace-preview {\n\tdisplay: block;\n\toverflow: hidden;\n\tmargin-top: 6px;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.trace-counts {\n\tjustify-content: flex-end;\n\tmin-width: 88px;\n}\n\n.status-badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tmin-height: 22px;\n\tpadding: 0 8px;\n\tborder-radius: 999px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\tfont-size: 12px;\n\tfont-weight: 700;\n}\n\n.status-badge.success,\n.status-badge.ok {\n\tbackground: var(--color-success);\n\tcolor: var(--color-primary-element-text);\n}\n\n.status-badge.error {\n\tbackground: var(--color-error);\n\tcolor: var(--color-error-text);\n}\n\n.status-badge.partial,\n.status-badge.running,\n.status-badge.started {\n\tbackground: var(--color-warning);\n\tcolor: var(--color-main-text);\n}\n\n.pagination {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tmargin-top: 12px;\n}\n\n.trace-detail {\n\tpadding: 16px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n}\n\n.run-summary {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(4, minmax(0, 1fr));\n\tgap: 8px;\n\tmargin: 16px 0;\n}\n\n.run-summary div {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 8px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n}\n\n.event-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.event-item {\n\tpadding: 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n}\n\n.event-type {\n\tcolor: var(--color-main-text);\n\tfont-weight: 700;\n\ttext-transform: capitalize;\n}\n\n.event-tool {\n\tfont-family: var(--font-face-monospace);\n}\n\n.event-time {\n\tmargin-left: auto;\n}\n\n.event-error {\n\tcolor: var(--color-error);\n\tfont-weight: 600;\n}\n\n.raw-llm-payload {\n\tmargin-top: 10px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n}\n\n.raw-llm-payload summary {\n\tcursor: pointer;\n\tfont-weight: 600;\n}\n\n.raw-llm-payload pre {\n\toverflow: auto;\n\tmax-height: 420px;\n\tmargin: 6px 0;\n\tpadding: 10px;\n\tborder: 1px dashed var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 12px;\n\tline-height: 1.35;\n\twhite-space: pre-wrap;\n\tword-break: break-word;\n}\n\n.event-block {\n\tmargin-top: 10px;\n}\n\n.event-block pre {\n\toverflow: auto;\n\tmax-height: 260px;\n\tmargin: 6px 0;\n\tpadding: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-dark);\n\twhite-space: pre-wrap;\n\tword-break: break-word;\n}\n\n.event-duration {\n\tmargin-top: 8px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n@media (max-width: 1000px) {\n\t.activity-layout,\n\t.filters {\n\t\tgrid-template-columns: 1fr;\n\t}\n\n\t.activity-title-row,\n\t.detail-header {\n\t\tflex-direction: column;\n\t}\n}\n\n@media (max-width: 700px) {\n\t.run-summary {\n\t\tgrid-template-columns: 1fr 1fr;\n\t}\n\n\t.trace-row {\n\t\talign-items: flex-start;\n\t\tflex-direction: column;\n\t}\n}\n</style>\n","<template>\n\t<div class=\"bot-card\" :class=\"{ inactive: !bot.is_active }\">\n\t\t<!-- Approval status badge (top right) -->\n\t\t<div v-if=\"approvalStatus !== 'approved'\" class=\"approval-badge\" :class=\"approvalStatus\">\n\t\t\t{{ approvalStatusLabel }}\n\t\t</div>\n\n\t\t<div class=\"bot-header\">\n\t\t\t<h3>{{ bot.bot_name }}</h3>\n\t\t\t<span class=\"mention-badge\">{{ bot.mention_name }}</span>\n\t\t</div>\n\n\t\t<div class=\"bot-prompt\">\n\t\t\t<p>{{ truncatedPrompt }}</p>\n\t\t</div>\n\n\t\t<div class=\"bot-visibility\">\n\t\t\t<span class=\"visibility-badge\" :class=\"visibilityClass\">{{ visibilityLabel }}</span>\n\t\t</div>\n\n\t\t<div class=\"bot-meta\">\n\t\t\t<span class=\"icon-calendar\" />\n\t\t\t<span>Created {{ formatDate(bot.created_at) }}</span>\n\t\t</div>\n\n\t\t<div v-if=\"!readonly\" class=\"bot-actions\">\n\t\t\t<button\n\t\t\t\tv-if=\"canEdit\"\n\t\t\t\tclass=\"button\"\n\t\t\t\t@click=\"$emit('edit', bot)\">\n\t\t\t\t<span class=\"icon-rename\" /> Edit\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\tv-else-if=\"editBlockedReason\"\n\t\t\t\tclass=\"button disabled\"\n\t\t\t\t:title=\"editBlockedReason\"\n\t\t\t\tdisabled>\n\t\t\t\t<span class=\"icon-lock\" /> View Only\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\tv-if=\"approvalStatus === 'draft'\"\n\t\t\t\tclass=\"button primary\"\n\t\t\t\t@click=\"$emit('submit', bot.id)\">\n\t\t\t\t<span class=\"icon-checkmark\" /> Submit for Approval\n\t\t\t</button>\n\t\t\t<button class=\"button error\" @click=\"$emit('delete', bot.id)\">\n\t\t\t\t<span class=\"icon-delete\" /> Delete\n\t\t\t</button>\n\t\t</div>\n\n\t\t<div v-if=\"!bot.is_active && approvalStatus === 'approved'\" class=\"bot-status\">\n\t\t\t<span class=\"icon-close\" /> Inactive\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nexport default {\n\tname: 'BotCard',\n\tprops: {\n\t\tbot: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\t\treadonly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t\tuserPermissions: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({\n\t\t\t\thasApprovalRights: false,\n\t\t\t}),\n\t\t},\n\t},\n\tcomputed: {\n\t\ttruncatedPrompt() {\n\t\t\tconst prompt = this.bot.system_prompt || ''\n\t\t\treturn prompt.length > 150\n\t\t\t\t? prompt.substring(0, 150) + '...'\n\t\t\t\t: prompt\n\t\t},\n\t\tapprovalStatus() {\n\t\t\treturn this.bot.approval_status || 'approved'\n\t\t},\n\t\tapprovalStatusLabel() {\n\t\t\tconst status = this.approvalStatus\n\t\t\tif (status === 'draft') {\n\t\t\t\treturn 'Draft'\n\t\t\t}\n\t\t\tif (status === 'pending') {\n\t\t\t\treturn 'Pending Approval'\n\t\t\t}\n\t\t\tif (status === 'personal') {\n\t\t\t\treturn 'Personal'\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\t\tvisibilityLabel() {\n\t\t\tconst v = this.bot.visibility ? this.bot.visibility : (this.bot.is_public ? 'global' : 'groups')\n\t\t\tif (v === 'global') {\n\t\t\t\treturn 'Global'\n\t\t\t}\n\t\t\tif (v === 'personal') {\n\t\t\t\treturn 'Personal'\n\t\t\t}\n\t\t\tif (v === 'teams') {\n\t\t\t\treturn 'Team access'\n\t\t\t}\n\t\t\treturn 'Group access'\n\t\t},\n\t\tvisibilityClass() {\n\t\t\tconst v = this.bot.visibility ? this.bot.visibility : (this.bot.is_public ? 'global' : 'groups')\n\t\t\tif (v === 'global') {\n\t\t\t\treturn 'global'\n\t\t\t}\n\t\t\tif (v === 'personal') {\n\t\t\t\treturn 'personal'\n\t\t\t}\n\t\t\tif (v === 'teams') {\n\t\t\t\treturn 'teams'\n\t\t\t}\n\t\t\treturn 'groups'\n\t\t},\n\t\tcanEdit() {\n\t\t\t// Readonly mode disables editing\n\t\t\tif (this.readonly) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst status = this.approvalStatus\n\t\t\t// Drafts and personal bots can always be edited by owner\n\t\t\tif (status === 'draft' || status === 'personal') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// Pending bots can be edited by owner (to make corrections)\n\t\t\tif (status === 'pending') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (status === 'approved') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\teditBlockedReason() {\n\t\t\treturn null\n\t\t},\n\t},\n\tmethods: {\n\t\tformatDate(timestamp) {\n\t\t\tconst date = new Date(timestamp * 1000)\n\t\t\tconst formatted = date.toLocaleDateString(undefined, {\n\t\t\t\tday: 'numeric',\n\t\t\t\tmonth: 'long',\n\t\t\t\tyear: 'numeric',\n\t\t\t})\n\t\t\treturn `on ${formatted}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.bot-card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 8px;\n\tpadding: 20px;\n\tposition: relative;\n\ttransition: box-shadow 0.2s;\n}\n\n.bot-card:hover {\n\tbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n}\n\n.bot-card.inactive {\n\topacity: 0.6;\n}\n\n.bot-header {\n\tmargin-bottom: 12px;\n}\n\n.bot-header h3 {\n\tmargin: 0 0 8px 0;\n\tfont-size: 18px;\n\tfont-weight: 600;\n}\n\n.mention-badge {\n\tdisplay: inline-block;\n\tbackground: var(--color-primary-element);\n\tcolor: white;\n\tpadding: 4px 12px;\n\tborder-radius: 12px;\n\tfont-size: 13px;\n\tfont-weight: 600;\n}\n\n.bot-prompt {\n\tmargin-bottom: 16px;\n\tmin-height: 60px;\n}\n.bot-visibility {\n\tmargin-bottom: 12px;\n}\n\n.visibility-badge {\n\tdisplay: inline-block;\n\tpadding: 3px 10px;\n\tborder-radius: 12px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n}\n\n.visibility-badge.global {\n\tbackground: var(--color-success);\n\tcolor: #000;\n}\n\n.visibility-badge.teams {\n\tbackground: var(--color-primary-element);\n\tcolor: #fff;\n}\n\n.visibility-badge.groups {\n\tbackground: var(--color-warning);\n\tcolor: #000;\n}\n\n.visibility-badge.personal {\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-dark);\n}\n\n/* Approval status badge (top right corner) */\n.approval-badge {\n\tposition: absolute;\n\ttop: 10px;\n\tright: 10px;\n\tpadding: 4px 10px;\n\tborder-radius: 12px;\n\tfont-size: 11px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n}\n\n.approval-badge.draft {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-lighter);\n\tborder: 1px solid var(--color-border);\n}\n\n.approval-badge.pending {\n\tbackground: var(--color-warning);\n\tcolor: #000;\n}\n\n.approval-badge.personal {\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-dark);\n}\n\n.button.primary {\n\tbackground-color: var(--color-primary);\n\tcolor: white;\n\tborder-color: var(--color-primary);\n}\n\n.button.primary:hover {\n\tbackground-color: var(--color-primary-element-light);\n}\n\n.button.disabled {\n\topacity: 0.6;\n\tcursor: not-allowed;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-lighter);\n}\n\n.bot-prompt p {\n\tmargin: 0;\n\tcolor: var(--color-text-lighter);\n\tfont-size: 14px;\n\tline-height: 1.5;\n}\n\n.bot-meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tcolor: var(--color-text-lighter);\n\tfont-size: 13px;\n\tmargin-bottom: 16px;\n}\n\n.bot-actions {\n\tdisplay: flex;\n\tgap: 10px;\n}\n\n.button {\n\tflex: 1;\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n\tborder-radius: 3px;\n\tcursor: pointer;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 6px;\n\tfont-size: 14px;\n\ttransition: all 0.2s;\n}\n\n.button:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.button.error {\n\tcolor: var(--color-error);\n}\n\n.button.error:hover {\n\tbackground: var(--color-error);\n\tcolor: white;\n\tborder-color: var(--color-error);\n}\n\n.bot-status {\n\tposition: absolute;\n\ttop: 10px;\n\tright: 10px;\n\tbackground: var(--color-error);\n\tcolor: white;\n\tpadding: 4px 10px;\n\tborder-radius: 12px;\n\tfont-size: 12px;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n}\n</style>\n","<template>\n\t<div class=\"bot-management\">\n\t\t<div class=\"header\">\n\t\t\t<h2>{{ APP_DISPLAY_NAME }} - Multi-Bot Manager</h2>\n\t\t\t<button class=\"button primary\" @click=\"showCreateForm = true\">\n\t\t\t\t<span class=\"icon-add\" />\n\t\t\t\tCreate New Bot\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- My Bots Section -->\n\t\t<div v-if=\"bots.length > 0\" class=\"bot-grid\">\n\t\t\t<BotCard\n\t\t\t\tv-for=\"bot in bots\"\n\t\t\t\t:key=\"bot.id\"\n\t\t\t\t:bot=\"bot\"\n\t\t\t\t:user-permissions=\"userPermissions\"\n\t\t\t\t@edit=\"editBot\"\n\t\t\t\t@delete=\"deleteBot\"\n\t\t\t\t@submit=\"submitForApproval\" />\n\t\t</div>\n\n\t\t<div v-if=\"bots.length === 0 && !loading\" class=\"empty-state\">\n\t\t\t<span class=\"icon-comment\" />\n\t\t\t<h3>No bots created yet</h3>\n\t\t\t<p>Create your first AI bot to get started with Nextcloud Talk integration</p>\n\t\t\t<button class=\"button primary\" @click=\"showCreateForm = true\">\n\t\t\t\tCreate Your First Bot\n\t\t\t</button>\n\t\t</div>\n\n\t\t<div v-if=\"loading\" class=\"loading\">\n\t\t\t<span class=\"icon-loading\" />\n\t\t\t<p>Loading bots...</p>\n\t\t</div>\n\n\t\t<!-- Approval Queue Section (only visible to users with approval rights) -->\n\t\t<div v-if=\"userPermissions.hasApprovalRights\" class=\"approval-section\">\n\t\t\t<div class=\"section-header\">\n\t\t\t\t<h3>Pending Approvals</h3>\n\t\t\t\t<span v-if=\"pendingApprovals.length > 0\" class=\"badge\">{{ pendingApprovals.length }}</span>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"loadingApprovals\" class=\"loading-small\">\n\t\t\t\t<span class=\"icon-loading\" />\n\t\t\t\t<span>Loading pending approvals...</span>\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"pendingApprovals.length === 0\" class=\"empty-approvals\">\n\t\t\t\t<p>No bots are pending approval.</p>\n\t\t\t</div>\n\n\t\t\t<div v-else class=\"approval-grid\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"bot in pendingApprovals\"\n\t\t\t\t\t:key=\"'pending-' + bot.id\"\n\t\t\t\t\tclass=\"approval-card\">\n\t\t\t\t\t<div class=\"approval-info\">\n\t\t\t\t\t\t<h4>{{ getReviewTarget(bot).bot_name }}</h4>\n\t\t\t\t\t\t<span class=\"mention-badge\">{{ bot.mention_name }}</span>\n\t\t\t\t\t\t<p class=\"owner\">\n\t\t\t\t\t\t\tSubmitted by {{ bot.owner_name }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p class=\"visibility\">\n\t\t\t\t\t\t\t<span class=\"visibility-badge\" :class=\"getReviewTarget(bot).visibility || 'groups'\">\n\t\t\t\t\t\t\t\t{{ formatVisibility(getReviewTarget(bot).visibility) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span v-if=\"getReviewTarget(bot).is_update\" class=\"update-badge\">\n\t\t\t\t\t\t\t\tUpdate to existing bot\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span v-else class=\"new-badge\">\n\t\t\t\t\t\t\t\tNew bot\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-if=\"getReviewTarget(bot).is_update\" class=\"review-note\">\n\t\t\t\t\t\t\tReviewing the submitted pending version. The currently approved version stays live until approval.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<div class=\"questionnaire\">\n\t\t\t\t\t\t\t<p v-if=\"bot.approval_reason\">\n\t\t\t\t\t\t\t\t<strong>Why share:</strong>\n\t\t\t\t\t\t\t\t{{ bot.approval_reason }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<p v-if=\"bot.bot_capabilities\">\n\t\t\t\t\t\t\t\t<strong>What it does well:</strong>\n\t\t\t\t\t\t\t\t{{ bot.bot_capabilities }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<p v-if=\"bot.rag_source_description\">\n\t\t\t\t\t\t\t\t<strong>RAG sources:</strong>\n\t\t\t\t\t\t\t\t{{ bot.rag_source_description }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<p v-if=\"bot.testing_description\">\n\t\t\t\t\t\t\t\t<strong>Testing done:</strong>\n\t\t\t\t\t\t\t\t{{ bot.testing_description }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<p v-if=\"bot.submitted_at\" class=\"submitted-time\">\n\t\t\t\t\t\t\tSubmitted {{ formatDate(bot.submitted_at) }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"approval-actions\">\n\t\t\t\t\t\t<button class=\"button\" @click=\"enableTest(bot)\">\n\t\t\t\t\t\t\t<span class=\"icon-play\" /> Test Bot\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button class=\"button\" @click=\"previewBot(bot)\">\n\t\t\t\t\t\t\t<span class=\"icon-details\" /> Preview\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button class=\"button primary\" @click=\"approveBot(bot.id)\">\n\t\t\t\t\t\t\t<span class=\"icon-checkmark\" /> Approve\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button class=\"button error\" @click=\"rejectBot(bot.id)\">\n\t\t\t\t\t\t\t<span class=\"icon-close\" /> Reject\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<BotForm\n\t\t\tv-if=\"showCreateForm || editingBot\"\n\t\t\t:bot=\"editingBot\"\n\t\t\t:user-permissions=\"userPermissions\"\n\t\t\t@save=\"saveBot\"\n\t\t\t@cancel=\"closeForm\" />\n\n\t\t<!-- Approval submission modal -->\n\t\t<div v-if=\"showSubmitModal\" class=\"modal-mask\" @click.self=\"closeSubmitModal\">\n\t\t\t<div class=\"modal-container\">\n\t\t\t\t<div class=\"modal-header\">\n\t\t\t\t\t<h2>Submit for Approval</h2>\n\t\t\t\t\t<button class=\"close-button\" @click=\"closeSubmitModal\">\n\t\t\t\t\t\t<span class=\"icon-close\" />\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"modal-body\">\n\t\t\t\t\t<p class=\"hint\">\n\t\t\t\t\t\tYou can test the bot yourself in Nextcloud Talk before submitting. Mention <strong>{{ submitForm.mentionName }}</strong> in any conversation.\n\t\t\t\t\t</p>\n\t\t\t\t\t<div class=\"form-group\">\n\t\t\t\t\t\t<label>Why do you want to share your bot with this specific group (or global)?</label>\n\t\t\t\t\t\t<textarea v-model=\"submitForm.approvalReason\" rows=\"3\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"form-group\">\n\t\t\t\t\t\t<label>What is your bot good at?</label>\n\t\t\t\t\t\t<textarea v-model=\"submitForm.botCapabilities\" rows=\"3\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"form-group\">\n\t\t\t\t\t\t<label>What is/are the source(s) of the information you fed the RAG?</label>\n\t\t\t\t\t\t<textarea v-model=\"submitForm.ragSourceDescription\" rows=\"3\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"form-group\">\n\t\t\t\t\t\t<label>Describe fully how you tested your bot so we can reproduce its behaviour.</label>\n\t\t\t\t\t\t<textarea v-model=\"submitForm.testingDescription\" rows=\"3\" />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"modal-footer\">\n\t\t\t\t\t<button class=\"button\" @click=\"closeSubmitModal\">\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t<button class=\"button primary\" @click=\"confirmSubmitForApproval\">\n\t\t\t\t\t\tSubmit\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div v-if=\"showReviewPreview && reviewPreviewBot\" class=\"modal-mask\" @click.self=\"closeReviewPreview\">\n\t\t\t<div class=\"modal-container review-preview-modal\">\n\t\t\t\t<div class=\"modal-header\">\n\t\t\t\t\t<h2>{{ getReviewTarget(reviewPreviewBot).bot_name }}</h2>\n\t\t\t\t\t<button class=\"close-button\" @click=\"closeReviewPreview\">\n\t\t\t\t\t\t<span class=\"icon-close\" />\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"modal-body\">\n\t\t\t\t\t<p class=\"hint\">\n\t\t\t\t\t\tReview target for <strong>{{ reviewPreviewBot.mention_name }}</strong>\n\t\t\t\t\t\t<span v-if=\"getReviewTarget(reviewPreviewBot).is_update\">\n\t\t\t\t\t\t\t. This is the pending version that owner and enabled reviewer can test in Talk.\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</p>\n\t\t\t\t\t<div class=\"form-group\">\n\t\t\t\t\t\t<label>Description</label>\n\t\t\t\t\t\t<p class=\"preview-block\">\n\t\t\t\t\t\t\t{{ getReviewTarget(reviewPreviewBot).description || 'No description provided' }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"form-group\">\n\t\t\t\t\t\t<label>Visibility</label>\n\t\t\t\t\t\t<p class=\"preview-block\">\n\t\t\t\t\t\t\t{{ formatVisibility(getReviewTarget(reviewPreviewBot).visibility) }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"form-group\">\n\t\t\t\t\t\t<label>Temperature</label>\n\t\t\t\t\t\t<p class=\"preview-block\">\n\t\t\t\t\t\t\t{{ getReviewTarget(reviewPreviewBot).temperature === null || getReviewTarget(reviewPreviewBot).temperature === undefined\n\t\t\t\t\t\t\t\t? 'Uses global default'\n\t\t\t\t\t\t\t\t: formatTemperature(getReviewTarget(reviewPreviewBot).temperature) }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-if=\"getReviewTarget(reviewPreviewBot).allowed_groups?.length || getReviewTarget(reviewPreviewBot).allowed_teams?.length\"\n\t\t\t\t\t\tclass=\"form-group\">\n\t\t\t\t\t\t<label>Target Audience</label>\n\t\t\t\t\t\t<p v-if=\"getReviewTarget(reviewPreviewBot).allowed_groups?.length\" class=\"preview-block\">\n\t\t\t\t\t\t\tGroups: {{ getReviewTarget(reviewPreviewBot).allowed_groups.join(', ') }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-if=\"getReviewTarget(reviewPreviewBot).allowed_teams?.length\" class=\"preview-block\">\n\t\t\t\t\t\t\tTeams: {{ reviewTeamNames(getReviewTarget(reviewPreviewBot)).join(', ') }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div v-if=\"getReviewTarget(reviewPreviewBot).rag_enabled\" class=\"form-group\">\n\t\t\t\t\t\t<label>RAG Sources</label>\n\t\t\t\t\t\t<p v-if=\"reviewSourcesLoading\" class=\"preview-block\">\n\t\t\t\t\t\t\tLoading sources...\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-else-if=\"reviewSourcesError\" class=\"preview-block error-text\">\n\t\t\t\t\t\t\t{{ reviewSourcesError }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-else-if=\"reviewSources.length === 0\" class=\"preview-block\">\n\t\t\t\t\t\t\tNo knowledge sources attached\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<ul v-else class=\"preview-source-list\">\n\t\t\t\t\t\t\t<li v-for=\"source in reviewSources\" :key=\"`${reviewPreviewBot.id}-source-${source.id}`\">\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t<strong>{{ describeReviewSource(source) }}</strong>\n\t\t\t\t\t\t\t\t\t<span v-if=\"source.status\"> - {{ formatSourceStatus(source.status) }}</span>\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\tv-if=\"sourceOpenUrl(source)\"\n\t\t\t\t\t\t\t\t\tclass=\"source-open-link\"\n\t\t\t\t\t\t\t\t\t:href=\"sourceOpenUrl(source)\"\n\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t\t\t\tOpen\n\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"form-group\">\n\t\t\t\t\t\t<label>System Prompt</label>\n\t\t\t\t\t\t<pre class=\"preview-code\">{{ getReviewTarget(reviewPreviewBot).system_prompt }}</pre>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"form-group\">\n\t\t\t\t\t\t<label>Enabled Tools</label>\n\t\t\t\t\t\t<ul v-if=\"getReviewTarget(reviewPreviewBot).tools && getReviewTarget(reviewPreviewBot).tools.length > 0\" class=\"preview-tool-list\">\n\t\t\t\t\t\t\t<li v-for=\"(tool, index) in getReviewTarget(reviewPreviewBot).tools\" :key=\"`${reviewPreviewBot.id}-tool-${index}`\">\n\t\t\t\t\t\t\t\t<strong>{{ tool.name }}</strong>\n\t\t\t\t\t\t\t\t<span v-if=\"tool.description\"> - {{ tool.description }}</span>\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t<p v-else class=\"preview-block\">\n\t\t\t\t\t\t\tNo tools configured\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"modal-footer\">\n\t\t\t\t\t<button class=\"button\" @click=\"closeReviewPreview\">\n\t\t\t\t\t\tClose\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { APP_DISPLAY_NAME } from '../branding.js'\nimport { generateUrl } from '@nextcloud/router'\nimport { showSuccess, showError } from '@nextcloud/dialogs'\nimport BotCard from '../components/BotCard.vue'\nimport BotForm from '../components/BotForm.vue'\n\nexport default {\n\tname: 'PersonalBots',\n\tcomponents: { BotCard, BotForm },\n\tdata() {\n\t\treturn {\n\t\t\tAPP_DISPLAY_NAME,\n\t\t\tbots: [],\n\t\t\tshowCreateForm: false,\n\t\t\teditingBot: null,\n\t\t\tloading: false,\n\t\t\tuserPermissions: {\n\t\t\t\tisAdmin: false,\n\t\t\t\tisGroupAdmin: false,\n\t\t\t\tisTeamAdmin: false,\n\t\t\t\thasApprovalRights: false,\n\t\t\t\tadminGroups: [],\n\t\t\t\tadminTeams: [],\n\t\t\t},\n\t\t\tpendingApprovals: [],\n\t\t\tloadingApprovals: false,\n\t\t\tshowSubmitModal: false,\n\t\t\tshowReviewPreview: false,\n\t\t\treviewPreviewBot: null,\n\t\t\treviewSources: [],\n\t\t\treviewSourcesLoading: false,\n\t\t\treviewSourcesError: '',\n\t\t\tsubmitForm: {\n\t\t\t\tbotId: null,\n\t\t\t\tmentionName: '',\n\t\t\t\tapprovalReason: '',\n\t\t\t\tbotCapabilities: '',\n\t\t\t\tragSourceDescription: '',\n\t\t\t\ttestingDescription: '',\n\t\t\t},\n\t\t}\n\t},\n\tmounted() {\n\t\tthis.loadPermissions()\n\t\tthis.loadBots()\n\t},\n\tmethods: {\n\t\tasync loadPermissions() {\n\t\t\ttry {\n\t\t\t\tconst response = await axios.get(generateUrl('/apps/educai/api/v1/permissions'))\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.log('[TalkAI] Permissions response:', response.data)\n\t\t\t\tthis.userPermissions = response.data.permissions || this.userPermissions\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.log('[TalkAI] userPermissions set to:', this.userPermissions)\n\t\t\t\t// Load pending approvals if user has approval rights\n\t\t\t\tif (this.userPermissions.hasApprovalRights) {\n\t\t\t\t\tthis.loadPendingApprovals()\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to load permissions:', error)\n\t\t\t}\n\t\t},\n\t\tasync loadPendingApprovals() {\n\t\t\tif (!this.userPermissions.hasApprovalRights) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loadingApprovals = true\n\t\t\ttry {\n\t\t\t\tconst response = await axios.get(generateUrl('/apps/educai/api/v1/approvals'))\n\t\t\t\tthis.pendingApprovals = response.data.bots || []\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to load pending approvals:', error)\n\t\t\t} finally {\n\t\t\t\tthis.loadingApprovals = false\n\t\t\t}\n\t\t},\n\t\tasync loadBots() {\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst response = await axios.get(generateUrl('/apps/educai/api/v1/bots'))\n\t\t\t\tthis.bots = response.data\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to load bots:', error)\n\t\t\t\tshowError('Failed to load bots')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t\tasync saveBot(botData) {\n\t\t\ttry {\n\t\t\t\tif (botData.id) {\n\t\t\t\t\tconst response = await axios.put(\n\t\t\t\t\t\tgenerateUrl(`/apps/educai/api/v1/bots/${botData.id}`),\n\t\t\t\t\t\tbotData,\n\t\t\t\t\t)\n\t\t\t\t\tconst updatedBot = response.data\n\t\t\t\t\tconst status = updatedBot.approval_status || 'approved'\n\t\t\t\t\tif (status === 'draft') {\n\t\t\t\t\t\tshowSuccess('Bot updated and saved as draft. Submit for approval when ready.')\n\t\t\t\t\t} else if (status === 'pending') {\n\t\t\t\t\t\tshowSuccess('Bot updated. Approval is required before the new shared version goes live.')\n\t\t\t\t\t} else if (status === 'personal') {\n\t\t\t\t\t\tshowSuccess('Personal bot updated successfully')\n\t\t\t\t\t} else {\n\t\t\t\t\t\tshowSuccess('Bot updated successfully')\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst response = await axios.post(\n\t\t\t\t\t\tgenerateUrl('/apps/educai/api/v1/bots'),\n\t\t\t\t\t\tbotData,\n\t\t\t\t\t)\n\t\t\t\t\tconst newBot = response.data\n\t\t\t\t\tconst status = newBot.approval_status || 'approved'\n\t\t\t\t\tif (status === 'draft') {\n\t\t\t\t\t\tshowSuccess('Bot saved as draft. Submit for approval when ready.')\n\t\t\t\t\t} else if (status === 'personal') {\n\t\t\t\t\t\tshowSuccess('Personal bot created successfully')\n\t\t\t\t\t} else {\n\t\t\t\t\t\tshowSuccess('Bot created successfully')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tawait this.loadBots()\n\t\t\t\tthis.closeForm()\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to save bot:', error)\n\t\t\t\tshowError(error.response?.data?.error || 'Failed to save bot')\n\t\t\t}\n\t\t},\n\t\tsubmitForApproval(botId) {\n\t\t\tconst bot = this.bots.find(b => b.id === botId)\n\t\t\tif (!bot) {\n\t\t\t\tshowError('Bot not found')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.submitForm.botId = botId\n\t\t\tthis.submitForm.mentionName = bot.mention_name\n\t\t\tthis.submitForm.approvalReason = ''\n\t\t\tthis.submitForm.botCapabilities = ''\n\t\t\tthis.submitForm.ragSourceDescription = ''\n\t\t\tthis.submitForm.testingDescription = ''\n\t\t\tthis.showSubmitModal = true\n\t\t},\n\t\tasync confirmSubmitForApproval() {\n\t\t\tif (!this.submitForm.botId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`/apps/educai/api/v1/bots/${this.submitForm.botId}/submit`), {\n\t\t\t\t\tapproval_reason: this.submitForm.approvalReason,\n\t\t\t\t\tbot_capabilities: this.submitForm.botCapabilities,\n\t\t\t\t\trag_source_description: this.submitForm.ragSourceDescription,\n\t\t\t\t\ttesting_description: this.submitForm.testingDescription,\n\t\t\t\t})\n\t\t\t\tshowSuccess('Bot submitted for approval')\n\t\t\t\tawait this.loadBots()\n\t\t\t\tthis.closeSubmitModal()\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to submit bot for approval:', error)\n\t\t\t\tshowError(error.response?.data?.error || 'Failed to submit for approval')\n\t\t\t}\n\t\t},\n\t\tcloseSubmitModal() {\n\t\t\tthis.showSubmitModal = false\n\t\t\tthis.submitForm.botId = null\n\t\t},\n\t\tasync approveBot(botId) {\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`/apps/educai/api/v1/bots/${botId}/approve`))\n\t\t\t\tshowSuccess('Bot approved successfully')\n\t\t\t\tawait this.loadPendingApprovals()\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to approve bot:', error)\n\t\t\t\tshowError(error.response?.data?.error || 'Failed to approve bot')\n\t\t\t}\n\t\t},\n\t\tasync rejectBot(botId) {\n\t\t\tconst reason = prompt('Optional: Enter a reason for rejection')\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`/apps/educai/api/v1/bots/${botId}/reject`), { reason })\n\t\t\t\tshowSuccess('Bot rejected and returned to draft')\n\t\t\t\tawait this.loadPendingApprovals()\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to reject bot:', error)\n\t\t\t\tshowError(error.response?.data?.error || 'Failed to reject bot')\n\t\t\t}\n\t\t},\n\t\tgetReviewTarget(bot) {\n\t\t\treturn bot.review_target || {\n\t\t\t\tbot_name: bot.bot_name,\n\t\t\t\tdescription: bot.description,\n\t\t\t\tsystem_prompt: bot.system_prompt,\n\t\t\t\ttemperature: bot.temperature,\n\t\t\t\tvisibility: bot.visibility || (bot.is_public ? 'global' : 'groups'),\n\t\t\t\tallowed_groups: bot.allowed_groups || [],\n\t\t\t\tallowed_teams: bot.allowed_teams || [],\n\t\t\t\tallowed_team_names: bot.allowed_team_names || [],\n\t\t\t\ttools: [],\n\t\t\t\tis_update: !!bot.has_pending_changes,\n\t\t\t}\n\t\t},\n\t\tpreviewBot(bot) {\n\t\t\tthis.reviewPreviewBot = bot\n\t\t\tthis.showReviewPreview = true\n\t\t\tthis.reviewSources = []\n\t\t\tthis.reviewSourcesError = ''\n\t\t\tif (this.getReviewTarget(bot).rag_enabled) {\n\t\t\t\tthis.loadReviewSources(bot)\n\t\t\t}\n\t\t},\n\t\tcloseReviewPreview() {\n\t\t\tthis.showReviewPreview = false\n\t\t\tthis.reviewPreviewBot = null\n\t\t\tthis.reviewSources = []\n\t\t\tthis.reviewSourcesError = ''\n\t\t\tthis.reviewSourcesLoading = false\n\t\t},\n\t\tasync loadReviewSources(bot) {\n\t\t\tthis.reviewSourcesLoading = true\n\t\t\tthis.reviewSourcesError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await axios.get(generateUrl(`/apps/educai/api/v1/bots/${bot.id}/sources`))\n\t\t\t\tconst sources = response.data?.sources\n\t\t\t\tthis.reviewSources = Array.isArray(sources) ? sources : []\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to load RAG sources for review:', error)\n\t\t\t\tthis.reviewSourcesError = error.response?.data?.error || 'Failed to load RAG sources'\n\t\t\t} finally {\n\t\t\t\tthis.reviewSourcesLoading = false\n\t\t\t}\n\t\t},\n\t\treviewTeamNames(target) {\n\t\t\tif (Array.isArray(target.allowed_team_names) && target.allowed_team_names.length > 0) {\n\t\t\t\treturn target.allowed_team_names\n\t\t\t}\n\t\t\treturn Array.isArray(target.allowed_teams) ? target.allowed_teams : []\n\t\t},\n\t\tdescribeReviewSource(source) {\n\t\t\tif (source.display_name) {\n\t\t\t\treturn source.display_name\n\t\t\t}\n\t\t\tif (source.path) {\n\t\t\t\treturn source.path\n\t\t\t}\n\t\t\tif (source.source_url) {\n\t\t\t\treturn source.source_url\n\t\t\t}\n\t\t\treturn `Source #${source.id}`\n\t\t},\n\t\tsourceOpenUrl(source) {\n\t\t\tif (!source) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (source.node_type === 'url' && source.source_url) {\n\t\t\t\treturn source.source_url\n\t\t\t}\n\t\t\tif (source.node_id && Number(source.node_id) > 0) {\n\t\t\t\treturn generateUrl(`/f/${source.node_id}`)\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\t\tformatSourceStatus(status) {\n\t\t\tif (status === 'ready') {\n\t\t\t\treturn 'Ready'\n\t\t\t}\n\t\t\tif (status === 'error') {\n\t\t\t\treturn 'Error'\n\t\t\t}\n\t\t\tif (status === 'pending') {\n\t\t\t\treturn 'Pending'\n\t\t\t}\n\t\t\treturn status || 'Unknown'\n\t\t},\n\t\tasync enableTest(bot) {\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`/apps/educai/api/v1/bots/${bot.id}/enable-test`))\n\t\t\t\tshowSuccess(`Testing enabled. Mention ${bot.mention_name} in Nextcloud Talk to try the pending version.`)\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to enable testing:', error)\n\t\t\t\tshowError(error.response?.data?.error || 'Failed to enable testing')\n\t\t\t}\n\t\t},\n\t\tasync deleteBot(botId) {\n\t\t\tif (!confirm('Delete this bot? All conversation history will be lost.')) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait axios.delete(generateUrl(`/apps/educai/api/v1/bots/${botId}`))\n\t\t\t\tshowSuccess('Bot deleted successfully')\n\t\t\t\tawait this.loadBots()\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to delete bot:', error)\n\t\t\t\tshowError(error.response?.data?.error || 'Failed to delete bot')\n\t\t\t}\n\t\t},\n\t\teditBot(bot) {\n\t\t\tthis.editingBot = bot\n\t\t},\n\t\tcloseForm() {\n\t\t\tthis.showCreateForm = false\n\t\t\tthis.editingBot = null\n\t\t},\n\t\tformatVisibility(visibility) {\n\t\t\tif (visibility === 'global') {\n\t\t\t\treturn 'Global'\n\t\t\t}\n\t\t\tif (visibility === 'personal') {\n\t\t\t\treturn 'Personal'\n\t\t\t}\n\t\t\tif (visibility === 'teams') {\n\t\t\t\treturn 'Team access'\n\t\t\t}\n\t\t\treturn 'Group access'\n\t\t},\n\t\tformatDate(timestamp) {\n\t\t\tif (!timestamp) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst date = new Date(timestamp * 1000)\n\t\t\treturn date.toLocaleDateString(undefined, {\n\t\t\t\tday: 'numeric',\n\t\t\t\tmonth: 'short',\n\t\t\t\tyear: 'numeric',\n\t\t\t\thour: '2-digit',\n\t\t\t\tminute: '2-digit',\n\t\t\t})\n\t\t},\n\t\tformatTemperature(value) {\n\t\t\tconst numeric = Number(value)\n\t\t\tif (!Number.isFinite(numeric)) {\n\t\t\t\treturn 'Uses global default'\n\t\t\t}\n\t\t\treturn numeric.toFixed(2)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.bot-management { padding: 20px; max-width: 1200px; margin: 0 auto; }\n.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }\n.header h2 { margin: 0; font-size: 24px; font-weight: 600; }\n.bot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; margin-bottom: 40px; }\n.empty-state { text-align: center; padding: 60px 20px; }\n.loading { text-align: center; padding: 40px; }\n.button { padding: 8px 16px; border: 1px solid var(--color-border); background: var(--color-main-background); border-radius: 3px; cursor: pointer; font-size: 14px; display: inline-flex; align-items: center; gap: 6px; }\n.button:hover { background: var(--color-background-hover); }\n.button.primary { background-color: var(--color-primary); color: white; border: none; padding: 10px 20px; border-radius: 3px; cursor: pointer; font-size: 14px; }\n.button.primary:hover { background-color: var(--color-primary-element-light); }\n.button.error { color: var(--color-error); }\n.button.error:hover { background: var(--color-error); color: white; border-color: var(--color-error); }\n\n/* Approval Section */\n.approval-section {\n\tmargin-top: 40px;\n\tpadding-top: 30px;\n\tborder-top: 2px solid var(--color-border);\n}\n\n.section-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tmargin-bottom: 20px;\n}\n\n.section-header h3 {\n\tmargin: 0;\n\tfont-size: 20px;\n\tfont-weight: 600;\n}\n\n.section-header .badge {\n\tbackground: var(--color-warning);\n\tcolor: #000;\n\tpadding: 2px 10px;\n\tborder-radius: 12px;\n\tfont-size: 13px;\n\tfont-weight: 600;\n}\n\n.loading-small {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tcolor: var(--color-text-lighter);\n\tpadding: 20px 0;\n}\n\n.empty-approvals {\n\tcolor: var(--color-text-lighter);\n\tpadding: 20px 0;\n}\n\n.approval-grid {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.approval-card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 8px;\n\tpadding: 16px 20px;\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 20px;\n}\n\n.approval-info h4 {\n\tmargin: 0 0 6px 0;\n\tfont-size: 16px;\n\tfont-weight: 600;\n}\n\n.approval-info .mention-badge {\n\tdisplay: inline-block;\n\tbackground: var(--color-primary-element);\n\tcolor: white;\n\tpadding: 2px 10px;\n\tborder-radius: 10px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n}\n\n.approval-info .owner {\n\tmargin: 8px 0 4px 0;\n\tfont-size: 13px;\n\tcolor: var(--color-text-lighter);\n}\n\n.approval-info .visibility {\n\tmargin: 4px 0;\n}\n\n.visibility-badge {\n\tdisplay: inline-block;\n\tpadding: 2px 8px;\n\tborder-radius: 10px;\n\tfont-size: 11px;\n\tfont-weight: 600;\n}\n\n.visibility-badge.global {\n\tbackground: var(--color-success);\n\tcolor: #000;\n}\n\n.visibility-badge.teams {\n\tbackground: var(--color-primary-element);\n\tcolor: #fff;\n}\n\n.visibility-badge.groups {\n\tbackground: var(--color-warning);\n\tcolor: #000;\n}\n\n.visibility-badge.personal {\n\tbackground: var(--color-primary-element-light);\n\tcolor: var(--color-primary-element-dark);\n}\n\n.update-badge {\n\tdisplay: inline-block;\n\tpadding: 2px 8px;\n\tborder-radius: 10px;\n\tfont-size: 11px;\n\tfont-weight: 600;\n\tbackground: var(--color-primary-element);\n\tcolor: #fff;\n\tmargin-left: 6px;\n}\n\n.new-badge {\n\tdisplay: inline-block;\n\tpadding: 2px 8px;\n\tborder-radius: 10px;\n\tfont-size: 11px;\n\tfont-weight: 600;\n\tbackground: var(--color-success);\n\tcolor: #000;\n\tmargin-left: 6px;\n}\n\n.approval-info .submitted-time {\n\tmargin: 4px 0 0 0;\n\tfont-size: 12px;\n\tcolor: var(--color-text-lighter);\n}\n\n.review-note {\n\tmargin: 8px 0;\n\tfont-size: 13px;\n\tcolor: var(--color-text-lighter);\n}\n\n.approval-actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tflex-shrink: 0;\n}\n\n/* Submit modal */\n.modal-mask {\n\tposition: fixed;\n\tz-index: 9998;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: rgba(0, 0, 0, 0.5);\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.modal-container {\n\tbackground: var(--color-main-background);\n\tborder-radius: 8px;\n\tbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);\n\tmax-width: 640px;\n\twidth: 90%;\n\tmax-height: 90vh;\n\toverflow-y: auto;\n}\n\n.modal-header,\n.modal-footer {\n\tpadding: 16px 20px;\n\tborder-bottom: 1px solid var(--color-border);\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n}\n\n.modal-footer {\n\tborder-top: 1px solid var(--color-border);\n\tborder-bottom: none;\n\tjustify-content: flex-end;\n\tgap: 10px;\n}\n\n.modal-body {\n\tpadding: 20px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.modal-header h2 {\n\tmargin: 0;\n\tfont-size: 18px;\n}\n\n.close-button {\n\tbackground: none;\n\tborder: none;\n\tcursor: pointer;\n\tpadding: 8px;\n\tfont-size: 20px;\n\topacity: 0.7;\n}\n\n.close-button:hover {\n\topacity: 1;\n}\n\n.modal-body .form-group label {\n\tfont-weight: 600;\n}\n\n.modal-body textarea {\n\twidth: 100%;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 4px;\n\tpadding: 8px;\n\tfont-family: inherit;\n}\n\n.questionnaire p {\n\tmargin: 4px 0;\n\tfont-size: 13px;\n\tcolor: var(--color-text-lighter);\n}\n\n.review-preview-modal {\n\tmax-width: 760px;\n}\n\n.preview-block {\n\tmargin: 0;\n\tpadding: 10px 12px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 6px;\n\twhite-space: pre-wrap;\n}\n\n.preview-code {\n\tmargin: 0;\n\tpadding: 12px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 6px;\n\twhite-space: pre-wrap;\n\tfont-family: var(--font-face-monospace);\n\tfont-size: 13px;\n}\n\n.preview-tool-list {\n\tmargin: 0;\n\tpadding-left: 18px;\n}\n\n.preview-tool-list li {\n\tmargin: 6px 0;\n}\n\n.preview-source-list {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.preview-source-list li {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tpadding: 10px 12px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 6px;\n}\n\n.source-open-link {\n\tflex-shrink: 0;\n}\n\n.error-text {\n\tcolor: var(--color-error);\n}\n</style>\n","<template>\n\t<div class=\"educai-personal-settings\">\n\t\t<header class=\"personal-header\">\n\t\t\t<div>\n\t\t\t\t<h2>{{ APP_DISPLAY_NAME }}</h2>\n\t\t\t\t<p>Manage your bots and review your own AI activity.</p>\n\t\t\t</div>\n\t\t\t<nav class=\"personal-tabs\" :aria-label=\"APP_DISPLAY_NAME + ' personal settings'\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"tab-button\"\n\t\t\t\t\t:class=\"{ active: activeView === 'bots' }\"\n\t\t\t\t\t@click=\"setView('bots')\">\n\t\t\t\t\t<span class=\"icon-comment\" />\n\t\t\t\t\tMy bots\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"tab-button\"\n\t\t\t\t\t:class=\"{ active: activeView === 'activity' }\"\n\t\t\t\t\t@click=\"setView('activity')\">\n\t\t\t\t\t<span class=\"icon-search\" />\n\t\t\t\t\tActivity\n\t\t\t\t</button>\n\t\t\t</nav>\n\t\t</header>\n\n\t\t<PersonalBots v-if=\"activeView === 'bots'\" />\n\t\t<PersonalActivity v-else />\n\t</div>\n</template>\n\n<script>\nimport PersonalActivity from './PersonalActivity.vue'\nimport { APP_DISPLAY_NAME } from '../branding.js'\nimport PersonalBots from './PersonalBots.vue'\n\nexport default {\n\tname: 'PersonalSettings',\n\n\tcomponents: {\n\t\tPersonalActivity,\n\t\tPersonalBots,\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tAPP_DISPLAY_NAME,\n\t\t\tactiveView: this.initialView(),\n\t\t}\n\t},\n\n\tmethods: {\n\t\tinitialView() {\n\t\t\tconst params = new URLSearchParams(window.location.search)\n\t\t\treturn params.get('view') === 'activity' ? 'activity' : 'bots'\n\t\t},\n\n\t\tsetView(view) {\n\t\t\tthis.activeView = view\n\t\t\tconst url = new URL(window.location.href)\n\t\t\tif (view === 'activity') {\n\t\t\t\turl.searchParams.set('view', 'activity')\n\t\t\t} else {\n\t\t\t\turl.searchParams.delete('view')\n\t\t\t}\n\t\t\twindow.history.replaceState({}, '', url.toString())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.educai-personal-settings {\n\tmax-width: 1280px;\n\tmargin: 0 auto;\n\tpadding: 24px;\n}\n\n.personal-header {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: space-between;\n\tgap: 16px;\n\tmargin-bottom: 20px;\n}\n\n.personal-header h2 {\n\tmargin: 0 0 4px;\n\tfont-size: 24px;\n}\n\n.personal-header p {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.personal-tabs {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\tpadding: 4px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n}\n\n.tab-button {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tmin-height: 36px;\n\tpadding: 0 12px;\n\tborder: 0;\n\tborder-radius: var(--border-radius);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n\tcursor: pointer;\n}\n\n.tab-button.active {\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n}\n\n.tab-button span {\n\tfilter: var(--background-invert-if-dark);\n}\n\n.tab-button.active span {\n\tfilter: none;\n}\n\n@media (max-width: 700px) {\n\t.educai-personal-settings {\n\t\tpadding: 16px;\n\t}\n\n\t.personal-header {\n\t\tflex-direction: column;\n\t}\n\n\t.personal-tabs {\n\t\twidth: 100%;\n\t}\n\n\t.tab-button {\n\t\tflex: 1;\n\t\tjustify-content: center;\n\t}\n}\n</style>\n","/* eslint-disable no-multiple-empty-lines */\nimport './utils/ensureOcFilePath.js'\nimport '@nextcloud/dialogs/style.css'\nimport Vue from 'vue'\nimport PersonalSettings from './views/PersonalSettings.vue'\nimport { syncEducAiAppIconFromSettings } from './utils/appIconRuntime.js'\n\nVue.mixin({ methods: { t, n } })\nconst VueApp = Vue.extend(PersonalSettings)\nnew VueApp().$mount('#educai-personal-root')\nsyncEducAiAppIconFromSettings()\n"],"names":["_sfc_main","APP_DISPLAY_NAME","params","response","axios","generateUrl","trace","error","showError","id","showSuccess","payload","blob","url","link","timestamp","durationMs","value","tokens","status","type","event","dateValue","date","prompt","v","BotCard","BotForm","botData","botId","bot","b","reason","sources","target","source","visibility","numeric","PersonalActivity","PersonalBots","view","Vue","VueApp","PersonalSettings","syncEducAiAppIconFromSettings"],"mappings":"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"}