diff --git a/services/library-desk/static/wikijs-integration.js b/services/library-desk/static/wikijs-integration.js
new file mode 100644
index 0000000..36b461e
--- /dev/null
+++ b/services/library-desk/static/wikijs-integration.js
@@ -0,0 +1,527 @@
+/**
+ * Library Desk Integration for Wiki.js
+ * Combined re-index and entity linking buttons
+ *
+ * Usage: Add to Wiki.js Code Injection:
+ *
+ */
+(function() {
+ 'use strict';
+
+ // Auto-detect Library Desk URL
+ const scriptTag = document.currentScript;
+ const scriptUrl = scriptTag ? scriptTag.src : '';
+ const libraryDeskUrl = scriptUrl ? scriptUrl.split('/static/')[0] : 'http://192.168.86.149:8089';
+
+ // Shared configuration
+ const CONFIG = window.LIBRARY_DESK_CONFIG || {
+ libraryDeskUrl: libraryDeskUrl,
+ apiKey: 'af88ed8f44bed81bdb20d0534f1c4547340b29e2aba4963f61a71b993d7eb6e5',
+ user: 'jpmschweitzer',
+ buttonPosition: 'toolbar', // 'toolbar' or 'floating'
+ debug: true
+ };
+
+ function log() {
+ if (CONFIG.debug) {
+ console.log('[Library Desk]', ...arguments);
+ }
+ }
+
+ // Initialize when DOM is ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+
+ function init() {
+ log('Initializing Library Desk integration...');
+ log('Library Desk URL:', CONFIG.libraryDeskUrl);
+
+ // Wait for Wiki.js to fully render
+ setTimeout(addButtons, 1500);
+ }
+
+ function addButtons() {
+ const pagePath = getPagePath();
+ if (!pagePath) {
+ log('Skipping buttons - not on a content page');
+ return;
+ }
+
+ log('Page path:', pagePath);
+
+ // Check if buttons already exist
+ if (document.getElementById('library-desk-buttons')) {
+ log('Buttons already exist');
+ return;
+ }
+
+ if (CONFIG.buttonPosition === 'floating') {
+ addFloatingButtons(pagePath);
+ } else {
+ addToolbarButtons(pagePath);
+ }
+ }
+
+ function addToolbarButtons(pagePath) {
+ const toolbar = findToolbar();
+ if (!toolbar) {
+ log('Toolbar not found, falling back to floating buttons');
+ addFloatingButtons(pagePath);
+ return;
+ }
+
+ // Container for both buttons
+ const btnContainer = document.createElement('div');
+ btnContainer.id = 'library-desk-buttons';
+ btnContainer.style.cssText = 'display: inline-flex; align-items: center; gap: 4px; margin-left: 8px;';
+
+ // Re-index button
+ const reindexBtn = createToolbarButton(
+ 'library-desk-reindex-btn',
+ 'Re-index this page in Library Desk (vectors + knowledge graph)',
+ 'mdi-database-sync',
+ () => reindexPage(pagePath, reindexBtn)
+ );
+
+ // Entity link button
+ const entityLinkBtn = createToolbarButton(
+ 'library-desk-entitylink-btn',
+ 'Link entities mentioned in this page to the knowledge graph',
+ 'mdi-graph-outline',
+ () => linkEntities(pagePath, entityLinkBtn)
+ );
+
+ btnContainer.appendChild(reindexBtn);
+ btnContainer.appendChild(entityLinkBtn);
+ toolbar.appendChild(btnContainer);
+ log('Toolbar buttons added');
+ }
+
+ function createToolbarButton(id, title, icon, onClick) {
+ const button = document.createElement('button');
+ button.id = id;
+ button.className = 'v-btn v-btn--icon v-btn--round theme--dark v-size--default';
+ button.type = 'button';
+ button.title = title;
+ button.setAttribute('aria-label', title);
+
+ button.innerHTML = ``;
+
+ button.addEventListener('mouseenter', function() {
+ this.style.backgroundColor = 'rgba(255, 255, 255, 0.08)';
+ });
+ button.addEventListener('mouseleave', function() {
+ this.style.backgroundColor = '';
+ });
+
+ button.onclick = function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ onClick();
+ };
+
+ return button;
+ }
+
+ function addFloatingButtons(pagePath) {
+ const container = document.createElement('div');
+ container.id = 'library-desk-buttons';
+ container.style.cssText = `
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ z-index: 9999;
+ `;
+
+ // Re-index button
+ const reindexBtn = createFloatingButton(
+ 'library-desk-reindex-btn',
+ '🔄 Re-index',
+ 'Re-index this page in Library Desk',
+ '#1976d2',
+ '#1565c0',
+ () => reindexPage(pagePath, reindexBtn)
+ );
+
+ // Entity link button
+ const entityLinkBtn = createFloatingButton(
+ 'library-desk-entitylink-btn',
+ '🔗 Link Entities',
+ 'Link entities in this page',
+ '#43a047',
+ '#388e3c',
+ () => linkEntities(pagePath, entityLinkBtn)
+ );
+
+ container.appendChild(reindexBtn);
+ container.appendChild(entityLinkBtn);
+ document.body.appendChild(container);
+ log('Floating buttons added');
+ }
+
+ function createFloatingButton(id, text, title, bgColor, hoverColor, onClick) {
+ const button = document.createElement('button');
+ button.id = id;
+ button.innerHTML = text;
+ button.title = title;
+ button.style.cssText = `
+ padding: 10px 15px;
+ background: ${bgColor};
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 500;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+ transition: all 0.3s;
+ `;
+
+ button.onmouseover = function() { this.style.background = hoverColor; };
+ button.onmouseout = function() { this.style.background = bgColor; };
+ button.onclick = onClick;
+
+ return button;
+ }
+
+ // ============================================================================
+ // RE-INDEX FUNCTIONALITY
+ // ============================================================================
+
+ async function reindexPage(pagePath, button) {
+ const isFloating = button.id === 'library-desk-reindex-btn' && button.innerHTML.includes('Re-index');
+ const icon = isFloating ? null : button.querySelector('.v-icon');
+ const originalContent = isFloating ? button.innerHTML : null;
+ const originalIcon = icon ? icon.className : null;
+
+ button.disabled = true;
+
+ if (isFloating) {
+ button.innerHTML = '⏳ Loading...';
+ button.style.background = '#757575';
+ } else {
+ icon.className = 'v-icon notranslate mdi mdi-loading mdi-spin theme--dark';
+ }
+
+ try {
+ log('Fetching page list...');
+
+ const listUrl = CONFIG.libraryDeskUrl + '/wiki/pages?user=' + CONFIG.user + '&limit=200';
+ const listResponse = await fetch(listUrl, {
+ headers: { 'Authorization': 'Bearer ' + CONFIG.apiKey }
+ });
+
+ if (!listResponse.ok) {
+ throw new Error('Failed to fetch page list: ' + listResponse.status);
+ }
+
+ const pageList = await listResponse.json();
+ log('Fetched', pageList.pages.length, 'pages');
+
+ // Find matching page
+ const page = findMatchingPage(pageList.pages, pagePath);
+
+ if (!page) {
+ log('Available pages:', pageList.pages.map(p => p.path));
+ throw new Error('Page not found in Library Desk. Check console for available pages.');
+ }
+
+ log('Matched page:', page.id, page.path, page.title);
+ log('Re-indexing...');
+
+ // Re-index
+ const response = await fetch(CONFIG.libraryDeskUrl + '/ingest/batch', {
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + CONFIG.apiKey,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ user: CONFIG.user,
+ page_ids: [page.id]
+ })
+ });
+
+ const result = await response.json();
+
+ if (response.ok && result.successful > 0) {
+ const pageResult = result.results[0];
+
+ if (isFloating) {
+ button.innerHTML = '✓ Done!';
+ button.style.background = '#4caf50';
+ } else {
+ icon.className = 'v-icon notranslate mdi mdi-check-circle theme--dark';
+ icon.style.color = '#4caf50';
+ }
+
+ const message = 'Re-indexed: ' + page.title + '\n\n' +
+ '✓ Vectors: ' + pageResult.vector_chunks_created + ' chunks\n' +
+ '✓ Entities: ' + pageResult.graph_entities_extracted + '\n' +
+ '✓ Relationships: ' + pageResult.graph_relationships_created + '\n' +
+ '⏱ Time: ' + Math.round(pageResult.processing_time_ms) + 'ms';
+
+ log('Success:', message);
+
+ // Show notification
+ showNotification('success', 'Page Re-indexed', message);
+
+ // Reset button
+ setTimeout(function() {
+ if (isFloating) {
+ button.innerHTML = originalContent;
+ button.style.background = '#1976d2';
+ } else {
+ icon.className = originalIcon;
+ icon.style.color = '';
+ }
+ button.disabled = false;
+ }, 3000);
+ } else {
+ throw new Error(result.detail || result.error || 'Re-indexing failed');
+ }
+ } catch (error) {
+ console.error('[Library Desk] Error:', error);
+
+ if (isFloating) {
+ button.innerHTML = '✗ Failed';
+ button.style.background = '#f44336';
+ } else {
+ icon.className = 'v-icon notranslate mdi mdi-alert-circle theme--dark';
+ icon.style.color = '#f44336';
+ }
+
+ showNotification('error', 'Re-indexing Failed', error.message + '\n\nCheck browser console for details.');
+
+ // Reset button
+ setTimeout(function() {
+ if (isFloating) {
+ button.innerHTML = originalContent;
+ button.style.background = '#1976d2';
+ } else {
+ icon.className = originalIcon;
+ icon.style.color = '';
+ }
+ button.disabled = false;
+ }, 4000);
+ }
+ }
+
+ // ============================================================================
+ // ENTITY LINKING FUNCTIONALITY
+ // ============================================================================
+
+ async function linkEntities(pagePath, button) {
+ const isFloating = button.id === 'library-desk-entitylink-btn' && button.innerHTML.includes('Link');
+ const icon = isFloating ? null : button.querySelector('.v-icon');
+ const originalContent = isFloating ? button.innerHTML : null;
+ const originalIcon = icon ? icon.className : null;
+
+ button.disabled = true;
+
+ if (isFloating) {
+ button.innerHTML = '⏳ Finding...';
+ button.style.background = '#757575';
+ } else {
+ icon.className = 'v-icon notranslate mdi mdi-loading mdi-spin theme--dark';
+ }
+
+ try {
+ log('Fetching page list...');
+
+ // Step 1: Get page ID
+ const listUrl = CONFIG.libraryDeskUrl + '/wiki/pages?user=' + CONFIG.user + '&limit=200';
+ const listResponse = await fetch(listUrl, {
+ headers: { 'Authorization': 'Bearer ' + CONFIG.apiKey }
+ });
+
+ if (!listResponse.ok) {
+ throw new Error('Failed to fetch page list');
+ }
+
+ const pageList = await listResponse.json();
+ const page = findMatchingPage(pageList.pages, pagePath);
+
+ if (!page) {
+ throw new Error('Page not found in Library Desk');
+ }
+
+ log('Found page:', page.id, page.title);
+
+ // Step 2: Call entity linking endpoint
+ const response = await fetch(CONFIG.libraryDeskUrl + '/entity-linking/link-page', {
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + CONFIG.apiKey,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ user: CONFIG.user,
+ page_id: page.id,
+ create_relationships: true,
+ re_index_if_changed: true
+ })
+ });
+
+ const result = await response.json();
+
+ if (response.ok) {
+ if (isFloating) {
+ button.innerHTML = '✓ Linked!';
+ button.style.background = '#4caf50';
+ } else {
+ icon.className = 'v-icon notranslate mdi mdi-check-circle theme--dark';
+ icon.style.color = '#4caf50';
+ }
+
+ const entityList = result.entities_found
+ .slice(0, 5)
+ .map(e => `- ${e.name} (${e.mentions} mentions)${e.path ? ' ✓ linked' : ''}`)
+ .join('\n');
+
+ const message = 'Entity Linking Complete!\n\n' +
+ 'Found: ' + result.entities_found.length + ' entities\n' +
+ 'Wiki links added: ' + result.content_links_added + '\n' +
+ 'Graph links added: ' + result.new_graph_links_created + '\n' +
+ 'Content updated: ' + (result.content_updated ? 'Yes' : 'No') + '\n' +
+ 'Re-indexed: ' + (result.re_indexed ? 'Yes' : 'No') + '\n\n' +
+ 'Top entities:\n' + (entityList || 'None');
+
+ log('Success:', result);
+ alert(message);
+
+ // Reload page if content was updated
+ if (result.content_updated) {
+ log('Reloading page to show updated content...');
+ setTimeout(function() {
+ window.location.reload();
+ }, 1500);
+ } else {
+ setTimeout(function() {
+ if (isFloating) {
+ button.innerHTML = originalContent;
+ button.style.background = '#43a047';
+ } else {
+ icon.className = originalIcon;
+ icon.style.color = '';
+ }
+ button.disabled = false;
+ }, 3000);
+ }
+ } else {
+ throw new Error(result.detail || 'Entity linking failed');
+ }
+ } catch (error) {
+ console.error('[Library Desk] Error:', error);
+
+ if (isFloating) {
+ button.innerHTML = '✗ Failed';
+ button.style.background = '#f44336';
+ } else {
+ icon.className = 'v-icon notranslate mdi mdi-alert-circle theme--dark';
+ icon.style.color = '#f44336';
+ }
+
+ alert('Entity linking failed:\n\n' + error.message);
+
+ setTimeout(function() {
+ if (isFloating) {
+ button.innerHTML = originalContent;
+ button.style.background = '#43a047';
+ } else {
+ icon.className = originalIcon;
+ icon.style.color = '';
+ }
+ button.disabled = false;
+ }, 4000);
+ }
+ }
+
+ // ============================================================================
+ // SHARED UTILITIES
+ // ============================================================================
+
+ function findMatchingPage(pages, pagePath) {
+ const possiblePaths = [
+ pagePath,
+ 'users/' + CONFIG.user + '/' + pagePath,
+ pagePath.replace(/^\//, ''),
+ CONFIG.user + '/' + pagePath
+ ];
+
+ log('Looking for page matching:', possiblePaths);
+
+ for (var i = 0; i < pages.length; i++) {
+ var page = pages[i];
+ for (var j = 0; j < possiblePaths.length; j++) {
+ var testPath = possiblePaths[j];
+ if (page.path === testPath ||
+ page.path.endsWith('/' + testPath) ||
+ ('users/' + CONFIG.user + '/' + testPath) === page.path) {
+ return page;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ function findToolbar() {
+ const selectors = [
+ 'header.v-toolbar .v-toolbar__content',
+ '.v-app-bar .v-toolbar__content',
+ 'nav.v-toolbar .v-toolbar__content',
+ 'header .v-toolbar__content',
+ '.v-app-bar__content'
+ ];
+
+ for (var i = 0; i < selectors.length; i++) {
+ var elem = document.querySelector(selectors[i]);
+ if (elem) return elem;
+ }
+
+ return null;
+ }
+
+ function getPagePath() {
+ var path = window.location.pathname;
+ path = path.replace(/^\//, '').replace(/\/$/, '');
+ path = path.replace(/^[a-z]{2}\//, ''); // Remove language code
+
+ // Don't add buttons to home page or special pages
+ if (path === '' || path === 'home' || path.startsWith('_') || path.startsWith('a/')) {
+ return null;
+ }
+
+ return path;
+ }
+
+ function showNotification(type, title, message) {
+ // Try Wiki.js notifications if available
+ if (window.$store && typeof window.$store.commit === 'function') {
+ try {
+ window.$store.commit('showNotification', {
+ message: title + ': ' + message,
+ style: type,
+ icon: type === 'success' ? 'check' : 'alert'
+ });
+ return;
+ } catch (e) {
+ // Fall through to alert
+ }
+ }
+
+ // Fallback to browser alert
+ alert(title + '\n\n' + message);
+ }
+
+ log('Library Desk integration loaded successfully');
+ log('Version: 2.0.0 - Combined re-index and entity linking');
+
+})();