botui/ui/suite/research/research.html

1457 lines
39 KiB
HTML

<!-- Research - AI-Powered Search & Discovery -->
<div class="research-container" id="research-app">
<!-- Sidebar - Sources & Collections -->
<aside class="research-sidebar" id="research-sidebar">
<div class="sidebar-header">
<h2>Research</h2>
<button class="btn-icon" id="toggle-research-sidebar" title="Collapse">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="11 17 6 12 11 7"></polyline>
<polyline points="18 17 13 12 18 7"></polyline>
</svg>
</button>
</div>
<!-- Focus Modes -->
<div class="focus-modes">
<button class="focus-btn active" data-focus="all" title="Search everything">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="2" y1="12" x2="22" y2="12"></line>
<path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"></path>
</svg>
<span>All</span>
</button>
<button class="focus-btn" data-focus="academic" title="Academic papers">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"></path>
<path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"></path>
</svg>
<span>Academic</span>
</button>
<button class="focus-btn" data-focus="code" title="Code & documentation">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="16 18 22 12 16 6"></polyline>
<polyline points="8 6 2 12 8 18"></polyline>
</svg>
<span>Code</span>
</button>
<button class="focus-btn" data-focus="internal" title="Internal knowledge base">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
<span>Internal</span>
</button>
</div>
<!-- Collections -->
<div class="sidebar-section">
<div class="section-header">
<h3>Collections</h3>
<button class="btn-icon-sm" title="New Collection"
hx-post="/api/research/collections/new"
hx-target="#collections-list"
hx-swap="afterbegin">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
</div>
<div class="collections-list" id="collections-list"
hx-get="/api/research/collections"
hx-trigger="load"
hx-swap="innerHTML">
<!-- Collections loaded here -->
</div>
</div>
<!-- Recent Searches -->
<div class="sidebar-section">
<h3>Recent</h3>
<div class="recent-list" id="recent-searches"
hx-get="/api/research/recent"
hx-trigger="load"
hx-swap="innerHTML">
<!-- Recent searches loaded here -->
</div>
</div>
<!-- Prompts Library -->
<div class="sidebar-section">
<div class="section-header">
<h3>Prompts</h3>
<button class="btn-icon-sm" title="Browse All"
hx-get="/api/research/prompts"
hx-target="#main-results">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
</div>
<div class="prompts-grid">
<button class="prompt-chip" data-prompt="explain">Explain</button>
<button class="prompt-chip" data-prompt="compare">Compare</button>
<button class="prompt-chip" data-prompt="summarize">Summarize</button>
<button class="prompt-chip" data-prompt="analyze">Analyze</button>
<button class="prompt-chip" data-prompt="pros-cons">Pros & Cons</button>
<button class="prompt-chip" data-prompt="how-to">How to</button>
</div>
</div>
<!-- Sources Categories -->
<div class="sidebar-section">
<h3>Sources</h3>
<div class="sources-categories">
<button class="source-category" data-category="all">
<span class="category-icon">📚</span>
<span class="category-name">All Sources</span>
<span class="category-count" id="count-all">0</span>
</button>
<button class="source-category" data-category="web">
<span class="category-icon">🌐</span>
<span class="category-name">Web</span>
<span class="category-count" id="count-web">0</span>
</button>
<button class="source-category" data-category="docs">
<span class="category-icon">📄</span>
<span class="category-name">Documents</span>
<span class="category-count" id="count-docs">0</span>
</button>
<button class="source-category" data-category="knowledge">
<span class="category-icon">🧠</span>
<span class="category-name">Knowledge Base</span>
<span class="category-count" id="count-kb">0</span>
</button>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="research-main">
<!-- Search Header -->
<div class="search-header">
<form class="search-form" id="research-form"
hx-post="/api/research/search"
hx-target="#main-results"
hx-indicator="#search-indicator">
<div class="search-input-wrapper">
<svg class="search-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<textarea
name="query"
id="search-input"
placeholder="Ask anything..."
rows="1"
autofocus></textarea>
<input type="hidden" name="focus" id="focus-mode" value="all">
<input type="hidden" name="pro" id="pro-mode" value="false">
</div>
<div class="search-options">
<label class="option-toggle">
<input type="checkbox" id="pro-search-toggle">
<span class="toggle-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
</svg>
Pro Search
</span>
</label>
<label class="option-toggle">
<input type="checkbox" id="include-images-toggle">
<span class="toggle-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
Images
</span>
</label>
<button type="submit" class="search-btn">
<span>Search</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
</svg>
</button>
</div>
</form>
<!-- Search Indicator -->
<div class="search-indicator htmx-indicator" id="search-indicator">
<div class="indicator-dots">
<span></span><span></span><span></span>
</div>
<span class="indicator-text">Searching sources...</span>
</div>
</div>
<!-- Suggestions (shown when no search) -->
<div class="suggestions-panel" id="suggestions-panel">
<h3>Try asking about</h3>
<div class="suggestion-cards">
<button class="suggestion-card" data-query="What are the latest developments in AI?">
<span class="suggestion-icon">🤖</span>
<span class="suggestion-text">Latest AI developments</span>
</button>
<button class="suggestion-card" data-query="Explain quantum computing in simple terms">
<span class="suggestion-icon">⚛️</span>
<span class="suggestion-text">Quantum computing explained</span>
</button>
<button class="suggestion-card" data-query="Best practices for software architecture">
<span class="suggestion-icon">🏗️</span>
<span class="suggestion-text">Software architecture</span>
</button>
<button class="suggestion-card" data-query="How to improve productivity with AI tools">
<span class="suggestion-icon">📈</span>
<span class="suggestion-text">AI productivity tools</span>
</button>
</div>
<!-- Trending Topics -->
<div class="trending-section">
<h3>Trending</h3>
<div class="trending-tags" id="trending-tags"
hx-get="/api/research/trending"
hx-trigger="load"
hx-swap="innerHTML">
<!-- Trending tags loaded here -->
</div>
</div>
</div>
<!-- Results Area -->
<div class="results-container" id="main-results">
<!-- Results loaded here via HTMX -->
</div>
</main>
<!-- Sources Panel (Right) -->
<aside class="sources-panel hidden" id="sources-panel">
<div class="sources-header">
<h3>Sources</h3>
<div class="sources-actions">
<button class="btn-icon-sm" title="Export Citations"
hx-get="/api/research/export-citations"
hx-swap="none">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
</button>
<button class="btn-icon-sm" id="close-sources">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
<div class="sources-list" id="sources-list">
<!-- Sources loaded here -->
</div>
</aside>
</div>
<!-- Result Template (for HTMX responses) -->
<template id="result-template">
<div class="result-card">
<div class="result-answer">
<div class="answer-content" id="answer-content">
<!-- AI-generated answer with citations -->
</div>
<div class="answer-actions">
<button class="action-btn" title="Copy">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
</svg>
</button>
<button class="action-btn" title="Save to Collection">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"></path>
</svg>
</button>
<button class="action-btn" title="Share">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="18" cy="5" r="3"></circle>
<circle cx="6" cy="12" r="3"></circle>
<circle cx="18" cy="19" r="3"></circle>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
</svg>
</button>
<button class="action-btn" title="Export to Paper">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
</button>
</div>
</div>
<!-- Related Questions -->
<div class="related-questions">
<h4>Related</h4>
<div class="related-list" id="related-questions">
<!-- Related questions loaded here -->
</div>
</div>
<!-- Sources Preview -->
<div class="sources-preview">
<div class="sources-preview-header">
<h4>Sources</h4>
<button class="btn-text" id="view-all-sources">View all</button>
</div>
<div class="sources-preview-list" id="sources-preview">
<!-- Top sources shown here -->
</div>
</div>
</div>
</template>
<style>
/* Research Container */
.research-container {
display: flex;
height: calc(100vh - 60px);
background: var(--background);
color: var(--foreground);
}
/* Sidebar */
.research-sidebar {
width: 280px;
border</style>-right: 1px solid var(--border);
background: var(--card);
display: flex;
flex-direction: column;
overflow-y: auto;
transition: width 0.2s ease;
}
.research-sidebar.collapsed {
width: 60px;
}
.research-sidebar.collapsed .sidebar-header h2,
.research-sidebar.collapsed .section-header h3,
.research-sidebar.collapsed .sidebar-section h3,
.research-sidebar.collapsed .focus-btn span,
.research-sidebar.collapsed .prompts-grid,
.research-sidebar.collapsed .collections-list,
.research-sidebar.collapsed .recent-list,
.research-sidebar.collapsed .category-name,
.research-sidebar.collapsed .category-count {
display: none;
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid var(--border);
}
.sidebar-header h2 {
font-size: 18px;
font-weight: 600;
margin: 0;
}
/* Focus Modes */
.focus-modes {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 12px;
border-bottom: 1px solid var(--border);
}
.focus-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border: 1px solid var(--border);
border-radius: 20px;
background: var(--background);
color: var(--foreground);
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
}
.focus-btn:hover {
background: var(--accent);
}
.focus-btn.active {
background: var(--primary);
color: var(--primary-foreground);
border-color: var(--primary);
}
.research-sidebar.collapsed .focus-btn {
padding: 8px;
justify-content: center;
}
/* Sidebar Sections */
.sidebar-section {
padding: 16px;
border-bottom: 1px solid var(--border);
}
.sidebar-section h3 {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
color: var(--muted-foreground);
margin: 0 0 12px 0;
letter-spacing: 0.5px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.section-header h3 {
margin: 0;
}
.btn-icon-sm {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border: none;
border-radius: 4px;
background: transparent;
color: var(--muted-foreground);
cursor: pointer;
}
.btn-icon-sm:hover {
background: var(--accent);
color: var(--foreground);
}
/* Collections */
.collections-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.collection-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 6px;
background: transparent;
border: none;
color: var(--foreground);
font-size: 13px;
text-align: left;
cursor: pointer;
transition: background 0.15s;
}
.collection-item:hover {
background: var(--accent);
}
.collection-icon {
font-size: 14px;
}
/* Recent Searches */
.recent-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.recent-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px;
border-radius: 4px;
background: transparent;
border: none;
color: var(--muted-foreground);
font-size: 12px;
text-align: left;
cursor: pointer;
transition: all 0.15s;
}
.recent-item:hover {
background: var(--accent);
color: var(--foreground);
}
/* Prompts */
.prompts-grid {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.prompt-chip {
padding: 6px 10px;
border: 1px solid var(--border);
border-radius: 14px;
background: var(--background);
color: var(--foreground);
font-size: 11px;
cursor: pointer;
transition: all 0.15s;
}
.prompt-chip:hover {
background: var(--primary);
color: var(--primary-foreground);
border-color: var(--primary);
}
/* Source Categories */
.sources-categories {
display: flex;
flex-direction: column;
gap: 4px;
}
.source-category {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--foreground);
font-size: 13px;
text-align: left;
cursor: pointer;
transition: background 0.15s;
}
.source-category:hover {
background: var(--accent);
}
.category-icon {
font-size: 16px;
}
.category-name {
flex: 1;
}
.category-count {
font-size: 11px;
padding: 2px 6px;
border-radius: 10px;
background: var(--muted);
color: var(--muted-foreground);
}
/* Main Content */
.research-main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Search Header */
.search-header {
padding: 24px 32px;
border-bottom: 1px solid var(--border);
background: var(--card);
}
.search-form {
max-width: 800px;
margin: 0 auto;
}
.search-input-wrapper {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 16px 20px;
border: 2px solid var(--border);
border-radius: 12px;
background: var(--background);
transition: border-color 0.2s;
}
.search-input-wrapper:focus-within {
border-color: var(--primary);
}
.search-icon {
color: var(--muted-foreground);
margin-top: 2px;
flex-shrink: 0;
}
.search-input-wrapper textarea {
flex: 1;
border: none;
background: transparent;
color: var(--foreground);
font-size: 16px;
line-height: 1.5;
resize: none;
outline: none;
min-height: 24px;
max-height: 120px;
}
.search-input-wrapper textarea::placeholder {
color: var(--muted-foreground);
}
.search-options {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 12px;
padding: 0 4px;
}
.option-toggle {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.option-toggle input {
display: none;
}
.toggle-label {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 16px;
background: var(--muted);
color: var(--muted-foreground);
font-size: 12px;
transition: all 0.15s;
}
.option-toggle input:checked + .toggle-label {
background: var(--primary);
color: var(--primary-foreground);
}
.search-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
border: none;
border-radius: 8px;
background: var(--primary);
color: var(--primary-foreground);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.search-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* Search Indicator */
.search-indicator {
display: none;
align-items: center;
justify-content: center;
gap: 12px;
padding: 16px;
margin-top: 16px;
}
.search-indicator.htmx-request {
display: flex;
}
.indicator-dots {
display: flex;
gap: 4px;
}
.indicator-dots span {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--primary);
animation: bounce 1.4s infinite ease-in-out both;
}
.indicator-dots span:nth-child(1) { animation-delay: -0.32s; }
.indicator-dots span:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
.indicator-text {
color: var(--muted-foreground);
font-size: 14px;
}
/* Suggestions Panel */
.suggestions-panel {
flex: 1;
padding: 40px 32px;
overflow-y: auto;
max-width: 900px;
margin: 0 auto;
width: 100%;
}
.suggestions-panel.hidden {
display: none;
}
.suggestions-panel h3 {
font-size: 14px;
font-weight: 600;
color: var(--muted-foreground);
margin: 0 0 16px 0;
}
.suggestion-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
margin-bottom: 32px;
}
.suggestion-card {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--card);
color: var(--foreground);
text-align: left;
cursor: pointer;
transition: all 0.2s;
}
.suggestion-card:hover {
border-color: var(--primary);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.suggestion-icon {
font-size: 24px;
}
.suggestion-text {
font-size: 14px;
font-weight: 500;
}
/* Trending */
.trending-section {
margin-top: 24px;
}
.trending-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.trending-tag {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: 1px solid var(--border);
border-radius: 20px;
background: var(--background);
color: var(--foreground);
font-size: 13px;
cursor: pointer;
transition: all 0.15s;
}
.trending-tag:hover {
background: var(--accent);
border-color: var(--primary);
}
.trending-tag .trend-icon {
color: var(--chart-1);
}
/* Results Container */
.results-container {
flex: 1;
overflow-y: auto;
padding: 24px 32px;
}
.results-container:empty + .suggestions-panel {
display: block;
}
.results-container:not(:empty) + .suggestions-panel {
display: none;
}
/* Result Card */
.result-card {
max-width: 900px;
margin: 0 auto;
}
.result-answer {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
}
.answer-content {
font-size: 15px;
line-height: 1.8;
}
.answer-content h1,
.answer-content h2,
.answer-content h3 {
margin-top: 24px;
margin-bottom: 12px;
}
.answer-content p {
margin: 12px 0;
}
.answer-content ul,
.answer-content ol {
padding-left: 24px;
margin: 12px 0;
}
.answer-content code {
background: var(--muted);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
font-size: 13px;
}
.answer-content pre {
background: var(--muted);
padding: 16px;
border-radius: 8px;
overflow-x: auto;
margin: 16px 0;
}
/* Citations */
.citation {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
margin: 0 2px;
border-radius: 4px;
background: var(--primary);
color: var(--primary-foreground);
font-size: 10px;
font-weight: 600;
cursor: pointer;
vertical-align: super;
transition: transform 0.15s;
}
.citation:hover {
transform: scale(1.1);
}
/* Answer Actions */
.answer-actions {
display: flex;
gap: 8px;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--border);
}
.action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--background);
color: var(--muted-foreground);
cursor: pointer;
transition: all 0.15s;
}
.action-btn:hover {
background: var(--accent);
color: var(--foreground);
border-color: var(--primary);
}
/* Related Questions */
.related-questions {
margin-bottom: 24px;
}
.related-questions h4 {
font-size: 14px;
font-weight: 600;
color: var(--muted-foreground);
margin: 0 0 12px 0;
}
.related-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.related-item {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--card);
color: var(--foreground);
font-size: 14px;
text-align: left;
cursor: pointer;
transition: all 0.15s;
}
.related-item:hover {
background: var(--accent);
border-color: var(--primary);
}
.related-item svg {
color: var(--primary);
flex-shrink: 0;
}
/* Sources Preview */
.sources-preview {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
}
.sources-preview-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.sources-preview-header h4 {
font-size: 14px;
font-weight: 600;
margin: 0;
}
.btn-text {
background: none;
border: none;
color: var(--primary);
font-size: 13px;
cursor: pointer;
}
.btn-text:hover {
text-decoration: underline;
}
.sources-preview-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 12px;
}
.source-card {
display: flex;
gap: 12px;
padding: 12px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--background);
cursor: pointer;
transition: all 0.15s;
}
.source-card:hover {
border-color: var(--primary);
background: var(--accent);
}
.source-number {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 4px;
background: var(--muted);
color: var(--foreground);
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
}
.source-info {
flex: 1;
min-width: 0;
}
.source-title {
font-size: 13px;
font-weight: 500;
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.source-domain {
font-size: 11px;
color: var(--muted-foreground);
display: flex;
align-items: center;
gap: 4px;
}
.source-favicon {
width: 12px;
height: 12px;
border-radius: 2px;
}
/* Sources Panel (Right) */
.sources-panel {
width: 360px;
border-left: 1px solid var(--border);
background: var(--card);
display: flex;
flex-direction: column;
transition: transform 0.2s ease;
}
.sources-panel.hidden {
display: none;
}
.sources-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid var(--border);
}
.sources-header h3 {
font-size: 16px;
font-weight: 600;
margin: 0;
}
.sources-actions {
display: flex;
gap: 4px;
}
.sources-list {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.source-detail-card {
padding: 16px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--background);
margin-bottom: 12px;
}
.source-detail-header {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 12px;
}
.source-detail-number {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 6px;
background: var(--primary);
color: var(--primary-foreground);
font-size: 13px;
font-weight: 600;
flex-shrink: 0;
}
.source-detail-title {
font-size: 14px;
font-weight: 600;
line-height: 1.4;
margin-bottom: 4px;
}
.source-detail-url {
font-size: 12px;
color: var(--primary);
text-decoration: none;
}
.source-detail-url:hover {
text-decoration: underline;
}
.source-detail-snippet {
font-size: 13px;
color: var(--muted-foreground);
line-height: 1.6;
margin-bottom: 12px;
}
.source-detail-actions {
display: flex;
gap: 8px;
}
.source-action-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--background);
color: var(--foreground);
font-size: 11px;
cursor: pointer;
transition: all 0.15s;
}
.source-action-btn:hover {
background: var(--accent);
border-color: var(--primary);
}
/* Responsive */
@media (max-width: 1024px) {
.sources-panel {
position: absolute;
right: 0;
top: 60px;
bottom: 0;
z-index: 50;
}
}
@media (max-width: 768px) {
.research-sidebar {
position: absolute;
left: 0;
top: 0;
bottom: 0;
z-index: 60;
transform: translateX(-100%);
}
.research-sidebar.open {
transform: translateX(0);
}
.search-header {
padding: 16px;
}
.search-input-wrapper {
padding: 12px 16px;
}
.suggestions-panel {
padding: 20px 16px;
}
.suggestion-cards {
grid-template-columns: 1fr;
}
.sources-preview-list {
grid-template-columns: 1fr;
}
.sources-panel {
width: 100%;
}
}
</style>
<script>
(function() {
const searchInput = document.getElementById('search-input');
const suggestionsPanel = document.getElementById('suggestions-panel');
const resultsContainer = document.getElementById('main-results');
const sourcesPanel = document.getElementById('sources-panel');
const sidebar = document.getElementById('research-sidebar');
// Auto-resize textarea
searchInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
// Handle Enter to submit (Shift+Enter for new line)
searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
document.getElementById('research-form').requestSubmit();
}
});
// Focus modes
document.querySelectorAll('.focus-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.focus-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
document.getElementById('focus-mode').value = this.dataset.focus;
});
});
// Pro search toggle
document.getElementById('pro-search-toggle').addEventListener('change', function() {
document.getElementById('pro-mode').value = this.checked;
});
// Suggestion cards
document.querySelectorAll('.suggestion-card').forEach(card => {
card.addEventListener('click', function() {
searchInput.value = this.dataset.query;
searchInput.style.height = 'auto';
searchInput.style.height = searchInput.scrollHeight + 'px';
document.getElementById('research-form').requestSubmit();
});
});
// Prompt chips
document.querySelectorAll('.prompt-chip').forEach(chip => {
chip.addEventListener('click', function() {
const prompt = this.dataset.prompt;
const currentValue = searchInput.value.trim();
const prefixes = {
'explain': 'Explain ',
'compare': 'Compare ',
'summarize': 'Summarize ',
'analyze': 'Analyze ',
'pros-cons': 'What are the pros and cons of ',
'how-to': 'How to '
};
searchInput.value = prefixes[prompt] + currentValue;
searchInput.focus();
});
});
// Toggle sidebar
document.getElementById('toggle-research-sidebar').addEventListener('click', function() {
sidebar.classList.toggle('collapsed');
});
// View all sources
document.addEventListener('click', function(e) {
if (e.target.id === 'view-all-sources' || e.target.closest('#view-all-sources')) {
sourcesPanel.classList.remove('hidden');
}
});
// Close sources panel
document.getElementById('close-sources').addEventListener('click', function() {
sourcesPanel.classList.add('hidden');
});
// Citation click handler
document.addEventListener('click', function(e) {
if (e.target.classList.contains('citation')) {
const sourceNum = e.target.textContent;
sourcesPanel.classList.remove('hidden');
// Scroll to source in panel
const sourceCard = sourcesPanel.querySelector(`[data-source="${sourceNum}"]`);
if (sourceCard) {
sourceCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
sourceCard.classList.add('highlight');
setTimeout(() => sourceCard.classList.remove('highlight'), 2000);
}
}
});
// Related question click
document.addEventListener('click', function(e) {
const relatedItem = e.target.closest('.related-item');
if (relatedItem) {
searchInput.value = relatedItem.textContent.trim();
document.getElementById('research-form').requestSubmit();
window.scrollTo({ top: 0, behavior: 'smooth' });
}
});
// Trending tag click
document.addEventListener('click', function(e) {
const trendingTag = e.target.closest('.trending-tag');
if (trendingTag) {
searchInput.value = trendingTag.dataset.query || trendingTag.textContent.trim();
document.getElementById('research-form').requestSubmit();
}
});
// Copy answer
document.addEventListener('click', function(e) {
const copyBtn = e.target.closest('.action-btn[title="Copy"]');
if (copyBtn) {
const content = document.getElementById('answer-content');
if (content) {
navigator.clipboard.writeText(content.innerText);
// Show feedback
const originalTitle = copyBtn.title;
copyBtn.title = 'Copied!';
setTimeout(() => copyBtn.title = originalTitle, 2000);
}
}
});
// Export to Paper
document.addEventListener('click', function(e) {
const exportBtn = e.target.closest('.action-btn[title="Export to Paper"]');
if (exportBtn) {
const content = document.getElementById('answer-content');
if (content) {
// Send to Paper via HTMX
htmx.ajax('POST', '/api/paper/import', {
values: {
content: content.innerHTML,
title: searchInput.value
}
}).then(() => {
// Navigate to Paper
window.location.hash = '#paper';
});
}
}
});
// Source category click
document.querySelectorAll('.source-category').forEach(cat => {
cat.addEventListener('click', function() {
const category = this.dataset.category;
htmx.ajax('GET', `/api/research/sources?category=${category}`, {
target: '#sources-list'
});
sourcesPanel.classList.remove('hidden');
});
});
// Handle search results display
htmx.on('#main-results', 'htmx:afterSwap', function() {
// Hide suggestions when results are shown
suggestionsPanel.classList.add('hidden');
// Update source counts
updateSourceCounts();
});
function updateSourceCounts() {
// This would typically come from the API response
// For now, we'll use placeholder logic
htmx.ajax('GET', '/api/research/source-counts', {
swap: 'none'
}).then(response => {
// Update counts in sidebar
});
}
// Collection item click
document.addEventListener('click', function(e) {
const collectionItem = e.target.closest('.collection-item');
if (collectionItem) {
const collectionId = collectionItem.dataset.id;
htmx.ajax('GET', `/api/research/collections/${collectionId}`, {
target: '#main-results'
});
}
});
// Recent item click
document.addEventListener('click', function(e) {
const recentItem = e.target.closest('.recent-item');
if (recentItem) {
searchInput.value = recentItem.dataset.query;
document.getElementById('research-form').requestSubmit();
}
});
// Mobile sidebar toggle
window.toggleResearchSidebar = function() {
sidebar.classList.toggle('open');
};
})();
</script>