9bd02d7ef7
Two fixes for the vector visualization page: 1. **CSS Loading Fix**: Moved CSS <link> from vector_viz.html fragment to user_info.html <head> block. HTMX fragments don't process <link> tags in <head>, causing unstyled page. Now CSS loads correctly. 2. **Camera Preservation**: Modified renderPlot() to preserve camera position when toggling query point visibility. Previously, toggling the "Show Query Point" checkbox would reset zoom/rotation to default. Now reads existing camera settings from plot before updating. Related: nextcloud_mcp_server/auth/static/vector-viz.js:123-130 Related: nextcloud_mcp_server/auth/templates/user_info.html:12 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
651 lines
32 KiB
HTML
651 lines
32 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Nextcloud MCP Server{% endblock %}
|
|
|
|
{% block extra_head %}
|
|
<!-- htmx for dynamic loading -->
|
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
|
|
|
<!-- Alpine.js for state management -->
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
|
|
<!-- Plotly.js for vector visualization -->
|
|
<script src="https://cdn.plot.ly/plotly-3.3.0.min.js"></script>
|
|
|
|
<!-- Vector Viz static assets -->
|
|
<link rel="stylesheet" href="/app/static/vector-viz.css">
|
|
{% endblock %}
|
|
|
|
{% block extra_styles %}
|
|
/* Smooth htmx transitions */
|
|
.htmx-swapping {
|
|
opacity: 0;
|
|
transition: opacity 200ms ease-out;
|
|
}
|
|
|
|
.htmx-settling {
|
|
opacity: 1;
|
|
transition: opacity 200ms ease-in;
|
|
}
|
|
|
|
/* Logout button styling */
|
|
.logout-section {
|
|
margin-top: 20px;
|
|
padding-top: 20px;
|
|
border-top: 1px solid var(--color-border);
|
|
}
|
|
|
|
/* Welcome tab specific styles */
|
|
.hero-section {
|
|
background: linear-gradient(135deg, var(--color-primary-element) 0%, #0082c9 100%);
|
|
color: white;
|
|
padding: 60px 24px;
|
|
margin: -24px -24px 40px -24px;
|
|
border-radius: 0 0 var(--border-radius-large) var(--border-radius-large);
|
|
text-align: center;
|
|
}
|
|
|
|
.hero-section h1 {
|
|
color: white;
|
|
font-size: 36px;
|
|
margin: 0 0 16px 0;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.hero-section p {
|
|
font-size: 18px;
|
|
opacity: 0.95;
|
|
max-width: 700px;
|
|
margin: 0 auto;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.feature-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 24px;
|
|
margin: 32px 0;
|
|
}
|
|
|
|
.feature-card {
|
|
background: var(--color-main-background);
|
|
border: 2px solid var(--color-border);
|
|
border-radius: var(--border-radius-large);
|
|
padding: 24px;
|
|
transition: all 0.2s;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
color: inherit;
|
|
display: block;
|
|
}
|
|
|
|
.feature-card:hover {
|
|
border-color: var(--color-primary-element);
|
|
box-shadow: 0 4px 12px rgba(0, 103, 158, 0.15);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.feature-card h3 {
|
|
color: var(--color-primary-element);
|
|
font-size: 20px;
|
|
margin: 12px 0 8px 0;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.feature-card p {
|
|
color: var(--color-text-maxcontrast);
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
margin: 8px 0 0 0;
|
|
}
|
|
|
|
.feature-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
background: var(--color-primary-element-light);
|
|
border-radius: var(--border-radius);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.feature-icon svg {
|
|
width: 28px;
|
|
height: 28px;
|
|
fill: var(--color-primary-element);
|
|
}
|
|
|
|
.info-section {
|
|
background: var(--color-background-hover);
|
|
border-radius: var(--border-radius-large);
|
|
padding: 32px;
|
|
margin: 32px 0;
|
|
}
|
|
|
|
.info-section h2 {
|
|
color: var(--color-main-text);
|
|
font-size: 24px;
|
|
margin: 0 0 16px 0;
|
|
border: none;
|
|
padding: 0;
|
|
}
|
|
|
|
.info-section p {
|
|
color: var(--color-text-maxcontrast);
|
|
line-height: 1.7;
|
|
margin: 12px 0;
|
|
}
|
|
|
|
.info-section ul {
|
|
margin: 12px 0;
|
|
padding-left: 24px;
|
|
}
|
|
|
|
.info-section li {
|
|
color: var(--color-text-maxcontrast);
|
|
line-height: 1.7;
|
|
margin: 8px 0;
|
|
}
|
|
|
|
.info-section code {
|
|
background: var(--color-main-background);
|
|
padding: 2px 8px;
|
|
border-radius: var(--border-radius);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.auth-status {
|
|
background: var(--color-primary-element-light);
|
|
border-left: 4px solid var(--color-primary-element);
|
|
padding: 16px 20px;
|
|
margin: 24px 0;
|
|
border-radius: var(--border-radius);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.auth-status svg {
|
|
width: 24px;
|
|
height: 24px;
|
|
fill: var(--color-primary-element);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.auth-status-text {
|
|
flex: 1;
|
|
}
|
|
|
|
.auth-status-text strong {
|
|
display: block;
|
|
color: var(--color-main-text);
|
|
font-size: 14px;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.auth-status-text span {
|
|
color: var(--color-text-maxcontrast);
|
|
font-size: 13px;
|
|
}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="app-content-wrapper" x-data="{ activeSection: 'welcome', navOpen: true }">
|
|
<!-- Side Navigation -->
|
|
<nav id="app-navigation" :class="{ 'app-navigation--closed': !navOpen }">
|
|
<div class="app-navigation__content">
|
|
<!-- Navigation List -->
|
|
<ul class="app-navigation-list">
|
|
<li class="app-navigation-entry" :class="{ 'active': activeSection === 'welcome' }">
|
|
<div class="app-navigation-entry__wrapper">
|
|
<a href="#"
|
|
@click.prevent="activeSection = 'welcome'"
|
|
class="app-navigation-entry-link">
|
|
<span class="app-navigation-entry-icon">
|
|
<svg class="nav-icon" viewBox="0 0 24 24">
|
|
<path d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" />
|
|
</svg>
|
|
</span>
|
|
<span class="app-navigation-entry__name">Welcome</span>
|
|
</a>
|
|
</div>
|
|
</li>
|
|
|
|
<li class="app-navigation-entry" :class="{ 'active': activeSection === 'user-info' }">
|
|
<div class="app-navigation-entry__wrapper">
|
|
<a href="#"
|
|
@click.prevent="activeSection = 'user-info'"
|
|
class="app-navigation-entry-link">
|
|
<span class="app-navigation-entry-icon">
|
|
<svg class="nav-icon" viewBox="0 0 24 24">
|
|
<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" />
|
|
</svg>
|
|
</span>
|
|
<span class="app-navigation-entry__name">User Info</span>
|
|
</a>
|
|
</div>
|
|
</li>
|
|
|
|
{% if show_vector_sync_tab %}
|
|
<li class="app-navigation-entry" :class="{ 'active': activeSection === 'vector-sync' }">
|
|
<div class="app-navigation-entry__wrapper">
|
|
<a href="#"
|
|
@click.prevent="activeSection = 'vector-sync'"
|
|
class="app-navigation-entry-link">
|
|
<span class="app-navigation-entry-icon">
|
|
<svg class="nav-icon" viewBox="0 0 24 24">
|
|
<path d="M12,18A6,6 0 0,1 6,12C6,11 6.25,10.03 6.7,9.2L5.24,7.74C4.46,8.97 4,10.43 4,12A8,8 0 0,0 12,20V23L16,19L12,15M12,4V1L8,5L12,9V6A6,6 0 0,1 18,12C18,13 17.75,13.97 17.3,14.8L18.76,16.26C19.54,15.03 20,13.57 20,12A8,8 0 0,0 12,4Z" />
|
|
</svg>
|
|
</span>
|
|
<span class="app-navigation-entry__name">Vector Sync</span>
|
|
</a>
|
|
</div>
|
|
</li>
|
|
|
|
<li class="app-navigation-entry" :class="{ 'active': activeSection === 'vector-viz' }">
|
|
<div class="app-navigation-entry__wrapper">
|
|
<a href="#"
|
|
@click.prevent="activeSection = 'vector-viz'"
|
|
class="app-navigation-entry-link">
|
|
<span class="app-navigation-entry-icon">
|
|
<svg class="nav-icon" viewBox="0 0 24 24">
|
|
<path d="M22,21H2V3H4V19H6V10H10V19H12V6H16V19H18V14H22V21Z" />
|
|
</svg>
|
|
</span>
|
|
<span class="app-navigation-entry__name">Vector Viz</span>
|
|
</a>
|
|
</div>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% if show_webhooks_tab %}
|
|
<li class="app-navigation-entry" :class="{ 'active': activeSection === 'webhooks' }">
|
|
<div class="app-navigation-entry__wrapper">
|
|
<a href="#"
|
|
@click.prevent="activeSection = 'webhooks'"
|
|
class="app-navigation-entry-link">
|
|
<span class="app-navigation-entry-icon">
|
|
<svg class="nav-icon" viewBox="0 0 24 24">
|
|
<path d="M10.59,13.41C11,13.8 11,14.44 10.59,14.83C10.2,15.22 9.56,15.22 9.17,14.83C7.22,12.88 7.22,9.71 9.17,7.76V7.76L12.71,4.22C14.66,2.27 17.83,2.27 19.78,4.22C21.73,6.17 21.73,9.34 19.78,11.29L18.29,12.78C18.3,11.96 18.17,11.14 17.89,10.36L18.36,9.88C19.54,8.71 19.54,6.81 18.36,5.64C17.19,4.46 15.29,4.46 14.12,5.64L10.59,9.17C9.41,10.34 9.41,12.24 10.59,13.41M13.41,9.17C13.8,8.78 14.44,8.78 14.83,9.17C16.78,11.12 16.78,14.29 14.83,16.24V16.24L11.29,19.78C9.34,21.73 6.17,21.73 4.22,19.78C2.27,17.83 2.27,14.66 4.22,12.71L5.71,11.22C5.7,12.04 5.83,12.86 6.11,13.65L5.64,14.12C4.46,15.29 4.46,17.19 5.64,18.36C6.81,19.54 8.71,19.54 9.88,18.36L13.41,14.83C14.59,13.66 14.59,11.76 13.41,10.59C13,10.2 13,9.56 13.41,9.17Z" />
|
|
</svg>
|
|
</span>
|
|
<span class="app-navigation-entry__name">Webhooks</span>
|
|
</a>
|
|
</div>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
|
|
<!-- Settings/Logout at bottom -->
|
|
{% if logout_url %}
|
|
<ul class="app-navigation__settings">
|
|
<li class="app-navigation-entry">
|
|
<div class="app-navigation-entry__wrapper">
|
|
<a href="{{ logout_url }}" class="app-navigation-entry-link">
|
|
<span class="app-navigation-entry-icon">
|
|
<svg class="nav-icon" viewBox="0 0 24 24">
|
|
<path d="M16,17V14H9V10H16V7L21,12L16,17M14,2A2,2 0 0,1 16,4V6H14V4H5V20H14V18H16V20A2,2 0 0,1 14,22H5A2,2 0 0,1 3,20V4A2,2 0 0,1 5,2H14Z" />
|
|
</svg>
|
|
</span>
|
|
<span class="app-navigation-entry__name">Logout</span>
|
|
</a>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Toggle Button (mobile) -->
|
|
<button @click="navOpen = !navOpen"
|
|
class="app-navigation-toggle"
|
|
:aria-expanded="navOpen.toString()">
|
|
☰
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- Main Content Area -->
|
|
<main id="app-content">
|
|
<div class="page-content">
|
|
<!-- Welcome Section -->
|
|
<div x-show="activeSection === 'welcome'">
|
|
<!-- Hero Section -->
|
|
<div class="hero-section">
|
|
<h1>Welcome to Nextcloud MCP Server</h1>
|
|
<p>
|
|
Interactive user interface for semantic search and document retrieval.
|
|
Test queries, visualize results, and explore your Nextcloud content using RAG workflows.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Authentication Status -->
|
|
<div class="auth-status">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" />
|
|
</svg>
|
|
<div class="auth-status-text">
|
|
<strong>Authenticated as: {{ username }}</strong>
|
|
<span>Authentication mode: <code>{{ auth_mode }}</code></span>
|
|
</div>
|
|
</div>
|
|
|
|
{% if vector_sync_enabled %}
|
|
<!-- Vector Sync Enabled Content -->
|
|
<div class="info-section">
|
|
<h2>About Semantic Search</h2>
|
|
<p>
|
|
This interface provides access to <strong>semantic search</strong> capabilities powered by vector embeddings.
|
|
Unlike traditional keyword search, semantic search understands the <em>meaning</em> of your queries and finds
|
|
conceptually similar content across your Nextcloud apps.
|
|
</p>
|
|
<p>
|
|
<strong>How it works:</strong>
|
|
</p>
|
|
<ul>
|
|
<li>Documents from Notes, Calendar, Files, Contacts, and Deck are indexed into a vector database</li>
|
|
<li>Each document chunk is converted to a 768-dimensional vector embedding that captures semantic meaning</li>
|
|
<li>Queries are also converted to embeddings and matched against document vectors using similarity search</li>
|
|
<li>Results can be retrieved using pure semantic search or hybrid BM25 search combining keywords and semantics</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="info-section">
|
|
<h2>RAG Workflow Integration</h2>
|
|
<p>
|
|
This UI allows you to <strong>test the same queries that Large Language Models (LLMs) would use</strong> in a
|
|
Retrieval-Augmented Generation (RAG) workflow. When an AI assistant needs to answer questions about your data:
|
|
</p>
|
|
<ul>
|
|
<li><strong>Step 1:</strong> The assistant converts your question into a search query</li>
|
|
<li><strong>Step 2:</strong> The MCP server retrieves relevant document chunks using semantic search</li>
|
|
<li><strong>Step 3:</strong> Retrieved context is passed to the LLM to generate an informed answer</li>
|
|
</ul>
|
|
|
|
<!-- RAG Workflow Diagram -->
|
|
<div style="background: var(--color-main-background); border: 2px solid var(--color-primary-element); border-radius: var(--border-radius-large); padding: 24px; margin: 24px 0; overflow-x: auto;">
|
|
<div style="text-align: center; font-weight: 600; margin-bottom: 20px; color: var(--color-primary-element); font-size: 16px;">
|
|
MCP Sampling RAG Workflow
|
|
</div>
|
|
|
|
<!-- Four-component bidirectional flow -->
|
|
<div style="max-width: 1000px; margin: 0 auto;">
|
|
<div style="display: grid; grid-template-columns: 0.7fr auto 1fr auto 1fr auto 0.9fr; gap: 10px; align-items: center;">
|
|
<!-- User -->
|
|
<div style="background: var(--color-background-hover); border: 2px solid var(--color-border); border-radius: var(--border-radius-large); padding: 14px; text-align: center;">
|
|
<div style="font-size: 26px; margin-bottom: 5px;">👤</div>
|
|
<div style="font-weight: 600; color: var(--color-main-text); font-size: 12px;">User</div>
|
|
<div style="font-size: 9px; color: var(--color-text-maxcontrast); font-style: italic; margin-top: 5px; line-height: 1.2;">
|
|
"What are health<br>benefits of coffee?"
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Arrow User <-> Client -->
|
|
<div style="text-align: center;">
|
|
<div style="font-size: 20px; color: var(--color-text-maxcontrast);">↔</div>
|
|
</div>
|
|
|
|
<!-- MCP Client + LLM (combined) -->
|
|
<div style="background: var(--color-primary-element-light); border: 2px solid var(--color-primary-element); border-radius: var(--border-radius-large); padding: 12px; text-align: center;">
|
|
<div style="font-weight: 600; color: var(--color-primary-element); font-size: 13px; margin-bottom: 8px;">MCP Client + LLM</div>
|
|
|
|
<div style="background: var(--color-main-background); border-radius: var(--border-radius); padding: 8px; margin-bottom: 6px;">
|
|
<div style="font-size: 9px; color: var(--color-text-maxcontrast);">(Claude Code)</div>
|
|
</div>
|
|
|
|
<div style="background: var(--color-main-background); border-radius: var(--border-radius); padding: 8px; border: 2px solid var(--color-primary-element);">
|
|
<div style="font-size: 16px; margin-bottom: 2px;">🧠</div>
|
|
<div style="font-weight: 600; color: var(--color-main-text); font-size: 10px;">Client's LLM</div>
|
|
<div style="font-size: 8px; color: var(--color-text-maxcontrast);">(Claude)</div>
|
|
</div>
|
|
|
|
<div style="margin-top: 8px; font-size: 8px; color: var(--color-text-maxcontrast); line-height: 1.2;">
|
|
<strong>Enables RAG:</strong><br>
|
|
Receives context,<br>
|
|
generates answer
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Arrow Client <-> Server -->
|
|
<div style="text-align: center;">
|
|
<div style="font-size: 20px; color: var(--color-primary-element);">↔</div>
|
|
<div style="font-size: 7px; color: var(--color-text-maxcontrast); margin-top: 2px; font-weight: 600; line-height: 1.1;">
|
|
Query +<br>
|
|
Sampling
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MCP Server -->
|
|
<div style="background: var(--color-primary-element-light); border: 2px solid var(--color-primary-element); border-radius: var(--border-radius-large); padding: 12px; text-align: center;">
|
|
<div style="font-weight: 600; color: var(--color-primary-element); font-size: 13px; margin-bottom: 8px;">MCP Server</div>
|
|
|
|
<div style="background: var(--color-main-background); border-radius: var(--border-radius); padding: 7px; margin-bottom: 5px;">
|
|
<div style="font-weight: 600; color: var(--color-main-text); font-size: 9px; margin-bottom: 2px;">1. Semantic Search</div>
|
|
<div style="font-size: 7px; color: var(--color-text-maxcontrast); line-height: 1.2;">
|
|
Vector embeddings<br>
|
|
BM25 Hybrid + RRF
|
|
</div>
|
|
</div>
|
|
|
|
<div style="background: var(--color-main-background); border-radius: var(--border-radius); padding: 7px; margin-bottom: 5px;">
|
|
<div style="font-weight: 600; color: var(--color-main-text); font-size: 9px; margin-bottom: 2px;">2. Retrieve Context</div>
|
|
<div style="font-size: 7px; color: var(--color-text-maxcontrast); line-height: 1.2;">
|
|
Top relevant docs<br>
|
|
with scores
|
|
</div>
|
|
</div>
|
|
|
|
<div style="background: var(--color-main-background); border-radius: var(--border-radius); padding: 7px; margin-bottom: 5px;">
|
|
<div style="font-weight: 600; color: var(--color-main-text); font-size: 9px; margin-bottom: 2px;">3. Format Response</div>
|
|
<div style="font-size: 7px; color: var(--color-text-maxcontrast); line-height: 1.2;">
|
|
Document chunks<br>
|
|
with citations
|
|
</div>
|
|
</div>
|
|
|
|
<div style="background: var(--color-main-background); border-radius: var(--border-radius); padding: 7px;">
|
|
<div style="font-weight: 600; color: var(--color-main-text); font-size: 9px; margin-bottom: 2px;">4. Send to LLM</div>
|
|
<div style="font-size: 7px; color: var(--color-text-maxcontrast); line-height: 1.2;">
|
|
Via MCP sampling<br>
|
|
for answer generation
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Arrow Server <-> Nextcloud -->
|
|
<div style="text-align: center;">
|
|
<div style="font-size: 20px; color: var(--color-primary-element);">↔</div>
|
|
<div style="font-size: 7px; color: var(--color-text-maxcontrast); margin-top: 2px; font-weight: 600; line-height: 1.1;">
|
|
Retrieve
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Nextcloud -->
|
|
<div style="background: var(--color-background-hover); border: 2px solid var(--color-border); border-radius: var(--border-radius-large); padding: 12px; text-align: center; position: relative;">
|
|
<img src="/app/static/nextcloud-logo.png" alt="Nextcloud" style="width: 40px; height: 40px; margin-bottom: 6px;" />
|
|
<div style="font-weight: 600; color: var(--color-main-text); font-size: 12px; margin-bottom: 4px;">Nextcloud</div>
|
|
<div style="font-size: 8px; color: var(--color-text-maxcontrast); line-height: 1.2;">
|
|
Notes, Calendar,<br>
|
|
Files, Contacts,<br>
|
|
Deck
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Explanation below diagram -->
|
|
<div style="margin-top: 24px; padding: 16px; background: var(--color-background-hover); border-radius: var(--border-radius); border-left: 4px solid var(--color-primary-element);">
|
|
<div style="font-size: 12px; color: var(--color-main-text); line-height: 1.6;">
|
|
<strong>How RAG works via MCP Sampling:</strong>
|
|
</div>
|
|
<ol style="margin: 8px 0 0 0; padding-left: 20px; font-size: 11px; color: var(--color-text-maxcontrast); line-height: 1.6;">
|
|
<li>User asks question through MCP Client</li>
|
|
<li>Client sends query to MCP Server</li>
|
|
<li>Server retrieves relevant document context from Nextcloud</li>
|
|
<li><strong>Server sends context back to Client's LLM</strong> (MCP Sampling)</li>
|
|
<li>Client's LLM generates answer with citations using retrieved context</li>
|
|
<li>Answer returned to user</li>
|
|
</ol>
|
|
<div style="margin-top: 8px; font-size: 10px; color: var(--color-text-maxcontrast); font-style: italic;">
|
|
The server has no LLM - it only retrieves context. The client's existing LLM is reused for answer generation.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<p style="margin-top: 16px;">
|
|
<strong>Key Point:</strong> The MCP server retrieves context but doesn't generate answers itself.
|
|
Through <strong>MCP sampling</strong>, it requests the client's LLM to generate responses, giving users
|
|
full control over which model is used and ensuring all processing happens client-side.
|
|
</p>
|
|
|
|
<p>
|
|
By using this interface, you can preview search results, understand relevance scores, and verify
|
|
that the system retrieves the right information before it reaches the LLM.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Feature Cards -->
|
|
<h2>Available Features</h2>
|
|
<div class="feature-grid">
|
|
<a href="#" @click.prevent="activeSection = 'user-info'" class="feature-card">
|
|
<div class="feature-icon">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" />
|
|
</svg>
|
|
</div>
|
|
<h3>User Information</h3>
|
|
<p>
|
|
View your authentication details, session information, and IdP profile.
|
|
Manage background access permissions.
|
|
</p>
|
|
</a>
|
|
|
|
<a href="#" @click.prevent="activeSection = 'vector-sync'" class="feature-card">
|
|
<div class="feature-icon">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M12,18A6,6 0 0,1 6,12C6,11 6.25,10.03 6.7,9.2L5.24,7.74C4.46,8.97 4,10.43 4,12A8,8 0 0,0 12,20V23L16,19L12,15M12,4V1L8,5L12,9V6A6,6 0 0,1 18,12C18,13 17.75,13.97 17.3,14.8L18.76,16.26C19.54,15.03 20,13.57 20,12A8,8 0 0,0 12,4Z" />
|
|
</svg>
|
|
</div>
|
|
<h3>Vector Sync Status</h3>
|
|
<p>
|
|
Monitor real-time indexing progress with metrics for indexed documents, pending queue,
|
|
and synchronization status.
|
|
</p>
|
|
</a>
|
|
|
|
<a href="#" @click.prevent="activeSection = 'vector-viz'" class="feature-card">
|
|
<div class="feature-icon">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M22,21H2V3H4V19H6V10H10V19H12V6H16V19H18V14H22V21Z" />
|
|
</svg>
|
|
</div>
|
|
<h3>Vector Visualization</h3>
|
|
<p>
|
|
Interactive search interface with 2D PCA visualization. Compare algorithms,
|
|
view relevance scores, and explore matched document chunks.
|
|
</p>
|
|
</a>
|
|
</div>
|
|
|
|
{% else %}
|
|
<!-- Vector Sync Disabled Content -->
|
|
<div class="warning">
|
|
<h3 style="margin-top: 0;">Vector Sync is Disabled</h3>
|
|
<p>
|
|
Semantic search and vector visualization features are currently disabled.
|
|
To enable these features, set <code>VECTOR_SYNC_ENABLED=true</code> in your environment configuration.
|
|
</p>
|
|
<p style="margin-bottom: 0;">
|
|
<strong>Learn more:</strong>
|
|
<a href="https://github.com/cbcoutinho/nextcloud-mcp-server/blob/master/docs/configuration.md" target="_blank" style="color: inherit; text-decoration: underline;">
|
|
Configuration Guide
|
|
</a>
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Limited Feature Card -->
|
|
<h2>Available Features</h2>
|
|
<div class="feature-grid">
|
|
<a href="#" @click.prevent="activeSection = 'user-info'" class="feature-card">
|
|
<div class="feature-icon">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" />
|
|
</svg>
|
|
</div>
|
|
<h3>User Information</h3>
|
|
<p>
|
|
View your authentication details, session information, and IdP profile.
|
|
Manage background access permissions.
|
|
</p>
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Documentation Section -->
|
|
<div class="info-section" style="margin-top: 40px;">
|
|
<h2>Documentation</h2>
|
|
<p>
|
|
For detailed information about configuration, authentication modes, and advanced features,
|
|
please refer to the project documentation:
|
|
</p>
|
|
<ul>
|
|
<li><a href="https://github.com/cbcoutinho/nextcloud-mcp-server/blob/master/docs/installation.md" target="_blank">Installation Guide</a></li>
|
|
<li><a href="https://github.com/cbcoutinho/nextcloud-mcp-server/blob/master/docs/configuration.md" target="_blank">Configuration Options</a></li>
|
|
<li><a href="https://github.com/cbcoutinho/nextcloud-mcp-server/blob/master/docs/authentication.md" target="_blank">Authentication Modes</a></li>
|
|
{% if vector_sync_enabled %}
|
|
<li><a href="https://github.com/cbcoutinho/nextcloud-mcp-server/blob/master/docs/user-guide/vector-sync-ui.md" target="_blank">Vector Sync UI Guide</a></li>
|
|
{% endif %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User Info Section -->
|
|
<div x-show="activeSection === 'user-info'">
|
|
<div class="content-section">
|
|
<h1>User Information</h1>
|
|
{{ user_info_tab_html|safe }}
|
|
</div>
|
|
</div>
|
|
|
|
{% if show_vector_sync_tab %}
|
|
<!-- Vector Sync Section -->
|
|
<div x-show="activeSection === 'vector-sync'">
|
|
<div class="content-section">
|
|
<h1>Vector Sync Status</h1>
|
|
{{ vector_sync_tab_html|safe }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Vector Viz Section -->
|
|
<div x-show="activeSection === 'vector-viz'">
|
|
<div class="content-section">
|
|
<h1>Vector Visualization</h1>
|
|
<div hx-get="/app/vector-viz" hx-trigger="load" hx-swap="outerHTML">
|
|
<p style="color: #999;">Loading vector visualization...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if show_webhooks_tab %}
|
|
<!-- Webhooks Section -->
|
|
<div x-show="activeSection === 'webhooks'">
|
|
<div class="content-section">
|
|
<h1>Webhook Management</h1>
|
|
{{ webhooks_tab_html|safe }}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
// Set global Nextcloud base URL for use in external JS
|
|
window.NEXTCLOUD_BASE_URL = '{{ nextcloud_host_for_links }}';
|
|
</script>
|
|
<script src="/app/static/vector-viz.js"></script>
|
|
{% endblock %}
|