Add help view and core memory styles

- Introduced styles for the help view including tab transitions, accordion animations, search highlighting, and responsive design.
- Implemented core memory styles with modal base styles, memory card designs, and knowledge graph visualization.
- Enhanced dark mode support across various components.
- Added loading states and empty state designs for better user experience.
This commit is contained in:
catlog22
2025-12-18 18:29:45 +08:00
parent 4577be71ce
commit 17af615fe2
46 changed files with 4941 additions and 4326 deletions

View File

@@ -2,5 +2,5 @@
- **CLI Tools Usage**: @~/.claude/workflows/cli-tools-usage.md - **CLI Tools Usage**: @~/.claude/workflows/cli-tools-usage.md
- **Coding Philosophy**: @~/.claude/workflows/coding-philosophy.md - **Coding Philosophy**: @~/.claude/workflows/coding-philosophy.md
- **Context Requirements**: @~/.claude/workflows/context-requirements.md - **Context Requirements**: @~/.claude/workflows/context-tools.md
- **File Modification**: @~/.claude/workflows/file-modification.md - **File Modification**: @~/.claude/workflows/file-modification.md

View File

@@ -61,3 +61,10 @@
- **Reference, don't duplicate** - point to other layers, never copy content - **Reference, don't duplicate** - point to other layers, never copy content
- **Maintain perspective** - each layer sees the system at its appropriate scale - **Maintain perspective** - each layer sees the system at its appropriate scale
- **Avoid implementation creep** - higher layers stay architectural - **Avoid implementation creep** - higher layers stay architectural
# Context Requirements
Before implementation, always:
- Identify 3+ existing similar patterns
- Map dependencies and integration points
- Understand testing framework and coding conventions

View File

@@ -1,10 +1,3 @@
# Context Requirements
Before implementation, always:
- Identify 3+ existing similar patterns
- Map dependencies and integration points
- Understand testing framework and coding conventions
## MCP Tools Usage ## MCP Tools Usage
### smart_search - Code Search (REQUIRED) ### smart_search - Code Search (REQUIRED)

View File

@@ -24,15 +24,29 @@ const MODULE_CSS_FILES = [
'07-managers.css', '07-managers.css',
'08-review.css', '08-review.css',
'09-explorer.css', '09-explorer.css',
'10-cli.css', // CLI modules (split from 10-cli.css)
'11-memory.css', '10-cli-status.css',
'11-prompt-history.css', '11-cli-history.css',
'12-skills-rules.css', '12-cli-legacy.css',
'13-claude-manager.css', '13-cli-ccw.css',
'14-graph-explorer.css', '14-cli-modals.css',
'15-mcp-manager.css', '15-cli-endpoints.css',
'16-help.css', '16-cli-session.css',
'17-core-memory.css' '17-cli-conversation.css',
'18-cli-settings.css',
'19-cli-native-session.css',
'20-cli-taskqueue.css',
'21-cli-toolmgmt.css',
'22-cli-semantic.css',
// Other modules
'23-memory.css',
'24-prompt-history.css',
'25-skills-rules.css',
'26-claude-manager.css',
'27-graph-explorer.css',
'28-mcp-manager.css',
'29-help.css',
'30-core-memory.css'
]; ];
const MODULE_FILES = [ const MODULE_FILES = [

View File

@@ -61,15 +61,29 @@ const MODULE_CSS_FILES = [
'07-managers.css', '07-managers.css',
'08-review.css', '08-review.css',
'09-explorer.css', '09-explorer.css',
'10-cli.css', // CLI modules (split from 10-cli.css)
'11-memory.css', '10-cli-status.css',
'11-prompt-history.css', '11-cli-history.css',
'12-skills-rules.css', '12-cli-legacy.css',
'13-claude-manager.css', '13-cli-ccw.css',
'14-graph-explorer.css', '14-cli-modals.css',
'15-mcp-manager.css', '15-cli-endpoints.css',
'16-help.css', '16-cli-session.css',
'17-core-memory.css' '17-cli-conversation.css',
'18-cli-settings.css',
'19-cli-native-session.css',
'20-cli-taskqueue.css',
'21-cli-toolmgmt.css',
'22-cli-semantic.css',
// Other modules
'23-memory.css',
'24-prompt-history.css',
'25-skills-rules.css',
'26-claude-manager.css',
'27-graph-explorer.css',
'28-mcp-manager.css',
'29-help.css',
'30-core-memory.css'
]; ];
// Modular JS files in dependency order // Modular JS files in dependency order

View File

@@ -0,0 +1,337 @@
/* ========================================
* CLI Manager Styles
* Unified font: system-ui for UI, monospace for code
* ======================================== */
/* ========================================
* Status Manager - Two Column Layout
* ======================================== */
.status-manager {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.status-two-column {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
align-items: start;
}
@media (max-width: 1024px) {
.status-two-column {
grid-template-columns: 1fr;
}
}
/* Section Container */
.status-section {
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.75rem;
overflow: hidden;
}
/* CLI Section - No card wrapper */
.cli-section {
/* No background, border, or card styling */
}
.cli-section .section-header {
padding: 0 0 0.75rem 0;
border-bottom: none;
background: transparent;
}
.cli-section .section-header h3 {
font-size: 0.9375rem;
}
.cli-section .tools-list,
.cli-section .ccw-list,
.cli-section .endpoint-tools-grid {
padding: 0;
}
/* Section Header */
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.875rem 1rem;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--muted) / 0.3);
}
.section-header-left {
display: flex;
align-items: center;
gap: 0.75rem;
}
.section-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0;
}
.section-header h3 i {
color: hsl(var(--muted-foreground));
}
.section-count {
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
}
.section-header-actions {
display: flex;
align-items: center;
gap: 0.25rem;
}
/* Tools List */
.tools-list {
padding: 0.5rem;
}
.tool-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem;
border-radius: 0.5rem;
margin-bottom: 0.375rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
transition: all 0.15s ease;
}
.tool-item:last-child {
margin-bottom: 0;
}
.tool-item:hover {
background: hsl(var(--hover));
border-color: hsl(var(--primary) / 0.3);
}
.tool-item.available {
/* No left border - use status dot instead */
}
.tool-item.unavailable {
opacity: 0.7;
}
.tool-item.endpoint {
/* No left border */
}
.tool-item-left {
display: flex;
align-items: center;
gap: 0.75rem;
}
.tool-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.tool-status-dot.status-available {
background: hsl(var(--success));
box-shadow: 0 0 6px hsl(var(--success) / 0.5);
}
.tool-status-dot.status-unavailable {
background: hsl(var(--muted-foreground) / 0.4);
}
.tool-item-info {
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.tool-item-name {
font-size: 0.8125rem;
font-weight: 600;
color: hsl(var(--foreground));
display: flex;
align-items: center;
gap: 0.5rem;
}
.tool-item-desc {
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
}
.tool-default-badge {
font-size: 0.5625rem;
font-weight: 600;
padding: 0.125rem 0.375rem;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.tool-type-badge {
font-size: 0.5625rem;
font-weight: 500;
padding: 0.125rem 0.375rem;
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
border-radius: 0.25rem;
}
.tool-type-badge.ai {
background: hsl(var(--primary) / 0.15);
color: hsl(var(--primary));
}
.tool-type-badge.llm {
background: hsl(142 76% 36% / 0.15);
color: hsl(142 76% 36%);
}
.tool-item-right {
display: flex;
align-items: center;
gap: 0.5rem;
}
.tool-status-text {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.6875rem;
font-weight: 500;
}
.tool-status-text.success {
color: hsl(var(--success));
}
.tool-status-text.muted {
color: hsl(var(--muted-foreground));
}
/* CCW List */
.ccw-list {
padding: 0.5rem;
}
.ccw-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 0.75rem;
border-radius: 0.5rem;
margin-bottom: 0.375rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
transition: all 0.15s ease;
}
.ccw-item:last-child {
margin-bottom: 0;
}
.ccw-item:hover {
background: hsl(var(--hover));
border-color: hsl(var(--primary) / 0.3);
}
.ccw-item-left {
display: flex;
align-items: flex-start;
gap: 0.75rem;
flex: 1;
min-width: 0;
}
.ccw-item-mode {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 0.5rem;
flex-shrink: 0;
}
.ccw-item-mode.global {
background: hsl(var(--primary) / 0.1);
color: hsl(var(--primary));
}
.ccw-item-mode.path {
background: hsl(var(--warning) / 0.1);
color: hsl(var(--warning));
}
.ccw-item-info {
flex: 1;
min-width: 0;
}
.ccw-item-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.25rem;
}
.ccw-item-name {
font-size: 0.8125rem;
font-weight: 600;
color: hsl(var(--foreground));
}
.ccw-item-path {
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
background: hsl(var(--muted) / 0.5);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
margin-bottom: 0.375rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ccw-item-meta {
display: flex;
gap: 0.75rem;
font-size: 0.625rem;
color: hsl(var(--muted-foreground));
}
.ccw-item-meta span {
display: flex;
align-items: center;
gap: 0.25rem;
}
.ccw-item-actions {
display: flex;
align-items: center;
gap: 0.25rem;
opacity: 0;
transition: opacity 0.15s ease;
}
.ccw-item:hover .ccw-item-actions {
opacity: 1;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,271 @@
/* ========================================
* History View Styles
* ======================================== */
.history-view {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.history-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 0;
margin-bottom: 1rem;
border-bottom: 1px solid hsl(var(--border));
}
.history-header-left {
display: flex;
align-items: center;
gap: 0.75rem;
}
.history-count {
font-size: 0.8125rem;
color: hsl(var(--muted-foreground));
}
.history-header-right {
display: flex;
align-items: center;
gap: 0.5rem;
}
.history-search-wrapper {
position: relative;
display: flex;
align-items: center;
}
.history-search-wrapper i {
position: absolute;
left: 0.625rem;
color: hsl(var(--muted-foreground));
pointer-events: none;
}
.history-search-input {
padding: 0.5rem 0.75rem 0.5rem 2rem;
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
background: hsl(var(--background));
color: hsl(var(--foreground));
font-size: 0.8125rem;
width: 220px;
transition: all 0.2s ease;
}
.history-search-input:focus {
outline: none;
border-color: hsl(var(--primary));
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.15);
width: 260px;
}
.history-search-input::placeholder {
color: hsl(var(--muted-foreground) / 0.7);
}
.history-filter-select {
padding: 0.5rem 0.75rem;
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
background: hsl(var(--background));
color: hsl(var(--foreground));
font-size: 0.8125rem;
cursor: pointer;
transition: all 0.2s ease;
}
.history-filter-select:hover {
border-color: hsl(var(--primary) / 0.5);
}
.history-filter-select:focus {
outline: none;
border-color: hsl(var(--primary));
}
/* History List */
.history-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.history-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 1rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.15s ease;
}
.history-item:hover {
background: hsl(var(--hover));
border-color: hsl(var(--primary) / 0.3);
box-shadow: 0 2px 8px hsl(var(--foreground) / 0.05);
}
.history-item:hover .history-item-actions {
opacity: 1;
}
.history-item-main {
flex: 1;
min-width: 0;
}
.history-item-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
flex-wrap: wrap;
}
.history-tool-tag {
font-size: 0.625rem;
font-weight: 600;
padding: 0.1875rem 0.5rem;
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.history-tool-tag.tool-gemini {
background: hsl(210 80% 55% / 0.12);
color: hsl(210 80% 45%);
}
.history-tool-tag.tool-qwen {
background: hsl(280 70% 55% / 0.12);
color: hsl(280 70% 45%);
}
.history-tool-tag.tool-codex {
background: hsl(142 71% 45% / 0.12);
color: hsl(142 71% 35%);
}
.history-tool-tag.tool-claude {
background: hsl(25 90% 50% / 0.12);
color: hsl(25 90% 40%);
}
.history-mode-tag {
font-size: 0.625rem;
font-weight: 500;
padding: 0.1875rem 0.5rem;
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
border-radius: 0.25rem;
}
.history-source-dir {
font-size: 0.625rem;
font-weight: 500;
padding: 0.1875rem 0.5rem;
background: hsl(var(--accent));
color: hsl(var(--accent-foreground));
border-radius: 0.25rem;
display: inline-flex;
align-items: center;
gap: 0.25rem;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.history-status {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.625rem;
font-weight: 500;
padding: 0.1875rem 0.5rem;
border-radius: 9999px;
}
.history-status.success {
background: hsl(var(--success) / 0.12);
color: hsl(var(--success));
}
.history-status.warning {
background: hsl(var(--warning) / 0.12);
color: hsl(var(--warning));
}
.history-status.error {
background: hsl(var(--destructive) / 0.12);
color: hsl(var(--destructive));
}
.history-item-prompt {
font-size: 0.875rem;
font-weight: 450;
color: hsl(var(--foreground));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
line-height: 1.5;
margin-bottom: 0.5rem;
}
.history-item-meta {
display: flex;
align-items: center;
gap: 1rem;
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
}
.history-item-meta span {
display: flex;
align-items: center;
gap: 0.25rem;
}
.history-item-actions {
display: flex;
align-items: center;
gap: 0.25rem;
opacity: 0;
transition: opacity 0.15s ease;
margin-left: 0.75rem;
}
/* History Empty State */
.history-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 2rem;
text-align: center;
color: hsl(var(--muted-foreground));
}
.history-empty-state i {
opacity: 0.3;
margin-bottom: 1rem;
}
.history-empty-state h3 {
font-size: 1rem;
font-weight: 600;
color: hsl(var(--foreground));
margin-bottom: 0.5rem;
}
.history-empty-state p {
font-size: 0.8125rem;
}

View File

@@ -0,0 +1,796 @@
/* ========================================
* Legacy Container Styles (kept for compatibility)
* ======================================== */
/* Container */
.cli-manager-container {
display: flex;
flex-direction: column;
gap: 1.25rem;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.cli-manager-grid {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 1.25rem;
align-items: start;
}
@media (max-width: 768px) {
.cli-manager-grid {
grid-template-columns: 1fr;
}
}
/* Panels */
.cli-panel {
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 1px 3px hsl(var(--foreground) / 0.04);
transition: box-shadow 0.2s ease;
}
.cli-panel:hover {
box-shadow: 0 4px 12px hsl(var(--foreground) / 0.08);
}
.cli-panel-full {
grid-column: 1 / -1;
}
/* Status Panel */
.cli-status-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.625rem 0.75rem;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--muted) / 0.3);
}
.cli-status-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.8125rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0;
letter-spacing: -0.01em;
}
.cli-status-header h3 i {
color: hsl(var(--muted-foreground));
}
.cli-tools-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 0.5rem;
padding: 0.5rem 0.625rem;
}
.cli-tool-card {
padding: 0.625rem 0.5rem;
border-radius: 0.5rem;
background: hsl(var(--background));
text-align: center;
border: 1.5px solid hsl(var(--border));
transition: all 0.2s ease;
}
.cli-tool-card.available {
background: hsl(var(--background));
}
.cli-tool-card.available:hover {
box-shadow: 0 2px 8px hsl(var(--foreground) / 0.08);
}
/* Tool-specific border colors */
.cli-tool-card.tool-gemini.available {
border-color: hsl(210 80% 55% / 0.5);
}
.cli-tool-card.tool-gemini.available:hover {
border-color: hsl(210 80% 55% / 0.7);
}
.cli-tool-card.tool-qwen.available {
border-color: hsl(280 70% 55% / 0.5);
}
.cli-tool-card.tool-qwen.available:hover {
border-color: hsl(280 70% 55% / 0.7);
}
.cli-tool-card.tool-codex.available {
border-color: hsl(142 71% 45% / 0.5);
}
.cli-tool-card.tool-codex.available:hover {
border-color: hsl(142 71% 45% / 0.7);
}
.cli-tool-card.tool-claude.available {
border-color: hsl(25 90% 50% / 0.5);
}
.cli-tool-card.tool-claude.available:hover {
border-color: hsl(25 90% 50% / 0.7);
}
.cli-tool-card.unavailable {
border-color: hsl(var(--border));
opacity: 0.6;
}
.cli-tool-header {
display: flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
margin-bottom: 0.1875rem;
}
.cli-tool-status {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.cli-tool-status.status-available {
background: hsl(var(--success));
box-shadow: 0 0 6px hsl(var(--success) / 0.5);
}
.cli-tool-status.status-unavailable {
background: hsl(var(--muted-foreground) / 0.5);
}
.cli-tool-name {
font-weight: 600;
font-size: 0.8125rem;
color: hsl(var(--foreground));
letter-spacing: -0.01em;
}
.cli-tool-badge {
font-size: 0.5625rem;
font-weight: 600;
padding: 0.125rem 0.375rem;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.cli-tool-info {
font-size: 0.6875rem;
margin-bottom: 0.3125rem;
color: hsl(var(--muted-foreground));
}
.cli-tool-info .text-success {
color: hsl(var(--success));
font-weight: 500;
}
/* CLI Tool Description */
.cli-tool-desc {
font-size: 0.625rem;
color: hsl(var(--muted-foreground) / 0.8);
line-height: 1.3;
}
/* CLI Tool Actions */
.cli-tool-actions {
min-height: 1.75rem;
}
/* CodexLens specific styles */
.cli-tool-card.tool-codexlens.available {
border-color: hsl(35 90% 50% / 0.5);
}
.cli-tool-card.tool-codexlens.available:hover {
border-color: hsl(35 90% 50% / 0.7);
}
/* Semantic Search specific styles */
.cli-tool-card.tool-semantic.available {
border-color: hsl(260 80% 60% / 0.5);
}
.cli-tool-card.tool-semantic.available:hover {
border-color: hsl(260 80% 60% / 0.7);
}
.cli-tool-card.tool-semantic.clickable {
cursor: pointer;
}
.cli-tool-card.tool-semantic.clickable:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px hsl(260 80% 60% / 0.15);
}
/* Execute Panel */
.cli-execute-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid hsl(var(--border));
}
.cli-execute-header h3 {
font-size: 0.875rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0;
}
.cli-execute-form {
padding: 1rem;
}
.cli-execute-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.cli-form-group {
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.cli-form-group label {
font-size: 0.75rem;
font-weight: 500;
color: hsl(var(--muted-foreground));
}
.cli-select,
.cli-textarea {
padding: 0.5rem;
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
background: hsl(var(--background));
color: hsl(var(--foreground));
font-size: 0.875rem;
}
.cli-textarea {
min-height: 80px;
resize: vertical;
font-family: monospace;
}
.cli-select:focus,
.cli-textarea:focus {
outline: none;
border-color: hsl(var(--primary));
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2);
}
.cli-execute-actions {
display: flex;
justify-content: flex-end;
margin-top: 0.75rem;
}
/* History Panel */
.cli-history-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.875rem 1rem;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--muted) / 0.3);
}
.cli-history-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.8125rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0;
letter-spacing: -0.01em;
}
.cli-history-header h3 i {
color: hsl(var(--muted-foreground));
}
.cli-history-controls {
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Search Input for History */
.cli-history-search {
padding: 0.375rem 0.625rem;
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
background: hsl(var(--background));
color: hsl(var(--foreground));
font-size: 0.75rem;
width: 160px;
transition: all 0.2s ease;
}
.cli-history-search:focus {
outline: none;
border-color: hsl(var(--primary));
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.15);
width: 200px;
}
.cli-history-search::placeholder {
color: hsl(var(--muted-foreground) / 0.7);
}
.cli-tool-filter {
padding: 0.375rem 0.625rem;
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
background: hsl(var(--background));
color: hsl(var(--foreground));
font-size: 0.75rem;
cursor: pointer;
transition: all 0.2s ease;
}
.cli-tool-filter:hover {
border-color: hsl(var(--primary) / 0.5);
}
.cli-tool-filter:focus {
outline: none;
border-color: hsl(var(--primary));
}
.cli-history-list {
max-height: 450px;
overflow-y: auto;
}
.cli-history-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid hsl(var(--border) / 0.5);
cursor: pointer;
transition: all 0.15s ease;
}
.cli-history-item:hover {
background: hsl(var(--hover));
}
.cli-history-item:hover .cli-history-actions {
opacity: 1;
}
.cli-history-item:last-child {
border-bottom: none;
}
.cli-history-item-content {
flex: 1;
min-width: 0;
}
.cli-history-item-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.375rem;
}
.cli-tool-tag {
font-size: 0.5625rem;
font-weight: 600;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.cli-tool-gemini {
background: hsl(210 80% 55% / 0.12);
color: hsl(210 80% 45%);
}
.cli-tool-qwen {
background: hsl(280 70% 55% / 0.12);
color: hsl(280 70% 45%);
}
.cli-tool-codex {
background: hsl(142 71% 45% / 0.12);
color: hsl(142 71% 35%);
}
.cli-tool-claude {
background: hsl(25 90% 50% / 0.12);
color: hsl(25 90% 40%);
}
.cli-history-time {
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
}
.cli-history-prompt {
font-size: 0.8125rem;
font-weight: 450;
color: hsl(var(--foreground));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
line-height: 1.4;
}
.cli-history-meta {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.6875rem;
margin-top: 0.25rem;
color: hsl(var(--muted-foreground));
}
/* History Item Actions */
.cli-history-actions {
display: flex;
align-items: center;
gap: 0.25rem;
opacity: 0;
transition: opacity 0.15s ease;
margin-left: 0.5rem;
}
.cli-history-actions .btn-icon {
padding: 0.25rem;
color: hsl(var(--muted-foreground));
}
.cli-history-actions .btn-icon:hover {
color: hsl(var(--foreground));
background: hsl(var(--hover));
}
.cli-history-actions .btn-icon.btn-danger:hover {
color: hsl(var(--destructive));
background: hsl(var(--destructive) / 0.1);
}
/* Output Panel */
.cli-output-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid hsl(var(--border));
}
.cli-output-header h3 {
font-size: 0.875rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0;
}
.cli-output-status {
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
}
.status-indicator.running {
background: hsl(var(--warning));
animation: pulse 1.5s infinite;
}
.status-indicator.success {
background: hsl(var(--success));
}
.status-indicator.error {
background: hsl(var(--destructive));
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.cli-output-content {
padding: 1rem;
background: hsl(var(--muted));
font-family: monospace;
font-size: 0.75rem;
color: hsl(var(--foreground));
max-height: 300px;
overflow-y: auto;
white-space: pre-wrap;
word-wrap: break-word;
margin: 0;
}
/* Detail Modal */
.cli-detail-header {
margin-bottom: 1.25rem;
padding-bottom: 1rem;
border-bottom: 1px solid hsl(var(--border));
}
.cli-detail-info {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.625rem;
flex-wrap: wrap;
}
.cli-detail-status {
font-size: 0.6875rem;
font-weight: 600;
padding: 0.25rem 0.625rem;
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.cli-detail-status.status-success {
background: hsl(var(--success) / 0.12);
color: hsl(var(--success));
}
.cli-detail-status.status-error {
background: hsl(var(--destructive) / 0.12);
color: hsl(var(--destructive));
}
.cli-detail-status.status-timeout {
background: hsl(var(--warning) / 0.12);
color: hsl(var(--warning));
}
.cli-detail-meta {
display: flex;
gap: 1.25rem;
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
flex-wrap: wrap;
}
.cli-detail-meta span {
display: flex;
align-items: center;
gap: 0.375rem;
}
.cli-detail-section {
margin-bottom: 1.25rem;
}
.cli-detail-section h4 {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
color: hsl(var(--foreground));
margin-bottom: 0.625rem;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.cli-detail-section h4 i {
width: 14px;
height: 14px;
color: hsl(var(--muted-foreground));
}
.cli-detail-prompt {
padding: 1rem;
background: hsl(var(--muted) / 0.5);
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
font-size: 0.75rem;
line-height: 1.6;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 200px;
overflow-y: auto;
color: hsl(var(--foreground));
}
.cli-detail-output {
padding: 1rem;
background: hsl(var(--muted) / 0.5);
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
font-size: 0.75rem;
line-height: 1.6;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 350px;
overflow-y: auto;
color: hsl(var(--foreground));
}
.cli-detail-error {
padding: 1rem;
background: hsl(var(--destructive) / 0.08);
border: 1px solid hsl(var(--destructive) / 0.2);
border-radius: 0.5rem;
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
font-size: 0.75rem;
line-height: 1.6;
color: hsl(var(--destructive));
white-space: pre-wrap;
word-wrap: break-word;
max-height: 180px;
overflow-y: auto;
}
/* Detail Actions */
.cli-detail-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid hsl(var(--border));
}
/* Button Styles */
.btn {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.btn-primary {
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
.btn-primary:hover:not(:disabled) {
opacity: 0.9;
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-icon {
padding: 0.375rem;
background: transparent;
border: none;
color: hsl(var(--muted-foreground));
cursor: pointer;
border-radius: 0.25rem;
transition: all 0.15s ease;
}
.btn-icon:hover {
background: hsl(var(--hover));
color: hsl(var(--foreground));
}
.btn-sm {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.25rem;
padding: 0.375rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: 0.375rem;
border: 1px solid hsl(var(--border));
background: hsl(var(--background));
color: hsl(var(--foreground));
cursor: pointer;
transition: all 0.15s ease;
white-space: nowrap;
}
.btn-sm:hover {
background: hsl(var(--hover));
border-color: hsl(var(--primary) / 0.3);
}
.btn-sm.btn-primary {
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border-color: hsl(var(--primary));
}
.btn-sm.btn-primary:hover {
opacity: 0.9;
}
.btn-sm.btn-outline {
background: transparent;
border-color: hsl(var(--border));
}
.btn-sm.btn-outline:hover {
background: hsl(var(--hover));
}
.btn-outline {
background: transparent;
border: 1px solid hsl(var(--border));
color: hsl(var(--foreground));
}
.btn-outline:hover {
background: hsl(var(--hover));
}
.btn-outline.btn-danger {
border-color: hsl(var(--destructive) / 0.3);
color: hsl(var(--destructive));
}
.btn-outline.btn-danger:hover {
background: hsl(var(--destructive) / 0.1);
border-color: hsl(var(--destructive) / 0.5);
}
/* Empty State */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
color: hsl(var(--muted-foreground));
}
.empty-state svg {
width: 2rem;
height: 2rem;
margin-bottom: 0.5rem;
opacity: 0.5;
}
.empty-state p {
font-size: 0.875rem;
}

View File

@@ -0,0 +1,199 @@
/* ========================================
* CCW Installation Panel Styles
* ======================================== */
/* CCW Header Actions */
.ccw-header-actions {
display: flex;
align-items: center;
gap: 0.25rem;
}
/* CCW Install Content */
.ccw-install-content {
padding: 0.5rem 0.625rem;
}
/* CCW Empty State */
.ccw-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
text-align: center;
color: hsl(var(--muted-foreground));
}
.ccw-empty-state i {
opacity: 0.4;
margin-bottom: 0.75rem;
}
.ccw-empty-state p {
font-size: 0.8125rem;
margin-bottom: 1rem;
}
/* ========================================
* CCW Carousel Styles
* ======================================== */
.ccw-carousel-wrapper {
position: relative;
display: flex;
align-items: center;
gap: 0.5rem;
}
.ccw-carousel-track {
display: flex;
flex: 1;
overflow: hidden;
transition: transform 0.3s ease;
}
.ccw-carousel-card {
flex: 0 0 100%;
min-width: 0;
padding: 0.625rem 0.75rem;
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
transition: all 0.2s ease;
}
.ccw-carousel-card.active {
border-color: hsl(var(--primary) / 0.4);
box-shadow: 0 2px 8px hsl(var(--primary) / 0.1);
}
/* Carousel Card Header */
.ccw-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.375rem;
}
.ccw-card-header-right {
display: flex;
align-items: center;
gap: 0.375rem;
}
.ccw-card-mode {
display: flex;
align-items: center;
gap: 0.375rem;
font-weight: 600;
font-size: 0.8125rem;
color: hsl(var(--foreground));
}
.btn-icon-sm {
padding: 0.25rem;
}
.ccw-card-mode.global {
color: hsl(var(--primary));
}
.ccw-card-mode.path {
color: hsl(var(--warning));
}
.ccw-version-tag {
font-size: 0.625rem;
font-weight: 600;
padding: 0.25rem 0.5rem;
background: hsl(var(--primary) / 0.1);
color: hsl(var(--primary));
border-radius: 9999px;
letter-spacing: 0.02em;
}
/* Carousel Card Path */
.ccw-card-path {
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
background: hsl(var(--muted) / 0.5);
padding: 0.3125rem 0.5rem;
border-radius: 0.375rem;
margin-bottom: 0.375rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Carousel Card Meta */
.ccw-card-meta {
display: flex;
gap: 0.75rem;
font-size: 0.625rem;
color: hsl(var(--muted-foreground));
}
.ccw-card-meta span {
display: flex;
align-items: center;
gap: 0.25rem;
}
/* Carousel Card Actions - moved to header */
/* Carousel Navigation Buttons */
.ccw-carousel-btn {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
flex-shrink: 0;
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
border-radius: 50%;
color: hsl(var(--muted-foreground));
cursor: pointer;
transition: all 0.15s ease;
}
.ccw-carousel-btn:hover {
background: hsl(var(--hover));
color: hsl(var(--foreground));
border-color: hsl(var(--primary) / 0.3);
}
.ccw-carousel-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
/* Carousel Dots */
.ccw-carousel-dots {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 0.5rem;
}
.ccw-carousel-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: hsl(var(--muted-foreground) / 0.3);
border: none;
cursor: pointer;
transition: all 0.2s ease;
}
.ccw-carousel-dot:hover {
background: hsl(var(--muted-foreground) / 0.5);
}
.ccw-carousel-dot.active {
background: hsl(var(--primary));
width: 20px;
border-radius: 4px;
}

View File

@@ -0,0 +1,258 @@
/* ========================================
* CCW Install Modal Styles
* ======================================== */
.ccw-install-modal {
padding: 0.5rem 0;
}
.ccw-install-options {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.ccw-install-option {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
background: hsl(var(--muted) / 0.3);
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.15s ease;
}
.ccw-install-option:hover {
background: hsl(var(--hover));
border-color: hsl(var(--primary) / 0.3);
}
.ccw-option-icon {
display: flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
border-radius: 0.5rem;
flex-shrink: 0;
}
.ccw-option-icon.global {
background: hsl(var(--primary) / 0.1);
color: hsl(var(--primary));
}
.ccw-option-icon.path {
background: hsl(var(--warning) / 0.1);
color: hsl(var(--warning));
}
.ccw-option-info {
flex: 1;
}
.ccw-option-title {
font-weight: 600;
font-size: 0.875rem;
color: hsl(var(--foreground));
margin-bottom: 0.25rem;
}
.ccw-option-desc {
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
}
/* Path Input Section */
.ccw-path-input-section {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid hsl(var(--border));
}
.ccw-path-input-group {
margin-bottom: 1rem;
}
.ccw-path-input-group label {
display: block;
font-size: 0.75rem;
font-weight: 500;
color: hsl(var(--muted-foreground));
margin-bottom: 0.5rem;
}
.ccw-path-input-group .cli-textarea {
width: 100%;
min-height: auto;
padding: 0.625rem 0.75rem;
font-size: 0.8125rem;
}
.ccw-install-action {
display: flex;
justify-content: flex-end;
}
/* Danger Button (icon style - subtle) */
.btn-icon.btn-danger {
color: hsl(var(--muted-foreground));
background: transparent;
}
.btn-icon.btn-danger:hover {
color: hsl(var(--destructive));
background: hsl(var(--destructive) / 0.1);
}
/* ========================================
* Generic Modal Styles
* ======================================== */
.generic-modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(2px);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
opacity: 0;
transition: opacity 0.2s ease;
}
.generic-modal-overlay.active {
opacity: 1;
}
.generic-modal {
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.75rem;
width: 90%;
max-width: 600px;
max-height: 85vh;
display: flex;
flex-direction: column;
box-shadow: 0 8px 32px rgb(0 0 0 / 0.25);
transform: scale(0.95);
transition: transform 0.2s ease;
}
.generic-modal-overlay.active .generic-modal {
transform: scale(1);
}
.generic-modal.large {
max-width: 800px;
}
.generic-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--muted) / 0.3);
border-radius: 0.75rem 0.75rem 0 0;
}
.generic-modal-title {
font-size: 0.9375rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0;
}
.generic-modal-close {
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
background: none;
border: none;
font-size: 1.25rem;
color: hsl(var(--muted-foreground));
cursor: pointer;
border-radius: 0.375rem;
transition: all 0.15s ease;
}
.generic-modal-close:hover {
background: hsl(var(--hover));
color: hsl(var(--foreground));
}
.generic-modal-body {
flex: 1;
overflow-y: auto;
padding: 1.25rem;
}
.generic-modal.lg {
max-width: 640px;
}
/* ========================================
* Modal Aliases (for backward compatibility)
* ======================================== */
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(2px);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
opacity: 1;
transition: opacity 0.2s ease;
}
.modal-container {
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.75rem;
width: 90%;
max-width: 600px;
max-height: 85vh;
display: flex;
flex-direction: column;
box-shadow: 0 8px 32px rgb(0 0 0 / 0.25);
transform: scale(1);
transition: transform 0.2s ease;
}
.modal-container.large {
max-width: 800px;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--muted) / 0.3);
border-radius: 0.75rem 0.75rem 0 0;
}
.modal-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
background: hsl(var(--primary-light));
border-radius: 0.5rem;
color: hsl(var(--primary));
}
.modal-body {
flex: 1;
overflow-y: auto;
padding: 1.25rem;
}

View File

@@ -0,0 +1,305 @@
/* ========================================
* Endpoint Tools Grid Styles
* ======================================== */
.endpoint-tools-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 0.75rem;
padding: 0.75rem;
}
.endpoint-tool-card {
display: flex;
flex-direction: column;
padding: 0.875rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.2s ease;
min-height: 100px;
}
.endpoint-tool-card:hover {
background: hsl(var(--hover));
border-color: hsl(var(--indigo) / 0.5);
box-shadow: 0 2px 8px hsl(var(--indigo) / 0.1);
transform: translateY(-1px);
}
.endpoint-tool-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.endpoint-tool-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: hsl(var(--indigo));
box-shadow: 0 0 6px hsl(var(--indigo) / 0.5);
flex-shrink: 0;
}
.endpoint-tool-name {
font-size: 0.75rem;
font-weight: 600;
color: hsl(var(--foreground));
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.endpoint-tool-desc {
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
line-height: 1.4;
flex: 1;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 0.5rem;
}
.endpoint-tool-meta {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: auto;
}
.endpoint-tool-params {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.625rem;
color: hsl(var(--muted-foreground));
background: hsl(var(--muted) / 0.5);
padding: 0.1875rem 0.375rem;
border-radius: 0.25rem;
}
.endpoint-tool-required {
font-size: 0.5625rem;
color: hsl(var(--warning));
background: hsl(var(--warning) / 0.1);
padding: 0.125rem 0.375rem;
border-radius: 9999px;
font-weight: 500;
}
/* ========================================
* Tool Detail Modal Styles
* ======================================== */
.tool-detail-modal {
padding: 0.5rem 0;
}
.tool-detail-header {
display: flex;
align-items: flex-start;
gap: 1rem;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid hsl(var(--border));
}
.tool-detail-icon {
display: flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
background: hsl(var(--indigo) / 0.1);
color: hsl(var(--indigo));
border-radius: 0.5rem;
flex-shrink: 0;
}
.tool-detail-title h3 {
font-size: 1rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0 0 0.375rem 0;
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
}
.tool-detail-badge {
font-size: 0.625rem;
font-weight: 500;
padding: 0.1875rem 0.5rem;
background: hsl(var(--indigo) / 0.1);
color: hsl(var(--indigo));
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.tool-detail-desc {
font-size: 0.8125rem;
color: hsl(var(--muted-foreground));
line-height: 1.6;
margin-bottom: 1.25rem;
}
.tool-detail-params h4,
.tool-detail-usage h4 {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0 0 0.75rem 0;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.tool-detail-params h4 i,
.tool-detail-usage h4 i {
color: hsl(var(--muted-foreground));
}
.tool-params-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-height: 280px;
overflow-y: auto;
padding-right: 0.5rem;
}
.tool-param-item {
padding: 0.75rem;
background: hsl(var(--muted) / 0.3);
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
}
.tool-param-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.375rem;
flex-wrap: wrap;
}
.tool-param-name {
font-size: 0.8125rem;
font-weight: 600;
color: hsl(var(--foreground));
background: hsl(var(--muted));
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
}
.tool-param-type {
font-size: 0.625rem;
color: hsl(var(--primary));
background: hsl(var(--primary) / 0.1);
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
}
.tool-param-required {
font-size: 0.5625rem;
font-weight: 600;
color: hsl(var(--destructive));
background: hsl(var(--destructive) / 0.1);
padding: 0.125rem 0.375rem;
border-radius: 9999px;
text-transform: uppercase;
}
.tool-param-optional {
font-size: 0.5625rem;
font-weight: 500;
color: hsl(var(--muted-foreground));
background: hsl(var(--muted));
padding: 0.125rem 0.375rem;
border-radius: 9999px;
}
.tool-param-desc {
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
line-height: 1.5;
}
.tool-param-default,
.tool-param-enum {
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
margin-top: 0.25rem;
}
.tool-param-default code,
.tool-param-enum code {
font-size: 0.6875rem;
background: hsl(var(--muted));
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
color: hsl(var(--foreground));
}
.tool-detail-no-params {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1.5rem;
background: hsl(var(--muted) / 0.3);
border: 1px dashed hsl(var(--border));
border-radius: 0.5rem;
color: hsl(var(--muted-foreground));
font-size: 0.8125rem;
}
.tool-detail-usage {
margin-top: 1.25rem;
}
.tool-usage-code {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background: hsl(var(--muted) / 0.5);
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
overflow-x: auto;
}
.tool-usage-code code {
font-size: 0.75rem;
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
color: hsl(var(--foreground));
white-space: nowrap;
flex: 1;
}
.tool-copy-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 0.375rem;
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
border-radius: 0.25rem;
color: hsl(var(--muted-foreground));
cursor: pointer;
transition: all 0.15s ease;
flex-shrink: 0;
}
.tool-copy-btn:hover {
background: hsl(var(--hover));
color: hsl(var(--foreground));
border-color: hsl(var(--primary) / 0.3);
}

View File

@@ -0,0 +1,241 @@
/* ========================================
* Resume Session Styles
* ======================================== */
/* Resume Badge */
.history-resume-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.1875rem 0.375rem;
background: hsl(var(--primary) / 0.12);
color: hsl(var(--primary));
border-radius: 0.25rem;
font-size: 0.625rem;
}
/* Resume Item Highlight */
.history-item-resume {
border-left: 3px solid hsl(var(--primary) / 0.5);
}
.history-item-resume:hover {
border-left-color: hsl(var(--primary));
}
/* History ID Display */
.history-id {
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
font-size: 0.625rem;
color: hsl(var(--muted-foreground) / 0.7);
}
/* Resume Button */
.btn-resume {
color: hsl(var(--primary));
}
.btn-resume:hover {
background: hsl(var(--primary) / 0.1);
color: hsl(var(--primary));
}
/* Resume Modal */
.resume-modal p {
font-size: 0.875rem;
color: hsl(var(--muted-foreground));
margin-bottom: 1rem;
}
.resume-prompt-input {
width: 100%;
padding: 0.75rem;
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
background: hsl(var(--background));
color: hsl(var(--foreground));
font-size: 0.875rem;
font-family: inherit;
resize: vertical;
min-height: 80px;
transition: all 0.2s ease;
}
.resume-prompt-input:focus {
outline: none;
border-color: hsl(var(--primary));
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.15);
}
.resume-prompt-input::placeholder {
color: hsl(var(--muted-foreground) / 0.7);
}
.resume-modal-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid hsl(var(--border));
}
/* ========================================
* Batch Delete & Multi-Select Styles
* ======================================== */
/* Delete Dropdown */
.history-delete-dropdown {
position: relative;
display: inline-block;
}
.delete-dropdown-menu {
display: none;
position: absolute;
top: 100%;
right: 0;
z-index: 50;
min-width: 180px;
padding: 0.375rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
box-shadow: 0 4px 16px hsl(var(--foreground) / 0.1);
margin-top: 0.25rem;
}
.delete-dropdown-menu.show {
display: block;
}
.delete-dropdown-menu button {
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
padding: 0.5rem 0.625rem;
border: none;
background: transparent;
color: hsl(var(--foreground));
font-size: 0.75rem;
text-align: left;
cursor: pointer;
border-radius: 0.375rem;
transition: all 0.15s ease;
}
.delete-dropdown-menu button:hover {
background: hsl(var(--hover));
}
.delete-dropdown-menu button i {
color: hsl(var(--muted-foreground));
}
.delete-dropdown-menu .delete-all-btn {
color: hsl(var(--destructive));
}
.delete-dropdown-menu .delete-all-btn i {
color: hsl(var(--destructive));
}
.delete-dropdown-menu .delete-all-btn:hover {
background: hsl(var(--destructive) / 0.1);
}
.dropdown-divider {
height: 1px;
margin: 0.375rem 0;
background: hsl(var(--border));
}
/* Batch Actions Bar */
.history-batch-actions {
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.75rem 1rem;
background: hsl(var(--primary) / 0.08);
border: 1px solid hsl(var(--primary) / 0.2);
border-radius: 0.5rem;
margin-bottom: 1rem;
}
.batch-select-count {
font-size: 0.8125rem;
font-weight: 600;
color: hsl(var(--primary));
margin-right: auto;
}
.btn-danger {
background: hsl(var(--destructive));
color: hsl(var(--destructive-foreground));
border-color: hsl(var(--destructive));
}
.btn-danger:hover:not(:disabled) {
opacity: 0.9;
}
.btn-danger:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Override for icon-style danger buttons (subtle, not solid red) */
.btn-icon.btn-danger,
.history-item-actions .btn-danger,
.cli-history-actions .btn-danger {
background: transparent;
color: hsl(var(--muted-foreground));
border: none;
}
.btn-icon.btn-danger:hover,
.history-item-actions .btn-danger:hover,
.cli-history-actions .btn-danger:hover {
background: hsl(var(--destructive) / 0.1);
color: hsl(var(--destructive));
opacity: 1;
}
/* Multi-Select Checkbox */
.history-checkbox-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
margin-right: 0.75rem;
flex-shrink: 0;
}
.history-checkbox {
width: 1.125rem;
height: 1.125rem;
cursor: pointer;
accent-color: hsl(var(--primary));
}
/* Selected Item State */
.history-item-selected {
background: hsl(var(--primary) / 0.08) !important;
border-color: hsl(var(--primary) / 0.3) !important;
}
/* Turn Badge for History List */
.history-turn-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.625rem;
font-weight: 600;
padding: 0.1875rem 0.5rem;
background: hsl(var(--primary) / 0.12);
color: hsl(var(--primary));
border-radius: 9999px;
}

View File

@@ -0,0 +1,283 @@
/* ========================================
* Multi-Turn Conversation Styles
* ======================================== */
/* Turn Badge in History List */
.cli-turn-badge {
font-size: 0.5625rem;
font-weight: 600;
padding: 0.125rem 0.5rem;
background: hsl(var(--primary) / 0.12);
color: hsl(var(--primary));
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.02em;
}
/* Turns Container in Detail Modal */
.cli-turns-container {
max-height: 60vh;
overflow-y: auto;
}
/* Turn Section */
.cli-turn-section {
margin-bottom: 1rem;
}
.cli-turn-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
flex-wrap: wrap;
}
.cli-turn-number {
font-size: 0.75rem;
font-weight: 600;
padding: 0.25rem 0.625rem;
background: hsl(var(--primary) / 0.1);
color: hsl(var(--primary));
border-radius: 9999px;
}
.cli-turn-status {
font-size: 0.625rem;
font-weight: 600;
padding: 0.1875rem 0.5rem;
border-radius: 9999px;
text-transform: uppercase;
}
.cli-turn-status.status-success {
background: hsl(var(--success) / 0.12);
color: hsl(var(--success));
}
.cli-turn-status.status-error {
background: hsl(var(--destructive) / 0.12);
color: hsl(var(--destructive));
}
.cli-turn-status.status-timeout {
background: hsl(var(--warning) / 0.12);
color: hsl(var(--warning));
}
.cli-turn-duration {
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
}
/* Turn Divider (legacy) */
.cli-turn-divider {
border: none;
border-top: 1px dashed hsl(var(--border));
margin: 1.25rem 0;
}
/* Error Section (smaller in multi-turn) */
.cli-detail-error-section .cli-detail-error {
max-height: 100px;
}
/* ========================================
* Enhanced Multi-Turn Display
* ======================================== */
/* Turn Section */
.cli-turn-section {
padding: 0.75rem;
border-radius: 0.5rem;
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
}
.cli-turn-section.cli-turn-latest {
border-color: hsl(var(--primary) / 0.3);
background: hsl(var(--primary) / 0.03);
}
/* Turn Header */
.cli-turn-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid hsl(var(--border));
flex-wrap: wrap;
gap: 0.5rem;
}
.cli-turn-marker {
display: flex;
align-items: center;
gap: 0.5rem;
}
.cli-turn-number {
font-size: 0.8125rem;
font-weight: 600;
color: hsl(var(--primary));
}
.cli-turn-latest-badge {
font-size: 0.5625rem;
font-weight: 600;
padding: 0.125rem 0.375rem;
background: hsl(var(--success) / 0.12);
color: hsl(var(--success));
border-radius: 9999px;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.cli-turn-meta {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
}
.cli-turn-meta span {
display: flex;
align-items: center;
gap: 0.25rem;
}
.cli-turn-time i,
.cli-turn-duration i {
color: hsl(var(--muted-foreground) / 0.7);
}
/* Turn Body */
.cli-turn-body {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
/* Section Labels */
.cli-prompt-section h4 {
color: hsl(var(--primary));
}
.cli-prompt-section h4 i {
color: hsl(var(--primary));
}
.cli-output-section h4 {
color: hsl(var(--success));
}
.cli-output-section h4 i {
color: hsl(var(--success));
}
/* Turn Connector (visual line between turns) */
.cli-turn-connector {
display: flex;
justify-content: center;
padding: 0.25rem 0;
}
.cli-turn-line {
width: 2px;
height: 1.5rem;
background: linear-gradient(
to bottom,
hsl(var(--border)),
hsl(var(--primary) / 0.3),
hsl(var(--border))
);
border-radius: 1px;
}
/* Truncated Notice */
.cli-truncated-notice {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.75rem;
color: hsl(var(--warning));
margin-top: 0.5rem;
padding: 0.375rem 0.625rem;
background: hsl(var(--warning) / 0.08);
border-radius: 0.25rem;
}
.cli-truncated-notice i {
flex-shrink: 0;
}
/* Turn Badge with Icon */
.cli-turn-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.cli-turn-badge i {
width: 12px;
height: 12px;
}
/* ========================================
* Conversation View Toggle
* ======================================== */
/* View Toggle Bar */
.cli-view-toggle {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
padding: 0.5rem;
background: hsl(var(--muted) / 0.3);
border-radius: 0.5rem;
}
.cli-view-toggle .btn {
flex: 1;
justify-content: center;
}
.cli-view-toggle .btn.active {
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border-color: hsl(var(--primary));
}
/* Concatenated Prompt Section */
.cli-concat-section {
margin-top: 1rem;
}
.cli-concat-format-selector {
display: flex;
gap: 0.375rem;
margin-bottom: 0.75rem;
}
.cli-concat-format-selector .btn-xs {
padding: 0.25rem 0.625rem;
font-size: 0.6875rem;
}
.cli-concat-output {
max-height: 400px;
overflow-y: auto;
font-size: 0.75rem;
white-space: pre-wrap;
word-break: break-word;
}
/* Button Sizes */
.btn-xs {
padding: 0.25rem 0.5rem;
font-size: 0.6875rem;
border-radius: 0.25rem;
}

View File

@@ -0,0 +1,160 @@
/* ========================================
* CLI Settings Section
* ======================================== */
.cli-settings-section {
/* No card wrapper - just title and cards */
}
.cli-settings-section .section-header {
padding: 0 0 0.75rem 0;
border-bottom: none;
background: transparent;
}
.cli-settings-section .section-header h3 {
font-size: 0.9375rem;
}
.cli-settings-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.75rem;
}
@media (max-width: 1200px) {
.cli-settings-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 640px) {
.cli-settings-grid {
grid-template-columns: 1fr;
}
}
.cli-setting-item {
padding: 0.75rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
display: flex;
flex-direction: column;
min-height: 90px;
}
.cli-setting-label {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.6875rem;
font-weight: 600;
color: hsl(var(--muted-foreground));
text-transform: uppercase;
letter-spacing: 0.025em;
margin-bottom: 0.5rem;
}
.cli-setting-label i {
color: hsl(var(--primary));
width: 12px;
height: 12px;
}
.cli-setting-control {
margin-bottom: 0.5rem;
flex-shrink: 0;
}
.cli-setting-select {
width: 100%;
padding: 0.4375rem 0.5rem;
font-size: 0.8125rem;
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
color: hsl(var(--foreground));
cursor: pointer;
transition: all 0.15s ease;
}
.cli-setting-select:hover {
border-color: hsl(var(--primary) / 0.5);
}
.cli-setting-select:focus {
outline: none;
border-color: hsl(var(--primary));
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.1);
}
.cli-setting-desc {
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
line-height: 1.3;
margin-top: auto;
}
.cli-setting-value {
font-size: 0.875rem;
color: hsl(var(--foreground));
font-weight: 500;
}
/* Toggle Switch */
.cli-toggle {
position: relative;
display: inline-block;
width: 36px;
height: 20px;
}
.cli-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.cli-toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: hsl(var(--muted));
transition: 0.3s;
border-radius: 20px;
}
.cli-toggle-slider:before {
position: absolute;
content: "";
height: 14px;
width: 14px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.3s;
border-radius: 50%;
}
.cli-toggle input:checked + .cli-toggle-slider {
background-color: hsl(var(--primary));
}
.cli-toggle input:checked + .cli-toggle-slider:before {
transform: translateX(16px);
}
.cli-toggle input:focus + .cli-toggle-slider {
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2);
}
/* Disabled state for settings */
.cli-setting-item.disabled {
opacity: 0.5;
pointer-events: none;
}

View File

@@ -0,0 +1,496 @@
/* ========================================
* Native Session Styles
* ======================================== */
/* Native badge in history list */
.cli-native-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.125rem 0.375rem;
background: hsl(var(--primary) / 0.1);
color: hsl(var(--primary));
border-radius: 0.25rem;
font-size: 0.625rem;
}
.cli-history-item.has-native {
border-left: 2px solid hsl(var(--primary) / 0.5);
}
/* Mode tag */
.cli-mode-tag {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.375rem;
font-size: 0.625rem;
font-weight: 500;
color: hsl(var(--muted-foreground));
background: hsl(var(--muted));
border-radius: 0.25rem;
}
/* Status badge */
.cli-status-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.125rem 0.375rem;
font-size: 0.625rem;
font-weight: 500;
border-radius: 0.25rem;
}
.cli-status-badge.text-success {
background: hsl(var(--success) / 0.1);
color: hsl(var(--success));
}
.cli-status-badge.text-warning {
background: hsl(var(--warning) / 0.1);
color: hsl(var(--warning));
}
.cli-status-badge.text-destructive {
background: hsl(var(--destructive) / 0.1);
color: hsl(var(--destructive));
}
/* Native Session Detail Modal */
.native-session-detail {
font-family: system-ui, -apple-system, sans-serif;
}
.native-session-header {
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid hsl(var(--border));
}
.native-session-info {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
margin-bottom: 0.5rem;
}
.native-model,
.native-session-id {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
}
.native-session-meta {
display: flex;
flex-wrap: wrap;
gap: 1rem;
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
}
.native-session-meta span {
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
/* Tokens Summary */
.native-tokens-summary {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
margin-bottom: 1rem;
background: hsl(var(--muted) / 0.5);
border-radius: 0.5rem;
font-size: 0.75rem;
color: hsl(var(--foreground));
}
/* Native Turns Container */
.native-turns-container {
max-height: 60vh;
overflow-y: auto;
padding-right: 0.5rem;
}
/* Native Turn */
.native-turn {
margin-bottom: 1rem;
padding: 0.875rem;
border-radius: 0.5rem;
border: 1px solid hsl(var(--border));
}
.native-turn.user {
background: hsl(var(--muted) / 0.3);
border-left: 3px solid hsl(var(--primary));
}
.native-turn.assistant {
background: hsl(var(--background));
border-left: 3px solid hsl(var(--success));
}
.native-turn.latest {
box-shadow: 0 0 0 1px hsl(var(--primary) / 0.3);
}
.native-turn-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.625rem;
flex-wrap: wrap;
}
.native-turn-role {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
color: hsl(var(--foreground));
}
.native-turn-number {
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
}
.native-turn-tokens {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.625rem;
color: hsl(var(--muted-foreground));
padding: 0.125rem 0.375rem;
background: hsl(var(--muted));
border-radius: 0.25rem;
}
.native-turn-latest {
font-size: 0.625rem;
font-weight: 500;
padding: 0.125rem 0.375rem;
background: hsl(var(--primary) / 0.1);
color: hsl(var(--primary));
border-radius: 0.25rem;
}
.native-turn-content pre {
margin: 0;
padding: 0.75rem;
background: hsl(var(--muted));
border-radius: 0.375rem;
font-family: monospace;
font-size: 0.75rem;
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 300px;
overflow-y: auto;
}
/* Thoughts Section */
.native-thoughts-section {
margin-top: 0.75rem;
padding: 0.625rem;
background: hsl(var(--warning) / 0.05);
border: 1px solid hsl(var(--warning) / 0.2);
border-radius: 0.375rem;
}
.native-thoughts-section h5 {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.6875rem;
font-weight: 600;
color: hsl(var(--warning));
margin: 0 0 0.5rem 0;
}
.native-thoughts-list {
margin: 0;
padding-left: 1.25rem;
font-size: 0.6875rem;
color: hsl(var(--foreground));
}
.native-thoughts-list li {
margin-bottom: 0.25rem;
}
/* Tool Calls Section */
.native-tools-section {
margin-top: 0.75rem;
padding: 0.625rem;
background: hsl(var(--muted) / 0.5);
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
}
.native-tools-section h5 {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.6875rem;
font-weight: 600;
color: hsl(var(--muted-foreground));
margin: 0 0 0.5rem 0;
}
.native-tools-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.native-tool-call {
padding: 0.5rem;
background: hsl(var(--background));
border-radius: 0.25rem;
}
.native-tool-name {
display: inline-block;
font-family: monospace;
font-size: 0.6875rem;
font-weight: 600;
color: hsl(var(--primary));
margin-bottom: 0.25rem;
}
.native-tool-output {
margin: 0.25rem 0 0 0;
padding: 0.375rem;
background: hsl(var(--muted));
border-radius: 0.25rem;
font-family: monospace;
font-size: 0.625rem;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 100px;
overflow-y: auto;
}
/* Native Session Actions */
.native-session-actions {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid hsl(var(--border));
flex-wrap: wrap;
}
/* ========================================
* Enhanced Native Session Display
* ======================================== */
/* View Full Process Button in Execution Detail */
.cli-detail-native-action {
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid hsl(var(--border) / 0.5);
}
.cli-detail-native-action .btn {
font-size: 0.8125rem;
gap: 0.5rem;
}
/* Collapsible Thinking Process */
.turn-thinking-details {
border: none;
margin: 0;
}
.turn-thinking-summary {
display: flex;
align-items: center;
gap: 0.375rem;
cursor: pointer;
padding: 0.5rem;
background: hsl(var(--warning) / 0.08);
border: 1px solid hsl(var(--warning) / 0.25);
border-radius: 0.375rem;
font-size: 0.75rem;
font-weight: 600;
color: hsl(var(--warning));
transition: all 0.2s ease;
list-style: none;
}
.turn-thinking-summary::-webkit-details-marker {
display: none;
}
.turn-thinking-summary:hover {
background: hsl(var(--warning) / 0.15);
border-color: hsl(var(--warning) / 0.4);
}
.turn-thinking-summary::before {
content: '▶';
display: inline-block;
margin-right: 0.25rem;
transition: transform 0.2s ease;
font-size: 0.6875rem;
}
.turn-thinking-details[open] .turn-thinking-summary::before {
transform: rotate(90deg);
}
.turn-thinking-content {
padding: 0.75rem;
margin-top: 0.5rem;
background: hsl(var(--warning) / 0.03);
border: 1px solid hsl(var(--warning) / 0.15);
border-radius: 0.375rem;
font-style: italic;
}
.turn-thinking-content ul {
margin: 0;
padding-left: 1.25rem;
}
.turn-thinking-content li {
margin-bottom: 0.375rem;
font-size: 0.6875rem;
line-height: 1.6;
color: hsl(var(--foreground) / 0.85);
}
/* Tool Calls Header */
.turn-tool-calls-header {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.75rem;
font-weight: 600;
color: hsl(var(--muted-foreground));
margin-bottom: 0.625rem;
padding-bottom: 0.375rem;
border-bottom: 1px solid hsl(var(--border) / 0.5);
}
/* Collapsible Tool Calls */
.turn-tool-call-details {
border: none;
margin-bottom: 0.5rem;
}
.turn-tool-call-summary {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
padding: 0.5rem 0.75rem;
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
font-size: 0.7rem;
transition: all 0.2s ease;
list-style: none;
}
.turn-tool-call-summary::-webkit-details-marker {
display: none;
}
.turn-tool-call-summary:hover {
background: hsl(var(--muted) / 0.5);
border-color: hsl(var(--primary) / 0.4);
}
.turn-tool-call-summary::before {
content: '▶';
display: inline-block;
margin-right: 0.5rem;
transition: transform 0.2s ease;
font-size: 0.625rem;
color: hsl(var(--muted-foreground));
}
.turn-tool-call-details[open] .turn-tool-call-summary::before {
transform: rotate(90deg);
}
.native-tool-size {
font-size: 0.625rem;
color: hsl(var(--muted-foreground));
font-weight: 400;
}
.turn-tool-call-content {
padding: 0.75rem;
margin-top: 0.5rem;
background: hsl(var(--muted) / 0.3);
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.turn-tool-input,
.turn-tool-output {
margin-bottom: 0.75rem;
}
.turn-tool-input:last-child,
.turn-tool-output:last-child {
margin-bottom: 0;
}
.turn-tool-input strong,
.turn-tool-output strong {
display: block;
font-size: 0.6875rem;
font-weight: 600;
color: hsl(var(--foreground));
margin-bottom: 0.375rem;
}
.turn-tool-input pre,
.turn-tool-output pre {
margin: 0;
padding: 0.5rem;
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
border-radius: 0.25rem;
font-family: monospace;
font-size: 0.6875rem;
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 400px;
overflow-y: auto;
}
/* Improved scrollbar for tool output */
.turn-tool-output pre::-webkit-scrollbar {
width: 6px;
}
.turn-tool-output pre::-webkit-scrollbar-track {
background: hsl(var(--muted));
border-radius: 3px;
}
.turn-tool-output pre::-webkit-scrollbar-thumb {
background: hsl(var(--muted-foreground) / 0.3);
border-radius: 3px;
}
.turn-tool-output pre::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground) / 0.5);
}

View File

@@ -0,0 +1,188 @@
/* ========================================
* Task Queue Sidebar - CLI Tab Styles
* ======================================== */
/* Tab Navigation */
.task-queue-tabs {
display: flex;
gap: 0;
border-bottom: 1px solid hsl(var(--border));
padding: 0 1rem;
}
.task-queue-tab {
flex: 1;
padding: 0.625rem 0.75rem;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
color: hsl(var(--muted-foreground));
font-size: 0.8125rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
}
.task-queue-tab:hover {
color: hsl(var(--foreground));
background: hsl(var(--muted) / 0.3);
}
.task-queue-tab.active {
color: hsl(var(--primary));
border-bottom-color: hsl(var(--primary));
}
.task-queue-tab .tab-badge {
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
padding: 0.125rem 0.375rem;
border-radius: 9999px;
font-size: 0.6875rem;
font-weight: 600;
min-width: 1.25rem;
text-align: center;
}
.task-queue-tab.active .tab-badge {
background: hsl(var(--primary) / 0.15);
color: hsl(var(--primary));
}
/* CLI Filter Buttons */
.cli-filter-btn {
padding: 0.375rem 0.625rem;
background: transparent;
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
color: hsl(var(--muted-foreground));
font-size: 0.75rem;
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.cli-filter-btn:hover {
background: hsl(var(--muted) / 0.5);
color: hsl(var(--foreground));
}
.cli-filter-btn.active {
background: hsl(var(--primary));
border-color: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
/* CLI Queue Item */
.cli-queue-item {
padding: 0.75rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
margin-bottom: 0.5rem;
cursor: pointer;
transition: all 0.15s;
}
.cli-queue-item:hover {
background: hsl(var(--muted) / 0.5);
border-color: hsl(var(--primary) / 0.3);
}
.cli-queue-item.category-user {
border-left: 3px solid #3b82f6;
}
.cli-queue-item.category-insight {
border-left: 3px solid #a855f7;
}
.cli-queue-item.category-internal {
border-left: 3px solid #22c55e;
}
.cli-queue-item-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.375rem;
}
.cli-queue-category-icon {
font-size: 0.875rem;
}
.cli-queue-tool-tag {
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
font-size: 0.625rem;
font-weight: 600;
text-transform: uppercase;
}
.cli-queue-tool-tag.cli-tool-gemini {
background: hsl(210 100% 50% / 0.15);
color: hsl(210 100% 45%);
}
.cli-queue-tool-tag.cli-tool-qwen {
background: hsl(280 100% 50% / 0.15);
color: hsl(280 100% 40%);
}
.cli-queue-tool-tag.cli-tool-codex {
background: hsl(145 60% 45% / 0.15);
color: hsl(145 60% 35%);
}
.cli-queue-tool-tag.cli-tool-claude {
background: hsl(25 90% 50% / 0.15);
color: hsl(25 90% 40%);
}
.cli-queue-status {
font-size: 0.75rem;
}
.cli-queue-time {
margin-left: auto;
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
}
.cli-queue-prompt {
font-size: 0.75rem;
color: hsl(var(--foreground));
line-height: 1.4;
margin-bottom: 0.375rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.cli-queue-meta {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.6875rem;
color: hsl(var(--muted-foreground));
}
.cli-queue-id {
font-family: monospace;
}
.cli-queue-turns {
background: hsl(var(--muted));
padding: 0.0625rem 0.25rem;
border-radius: 0.25rem;
}
.cli-queue-native {
font-size: 0.75rem;
}

View File

@@ -0,0 +1,310 @@
/* ========================================
* CLI Tool Management Styles
* ======================================== */
/* Disabled tool card */
.cli-tool-card.disabled {
opacity: 0.6;
}
.cli-tool-card.disabled .cli-tool-header {
opacity: 0.8;
}
/* Disabled status indicator */
.cli-tool-status.status-disabled {
background: hsl(var(--warning));
}
/* Warning badge */
.cli-tool-badge.badge-warning {
background: hsl(var(--warning) / 0.15);
color: hsl(var(--warning));
font-size: 0.65rem;
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
margin-left: 0.25rem;
}
/* Compact toggle for tool cards */
.cli-toggle-compact {
position: relative;
display: inline-block;
width: 28px;
height: 16px;
cursor: pointer;
}
.cli-toggle-compact input {
opacity: 0;
width: 0;
height: 0;
position: absolute;
}
.cli-toggle-slider-compact {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: hsl(var(--muted));
transition: 0.2s;
border-radius: 16px;
}
.cli-toggle-slider-compact:before {
position: absolute;
content: "";
height: 12px;
width: 12px;
left: 2px;
bottom: 2px;
background-color: white;
transition: 0.2s;
border-radius: 50%;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}
.cli-toggle-compact input:checked + .cli-toggle-slider-compact {
background-color: hsl(var(--primary));
}
.cli-toggle-compact input:checked + .cli-toggle-slider-compact:before {
transform: translateX(12px);
}
.cli-toggle-compact input:focus + .cli-toggle-slider-compact {
box-shadow: 0 0 0 2px hsl(var(--ring) / 0.5);
}
/* Language Setting Status Badge */
.cli-setting-status {
margin-left: 0.75rem;
padding: 0.25rem 0.625rem;
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
border-radius: 0.25rem;
display: inline-flex;
align-items: center;
}
.cli-setting-status.enabled {
background: hsl(var(--primary) / 0.15);
color: hsl(var(--primary));
}
.cli-setting-status.disabled {
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
}
/* Ghost button variant for destructive actions */
.btn-ghost.text-destructive {
color: hsl(var(--destructive));
}
/* ========================================
* Tool Configuration Modal
* ======================================== */
/* Tool item clickable */
.tool-item.clickable {
cursor: pointer;
transition: all 0.15s ease;
}
.tool-item.clickable:hover {
background: hsl(var(--accent));
border-color: hsl(var(--primary) / 0.3);
}
.tool-item.clickable:hover .tool-config-icon {
opacity: 1;
}
.tool-config-icon {
margin-left: 0.375rem;
color: hsl(var(--muted-foreground));
opacity: 0;
transition: opacity 0.15s ease;
}
/* Tool Config Modal */
.tool-config-modal {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.tool-config-section {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.tool-config-section h4 {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.025em;
color: hsl(var(--muted-foreground));
margin: 0;
}
.tool-config-section h4 .text-muted {
font-weight: 400;
text-transform: none;
color: hsl(var(--muted-foreground) / 0.7);
}
/* Status Badges */
.tool-config-badges {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.6875rem;
font-weight: 500;
border-radius: 9999px;
}
.badge-success {
background: hsl(var(--success) / 0.15);
color: hsl(var(--success));
}
.badge-primary {
background: hsl(var(--primary) / 0.15);
color: hsl(var(--primary));
}
.badge-muted {
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
}
/* Config Actions */
.tool-config-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.btn-danger-outline {
border-color: hsl(var(--destructive) / 0.5);
color: hsl(var(--destructive));
}
.btn-danger-outline:hover {
background: hsl(var(--destructive) / 0.1);
border-color: hsl(var(--destructive));
}
/* Config Selects and Inputs */
.tool-config-select,
.tool-config-input {
width: 100%;
padding: 0.5rem 0.75rem;
font-size: 0.8125rem;
font-family: inherit;
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
background: hsl(var(--background));
color: hsl(var(--foreground));
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.tool-config-select:focus,
.tool-config-input:focus {
outline: none;
border-color: hsl(var(--primary));
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2);
}
.tool-config-select {
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.25rem;
padding-right: 2rem;
}
.tool-config-input.hidden {
display: none;
}
.tool-config-input {
margin-top: 0.375rem;
}
/* Config Footer */
.tool-config-footer {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding: 1rem 1.25rem 1.25rem 1.25rem;
border-top: 1px solid hsl(var(--border));
margin-top: 0.5rem;
}
.tool-config-footer .btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
padding: 0.5rem 1rem;
font-size: 0.8125rem;
font-weight: 500;
border-radius: 0.375rem;
cursor: pointer;
transition: all 0.15s ease;
}
.tool-config-footer .btn-outline {
background: transparent;
border: 1px solid hsl(var(--border));
color: hsl(var(--foreground));
}
.tool-config-footer .btn-outline:hover {
background: hsl(var(--muted));
border-color: hsl(var(--muted-foreground) / 0.3);
}
.tool-config-footer .btn-primary {
background: hsl(var(--primary));
border: 1px solid hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
.tool-config-footer .btn-primary:hover {
background: hsl(var(--primary) / 0.9);
}
/* Model Select Group */
.model-select-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.model-select-group .tool-config-input {
margin-top: 0;
}
.btn-ghost.text-destructive:hover {
background: hsl(var(--destructive) / 0.1);
}

View File

@@ -0,0 +1,240 @@
/* ========================================
* Semantic Metadata Viewer Styles
* ======================================== */
.semantic-viewer-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
background: hsl(var(--muted) / 0.3);
border-bottom: 1px solid hsl(var(--border));
}
.semantic-table-container {
max-height: 400px;
overflow-y: auto;
}
.semantic-table {
width: 100%;
border-collapse: collapse;
font-size: 0.8125rem;
}
.semantic-table th {
position: sticky;
top: 0;
background: hsl(var(--card));
padding: 0.625rem 0.75rem;
text-align: left;
font-weight: 600;
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
border-bottom: 1px solid hsl(var(--border));
white-space: nowrap;
}
.semantic-table td {
padding: 0.625rem 0.75rem;
border-bottom: 1px solid hsl(var(--border) / 0.5);
vertical-align: top;
}
.semantic-row {
cursor: pointer;
transition: background 0.15s ease;
}
.semantic-row:hover {
background: hsl(var(--hover));
}
.semantic-cell-file {
max-width: 200px;
}
.semantic-cell-lang {
width: 80px;
color: hsl(var(--muted-foreground));
}
.semantic-cell-purpose {
max-width: 180px;
color: hsl(var(--foreground) / 0.8);
}
.semantic-cell-keywords {
max-width: 160px;
}
.semantic-cell-tool {
width: 70px;
}
.semantic-cell-date {
width: 80px;
color: hsl(var(--muted-foreground));
font-size: 0.75rem;
}
.semantic-keyword {
display: inline-block;
padding: 0.125rem 0.375rem;
margin: 0.125rem;
background: hsl(var(--primary) / 0.1);
color: hsl(var(--primary));
border-radius: 0.25rem;
font-size: 0.6875rem;
}
.semantic-keyword-more {
display: inline-block;
padding: 0.125rem 0.375rem;
margin: 0.125rem;
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
border-radius: 0.25rem;
font-size: 0.6875rem;
}
.tool-badge {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.6875rem;
font-weight: 500;
text-transform: capitalize;
}
.tool-badge.tool-gemini {
background: hsl(210 80% 55% / 0.15);
color: hsl(210 80% 45%);
}
.tool-badge.tool-qwen {
background: hsl(142 76% 36% / 0.15);
color: hsl(142 76% 36%);
}
.tool-badge.tool-unknown {
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
}
.semantic-detail-row {
background: hsl(var(--muted) / 0.2);
}
.semantic-detail-row.hidden {
display: none;
}
.semantic-detail-content {
padding: 1rem;
}
.semantic-detail-section {
margin-bottom: 1rem;
}
.semantic-detail-section h4 {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
color: hsl(var(--muted-foreground));
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.semantic-detail-section p {
font-size: 0.8125rem;
line-height: 1.5;
color: hsl(var(--foreground));
}
.semantic-keywords-full {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
.semantic-detail-meta {
display: flex;
gap: 1rem;
padding-top: 0.75rem;
border-top: 1px solid hsl(var(--border) / 0.5);
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
}
.semantic-detail-meta span {
display: flex;
align-items: center;
gap: 0.375rem;
}
.semantic-viewer-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
background: hsl(var(--muted) / 0.3);
border-top: 1px solid hsl(var(--border));
}
.semantic-loading,
.semantic-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
text-align: center;
color: hsl(var(--muted-foreground));
}
.semantic-loading {
gap: 1rem;
}
/* ========================================
* CodexLens Test Search Results
* ======================================== */
#searchResults {
margin-top: 0.5rem;
}
#searchResults > div {
display: flex;
flex-direction: column;
max-height: 200px;
overflow: hidden;
background: hsl(var(--muted) / 0.3);
border-radius: 0.5rem;
padding: 0.75rem;
}
#searchResults .flex.items-center {
flex-shrink: 0;
margin-bottom: 0.5rem;
}
#searchResultContent {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
font-size: 0.75rem;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-all;
color: hsl(var(--foreground) / 0.9);
background: hsl(var(--background) / 0.5);
border-radius: 0.375rem;
padding: 0.5rem;
margin: 0;
}

View File

@@ -130,14 +130,14 @@
.stat-label { .stat-label {
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-secondary); color: hsl(var(--muted-foreground));
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
.stat-value { .stat-value {
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: hsl(var(--foreground));
} }
/* Memories Grid */ /* Memories Grid */
@@ -155,8 +155,8 @@
/* Memory Card */ /* Memory Card */
.memory-card { .memory-card {
background: var(--card-bg); background: hsl(var(--card));
border: 1px solid var(--border-color); border: 1px solid hsl(var(--border));
border-radius: 8px; border-radius: 8px;
padding: 1.25rem; padding: 1.25rem;
transition: all 0.2s ease; transition: all 0.2s ease;
@@ -184,7 +184,7 @@
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-secondary); color: hsl(var(--muted-foreground));
flex-wrap: wrap; flex-wrap: wrap;
} }
@@ -203,7 +203,7 @@
border: none; border: none;
padding: 0.25rem; padding: 0.25rem;
cursor: pointer; cursor: pointer;
color: var(--text-secondary); color: hsl(var(--muted-foreground));
transition: color 0.2s; transition: color 0.2s;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -211,11 +211,11 @@
} }
.icon-btn:hover { .icon-btn:hover {
color: var(--text-primary); color: hsl(var(--foreground));
} }
.icon-btn.danger:hover { .icon-btn.danger:hover {
color: var(--danger-color); color: hsl(var(--destructive));
} }
.icon-btn i { .icon-btn i {
@@ -231,13 +231,13 @@
.memory-summary { .memory-summary {
font-size: 0.9375rem; font-size: 0.9375rem;
line-height: 1.6; line-height: 1.6;
color: var(--text-primary); color: hsl(var(--foreground));
} }
.memory-preview { .memory-preview {
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1.5; line-height: 1.5;
color: var(--text-secondary); color: hsl(var(--muted-foreground));
} }
/* Memory Tags */ /* Memory Tags */
@@ -250,8 +250,8 @@
.tag { .tag {
padding: 0.25rem 0.75rem; padding: 0.25rem 0.75rem;
background: var(--accent-bg); background: hsl(var(--accent));
color: var(--accent-color); color: hsl(var(--primary));
border-radius: 4px; border-radius: 4px;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 500;
@@ -263,14 +263,14 @@
flex-direction: column; flex-direction: column;
gap: 0.75rem; gap: 0.75rem;
padding-top: 1rem; padding-top: 1rem;
border-top: 1px solid var(--border-color); border-top: 1px solid hsl(var(--border));
} }
.memory-meta { .memory-meta {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
font-size: 0.8125rem; font-size: 0.8125rem;
color: var(--text-secondary); color: hsl(var(--muted-foreground));
flex-wrap: wrap; flex-wrap: wrap;
} }
@@ -293,8 +293,8 @@
.feature-btn { .feature-btn {
padding: 0.375rem 0.75rem; padding: 0.375rem 0.75rem;
background: var(--secondary-bg); background: hsl(var(--muted));
border: 1px solid var(--border-color); border: 1px solid hsl(var(--border));
border-radius: 4px; border-radius: 4px;
font-size: 0.8125rem; font-size: 0.8125rem;
cursor: pointer; cursor: pointer;
@@ -302,12 +302,12 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.375rem; gap: 0.375rem;
color: var(--text-primary); color: hsl(var(--foreground));
} }
.feature-btn:hover { .feature-btn:hover {
background: var(--hover-bg); background: hsl(var(--hover));
border-color: var(--accent-color); border-color: hsl(var(--primary));
} }
.feature-btn i { .feature-btn i {
@@ -362,17 +362,17 @@
display: block; display: block;
font-weight: 500; font-weight: 500;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
color: var(--text-primary); color: hsl(var(--foreground));
} }
.form-group textarea, .form-group textarea,
.form-group input[type="text"] { .form-group input[type="text"] {
width: 100%; width: 100%;
padding: 0.75rem; padding: 0.75rem;
border: 1px solid var(--border-color); border: 1px solid hsl(var(--border));
border-radius: 6px; border-radius: 6px;
background: var(--input-bg); background: hsl(var(--background));
color: var(--text-primary); color: hsl(var(--foreground));
font-family: inherit; font-family: inherit;
font-size: 0.9375rem; font-size: 0.9375rem;
resize: vertical; resize: vertical;
@@ -381,8 +381,8 @@
.form-group textarea:focus, .form-group textarea:focus,
.form-group input[type="text"]:focus { .form-group input[type="text"]:focus {
outline: none; outline: none;
border-color: var(--accent-color); border-color: hsl(var(--primary));
box-shadow: 0 0 0 3px var(--accent-shadow, rgba(99, 102, 241, 0.1)); box-shadow: 0 0 0 3px hsl(var(--primary) / 0.1);
} }
/* Memory Detail Content */ /* Memory Detail Content */
@@ -396,19 +396,19 @@
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
color: var(--text-primary); color: hsl(var(--foreground));
} }
.detail-text { .detail-text {
font-size: 0.9375rem; font-size: 0.9375rem;
line-height: 1.6; line-height: 1.6;
color: var(--text-primary); color: hsl(var(--foreground));
white-space: pre-wrap; white-space: pre-wrap;
} }
.detail-code { .detail-code {
background: var(--secondary-bg); background: hsl(var(--muted));
border: 1px solid var(--border-color); border: 1px solid hsl(var(--border));
border-radius: 6px; border-radius: 6px;
padding: 1rem; padding: 1rem;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
@@ -430,7 +430,7 @@
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
color: var(--text-primary); color: hsl(var(--foreground));
} }
.entities-list, .entities-list,
@@ -442,8 +442,8 @@
.entity-item { .entity-item {
padding: 0.75rem; padding: 0.75rem;
background: var(--secondary-bg); background: hsl(var(--muted));
border: 1px solid var(--border-color); border: 1px solid hsl(var(--border));
border-radius: 6px; border-radius: 6px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -452,21 +452,21 @@
.entity-name { .entity-name {
font-weight: 500; font-weight: 500;
color: var(--text-primary); color: hsl(var(--foreground));
} }
.entity-type { .entity-type {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
background: var(--accent-bg); background: hsl(var(--accent));
color: var(--accent-color); color: hsl(var(--primary));
border-radius: 4px; border-radius: 4px;
font-size: 0.75rem; font-size: 0.75rem;
} }
.relationship-item { .relationship-item {
padding: 0.75rem; padding: 0.75rem;
background: var(--secondary-bg); background: hsl(var(--muted));
border: 1px solid var(--border-color); border: 1px solid hsl(var(--border));
border-radius: 6px; border-radius: 6px;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -477,13 +477,13 @@
.rel-target { .rel-target {
flex: 1; flex: 1;
font-weight: 500; font-weight: 500;
color: var(--text-primary); color: hsl(var(--foreground));
} }
.rel-type { .rel-type {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
background: var(--info-bg, #dbeafe); background: hsl(var(--info) / 0.15);
color: var(--info-color, #1e3a8a); color: hsl(var(--info));
border-radius: 4px; border-radius: 4px;
font-size: 0.75rem; font-size: 0.75rem;
white-space: nowrap; white-space: nowrap;
@@ -498,9 +498,9 @@
.evolution-version { .evolution-version {
padding: 1rem; padding: 1rem;
background: var(--secondary-bg); background: hsl(var(--muted));
border: 1px solid var(--border-color); border: 1px solid hsl(var(--border));
border-left: 3px solid var(--accent-color); border-left: 3px solid hsl(var(--primary));
border-radius: 6px; border-radius: 6px;
} }
@@ -513,17 +513,17 @@
.version-number { .version-number {
font-weight: 600; font-weight: 600;
color: var(--accent-color); color: hsl(var(--primary));
} }
.version-date { .version-date {
font-size: 0.8125rem; font-size: 0.8125rem;
color: var(--text-secondary); color: hsl(var(--muted-foreground));
} }
.version-reason { .version-reason {
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-primary); color: hsl(var(--foreground));
margin-top: 0.5rem; margin-top: 0.5rem;
} }
@@ -531,7 +531,7 @@
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 3rem 1rem; padding: 3rem 1rem;
color: var(--text-secondary); color: hsl(var(--muted-foreground));
} }
.empty-state i { .empty-state i {
@@ -546,7 +546,7 @@
} }
.empty-text { .empty-text {
color: var(--text-secondary); color: hsl(var(--muted-foreground));
font-style: italic; font-style: italic;
} }
@@ -573,6 +573,219 @@
} }
} }
/* ============================================
Knowledge Graph D3 Visualization Styles
============================================ */
.knowledge-graph-container {
width: 100%;
min-height: 400px;
background: hsl(var(--muted) / 0.3);
border: 1px solid hsl(var(--border));
border-radius: 8px;
overflow: hidden;
}
.knowledge-graph-svg {
display: block;
width: 100%;
height: 100%;
}
.graph-content {
cursor: grab;
}
.graph-content:active {
cursor: grabbing;
}
.graph-edge {
stroke: hsl(var(--muted-foreground));
stroke-width: 1.5;
stroke-opacity: 0.6;
fill: none;
}
.graph-node {
cursor: pointer;
transition: all 0.2s ease;
}
.graph-node:hover {
filter: brightness(1.2);
stroke-width: 3;
}
.graph-label {
font-size: 11px;
fill: hsl(var(--foreground));
pointer-events: none;
user-select: none;
}
.graph-node-group.file .graph-node { fill: #3b82f6; }
.graph-node-group.function .graph-node { fill: #10b981; }
.graph-node-group.module .graph-node { fill: #8b5cf6; }
.graph-node-group.class .graph-node { fill: #f59e0b; }
.graph-node-group.entity .graph-node { fill: #6b7280; }
.graph-error,
.graph-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
color: hsl(var(--muted-foreground));
text-align: center;
padding: 2rem;
}
.graph-error i,
.graph-empty-state i {
width: 48px;
height: 48px;
margin-bottom: 1rem;
opacity: 0.5;
}
.graph-error p,
.graph-empty-state p {
font-size: 0.9375rem;
margin: 0;
}
/* ============================================
Evolution Version Card Styles
============================================ */
.version-card {
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-left: 3px solid hsl(var(--primary));
border-radius: 8px;
padding: 1rem;
transition: all 0.2s ease;
}
.version-card:hover {
box-shadow: 0 2px 8px hsl(var(--foreground) / 0.08);
}
.version-card .version-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.version-card .version-info {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}
.version-card .version-number {
font-size: 0.875rem;
font-weight: 700;
color: hsl(var(--primary));
padding: 0.25rem 0.5rem;
background: hsl(var(--primary) / 0.1);
border-radius: 4px;
}
.version-card .version-date {
font-size: 0.8125rem;
color: hsl(var(--muted-foreground));
}
.version-content-preview {
font-size: 0.875rem;
line-height: 1.5;
color: hsl(var(--muted-foreground));
background: hsl(var(--muted) / 0.5);
padding: 0.75rem;
border-radius: 6px;
margin-bottom: 0.75rem;
white-space: pre-wrap;
word-break: break-word;
}
.version-diff-stats {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.diff-stat {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.75rem;
font-weight: 500;
padding: 0.25rem 0.625rem;
border-radius: 4px;
}
.diff-stat i {
width: 12px;
height: 12px;
}
.diff-stat.diff-added {
background: hsl(142 76% 36% / 0.15);
color: hsl(142 76% 30%);
}
.diff-stat.diff-modified {
background: hsl(38 92% 50% / 0.15);
color: hsl(38 92% 40%);
}
.diff-stat.diff-deleted {
background: hsl(0 84% 60% / 0.15);
color: hsl(0 84% 45%);
}
.version-card .version-reason {
font-size: 0.8125rem;
color: hsl(var(--foreground));
padding-top: 0.75rem;
border-top: 1px solid hsl(var(--border));
margin-top: 0;
}
.version-card .version-reason strong {
color: hsl(var(--muted-foreground));
font-weight: 600;
}
.evolution-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px;
color: hsl(var(--muted-foreground));
text-align: center;
padding: 2rem;
}
.evolution-empty-state i {
width: 48px;
height: 48px;
margin-bottom: 1rem;
opacity: 0.5;
}
.evolution-empty-state p {
font-size: 0.9375rem;
margin: 0;
}
/* Dark Mode Adjustments */ /* Dark Mode Adjustments */
[data-theme="dark"] .memory-card { [data-theme="dark"] .memory-card {
background: #1e293b; background: #1e293b;
@@ -594,3 +807,25 @@
background: #1e293b; background: #1e293b;
border-color: #334155; border-color: #334155;
} }
[data-theme="dark"] .knowledge-graph-container {
background: #0f172a;
border-color: #334155;
}
[data-theme="dark"] .graph-label {
fill: #e2e8f0;
}
[data-theme="dark"] .graph-edge {
stroke: #64748b;
}
[data-theme="dark"] .version-card {
background: #1e293b;
border-color: #334155;
}
[data-theme="dark"] .version-content-preview {
background: #0f172a;
}

View File

@@ -92,6 +92,15 @@ async function loadCodexLensStatus() {
const data = await response.json(); const data = await response.json();
codexLensStatus = data; codexLensStatus = data;
// Expose to window for other modules (e.g., codexlens-manager.js)
if (!window.cliToolsStatus) {
window.cliToolsStatus = {};
}
window.cliToolsStatus.codexlens = {
installed: data.ready || false,
version: data.version || null
};
// Update CodexLens badge // Update CodexLens badge
updateCodexLensBadge(); updateCodexLensBadge();

View File

@@ -39,7 +39,27 @@ async function loadIndexStats() {
*/ */
function renderIndexCard() { function renderIndexCard() {
const container = document.getElementById('indexCard'); const container = document.getElementById('indexCard');
if (!container || !indexData) return; if (!container) {
console.warn('[IndexManager] Container not found');
return;
}
// Handle case when data is not loaded yet
if (!indexData) {
container.innerHTML = `
<div class="bg-card border border-border rounded-lg overflow-hidden">
<div class="bg-muted/30 border-b border-border px-4 py-3 flex items-center gap-2">
<i data-lucide="database" class="w-4 h-4 text-primary"></i>
<span class="font-medium text-foreground">${t('index.manager') || 'Index Manager'}</span>
</div>
<div class="p-4 text-center text-muted-foreground">
<div class="animate-pulse">${t('common.loading') || 'Loading...'}</div>
</div>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
return;
}
const { indexDir, indexes, summary } = indexData; const { indexDir, indexes, summary } = indexData;
@@ -64,113 +84,113 @@ function renderIndexCard() {
if (indexes && indexes.length > 0) { if (indexes && indexes.length > 0) {
indexes.forEach(function(idx) { indexes.forEach(function(idx) {
const vectorBadge = idx.hasVectorIndex const vectorBadge = idx.hasVectorIndex
? '<span class="text-xs px-1.5 py-0.5 bg-primary/10 text-primary rounded">' + (t('index.vector') || 'Vector') + '</span>' ? `<span class="text-xs px-1.5 py-0.5 bg-primary/10 text-primary rounded">${t('index.vector') || 'Vector'}</span>`
: ''; : '';
const normalBadge = idx.hasNormalIndex const normalBadge = idx.hasNormalIndex
? '<span class="text-xs px-1.5 py-0.5 bg-muted text-muted-foreground rounded">' + (t('index.fts') || 'FTS') + '</span>' ? `<span class="text-xs px-1.5 py-0.5 bg-muted text-muted-foreground rounded">${t('index.fts') || 'FTS'}</span>`
: ''; : '';
indexRows += '\ indexRows += `
<tr class="border-t border-border hover:bg-muted/30 transition-colors">\ <tr class="border-t border-border hover:bg-muted/30 transition-colors">
<td class="py-2 px-2 text-foreground">\ <td class="py-2 px-2 text-foreground">
<div class="flex items-center gap-2">\ <div class="flex items-center gap-2">
<span class="font-mono text-xs truncate max-w-[250px]" title="' + escapeHtml(idx.id) + '">' + escapeHtml(idx.id) + '</span>\ <span class="font-mono text-xs truncate max-w-[250px]" title="${escapeHtml(idx.id)}">${escapeHtml(idx.id)}</span>
</div>\ </div>
</td>\ </td>
<td class="py-2 px-2 text-right text-muted-foreground">' + idx.sizeFormatted + '</td>\ <td class="py-2 px-2 text-right text-muted-foreground">${idx.sizeFormatted}</td>
<td class="py-2 px-2 text-center">\ <td class="py-2 px-2 text-center">
<div class="flex items-center justify-center gap-1">' + vectorBadge + normalBadge + '</div>\ <div class="flex items-center justify-center gap-1">${vectorBadge}${normalBadge}</div>
</td>\ </td>
<td class="py-2 px-2 text-right text-muted-foreground">' + formatTimeAgo(idx.lastModified) + '</td>\ <td class="py-2 px-2 text-right text-muted-foreground">${formatTimeAgo(idx.lastModified)}</td>
<td class="py-2 px-1 text-center">\ <td class="py-2 px-1 text-center">
<button onclick="cleanIndexProject(\'' + escapeHtml(idx.id) + '\')" \ <button onclick="cleanIndexProject('${escapeHtml(idx.id)}')"
class="text-destructive/70 hover:text-destructive p-1 rounded hover:bg-destructive/10 transition-colors" \ class="text-destructive/70 hover:text-destructive p-1 rounded hover:bg-destructive/10 transition-colors"
title="' + (t('index.cleanProject') || 'Clean Index') + '">\ title="${t('index.cleanProject') || 'Clean Index'}">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>\ <i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
</button>\ </button>
</td>\ </td>
</tr>\ </tr>
'; `;
}); });
} else { } else {
indexRows = '\ indexRows = `
<tr>\ <tr>
<td colspan="5" class="py-4 text-center text-muted-foreground text-sm">' + (t('index.noIndexes') || 'No indexes yet') + '</td>\ <td colspan="5" class="py-4 text-center text-muted-foreground text-sm">${t('index.noIndexes') || 'No indexes yet'}</td>
</tr>\ </tr>
'; `;
} }
container.innerHTML = '\ container.innerHTML = `
<div class="bg-card border border-border rounded-lg overflow-hidden">\ <div class="bg-card border border-border rounded-lg overflow-hidden">
<div class="bg-muted/30 border-b border-border px-4 py-3 flex items-center justify-between">\ <div class="bg-muted/30 border-b border-border px-4 py-3 flex items-center justify-between">
<div class="flex items-center gap-2">\ <div class="flex items-center gap-2">
<i data-lucide="database" class="w-4 h-4 text-primary"></i>\ <i data-lucide="database" class="w-4 h-4 text-primary"></i>
<span class="font-medium text-foreground">' + (t('index.manager') || 'Index Manager') + '</span>\ <span class="font-medium text-foreground">${t('index.manager') || 'Index Manager'}</span>
<span class="text-xs px-2 py-0.5 bg-muted rounded-full text-muted-foreground">' + (summary?.totalSizeFormatted || '0 B') + '</span>\ <span class="text-xs px-2 py-0.5 bg-muted rounded-full text-muted-foreground">${summary?.totalSizeFormatted || '0 B'}</span>
</div>\ </div>
<div class="flex items-center gap-2">\ <div class="flex items-center gap-2">
<button onclick="loadIndexStats()" class="text-xs px-2 py-1 text-muted-foreground hover:text-foreground hover:bg-muted rounded transition-colors" title="' + (t('common.refresh') || 'Refresh') + '">\ <button onclick="loadIndexStats()" class="text-xs px-2 py-1 text-muted-foreground hover:text-foreground hover:bg-muted rounded transition-colors" title="${t('common.refresh') || 'Refresh'}">
<i data-lucide="refresh-cw" class="w-3.5 h-3.5"></i>\ <i data-lucide="refresh-cw" class="w-3.5 h-3.5"></i>
</button>\ </button>
<button onclick="showCodexLensConfigModal()" class="text-xs px-2 py-1 text-muted-foreground hover:text-foreground hover:bg-muted rounded transition-colors" title="' + (t('common.settings') || 'Settings') + '">\ <button onclick="showCodexLensConfigModal()" class="text-xs px-2 py-1 text-muted-foreground hover:text-foreground hover:bg-muted rounded transition-colors" title="${t('common.settings') || 'Settings'}">
<i data-lucide="settings" class="w-3.5 h-3.5"></i>\ <i data-lucide="settings" class="w-3.5 h-3.5"></i>
</button>\ </button>
</div>\ </div>
</div>\ </div>
<div class="p-4">\ <div class="p-4">
<div class="flex items-center gap-2 mb-3 text-xs text-muted-foreground">\ <div class="flex items-center gap-2 mb-3 text-xs text-muted-foreground">
<i data-lucide="folder" class="w-3.5 h-3.5"></i>\ <i data-lucide="folder" class="w-3.5 h-3.5"></i>
<span class="font-mono truncate" title="' + escapeHtml(indexDir || '') + '">' + escapeHtml(indexDir || t('index.notConfigured') || 'Not configured') + '</span>\ <span class="font-mono truncate" title="${escapeHtml(indexDir || '')}">${escapeHtml(indexDir || t('index.notConfigured') || 'Not configured')}</span>
</div>\ </div>
<div class="grid grid-cols-4 gap-3 mb-4">\ <div class="grid grid-cols-4 gap-3 mb-4">
<div class="bg-muted/30 rounded-lg p-3 text-center">\ <div class="bg-muted/30 rounded-lg p-3 text-center">
<div class="text-lg font-semibold text-foreground">' + (summary?.totalProjects || 0) + '</div>\ <div class="text-lg font-semibold text-foreground">${summary?.totalProjects || 0}</div>
<div class="text-xs text-muted-foreground">' + (t('index.projects') || 'Projects') + '</div>\ <div class="text-xs text-muted-foreground">${t('index.projects') || 'Projects'}</div>
</div>\ </div>
<div class="bg-muted/30 rounded-lg p-3 text-center">\ <div class="bg-muted/30 rounded-lg p-3 text-center">
<div class="text-lg font-semibold text-foreground">' + (summary?.totalSizeFormatted || '0 B') + '</div>\ <div class="text-lg font-semibold text-foreground">${summary?.totalSizeFormatted || '0 B'}</div>
<div class="text-xs text-muted-foreground">' + (t('index.totalSize') || 'Total Size') + '</div>\ <div class="text-xs text-muted-foreground">${t('index.totalSize') || 'Total Size'}</div>
</div>\ </div>
<div class="bg-muted/30 rounded-lg p-3 text-center">\ <div class="bg-muted/30 rounded-lg p-3 text-center">
<div class="text-lg font-semibold text-foreground">' + (summary?.vectorIndexCount || 0) + '</div>\ <div class="text-lg font-semibold text-foreground">${summary?.vectorIndexCount || 0}</div>
<div class="text-xs text-muted-foreground">' + (t('index.vectorIndexes') || 'Vector') + '</div>\ <div class="text-xs text-muted-foreground">${t('index.vectorIndexes') || 'Vector'}</div>
</div>\ </div>
<div class="bg-muted/30 rounded-lg p-3 text-center">\ <div class="bg-muted/30 rounded-lg p-3 text-center">
<div class="text-lg font-semibold text-foreground">' + (summary?.normalIndexCount || 0) + '</div>\ <div class="text-lg font-semibold text-foreground">${summary?.normalIndexCount || 0}</div>
<div class="text-xs text-muted-foreground">' + (t('index.ftsIndexes') || 'FTS') + '</div>\ <div class="text-xs text-muted-foreground">${t('index.ftsIndexes') || 'FTS'}</div>
</div>\ </div>
</div>\ </div>
<div class="border border-border rounded-lg overflow-hidden">\ <div class="border border-border rounded-lg overflow-hidden">
<table class="w-full text-sm">\ <table class="w-full text-sm">
<thead class="bg-muted/50">\ <thead class="bg-muted/50">
<tr class="text-xs text-muted-foreground">\ <tr class="text-xs text-muted-foreground">
<th class="py-2 px-2 text-left font-medium">' + (t('index.projectId') || 'Project ID') + '</th>\ <th class="py-2 px-2 text-left font-medium">${t('index.projectId') || 'Project ID'}</th>
<th class="py-2 px-2 text-right font-medium">' + (t('index.size') || 'Size') + '</th>\ <th class="py-2 px-2 text-right font-medium">${t('index.size') || 'Size'}</th>
<th class="py-2 px-2 text-center font-medium">' + (t('index.type') || 'Type') + '</th>\ <th class="py-2 px-2 text-center font-medium">${t('index.type') || 'Type'}</th>
<th class="py-2 px-2 text-right font-medium">' + (t('index.lastModified') || 'Modified') + '</th>\ <th class="py-2 px-2 text-right font-medium">${t('index.lastModified') || 'Modified'}</th>
<th class="py-2 px-1 w-8"></th>\ <th class="py-2 px-1 w-8"></th>
</tr>\ </tr>
</thead>\ </thead>
<tbody>\ <tbody>
' + indexRows + '\ ${indexRows}
</tbody>\ </tbody>
</table>\ </table>
</div>\ </div>
<div class="mt-4 flex justify-between items-center gap-2">\ <div class="mt-4 flex justify-between items-center gap-2">
<button onclick="initCodexLensIndex()" \ <button onclick="initCodexLensIndex()"
class="text-xs px-3 py-1.5 bg-primary/10 text-primary hover:bg-primary/20 rounded transition-colors flex items-center gap-1.5">\ class="text-xs px-3 py-1.5 bg-primary/10 text-primary hover:bg-primary/20 rounded transition-colors flex items-center gap-1.5">
<i data-lucide="database" class="w-3.5 h-3.5"></i>\ <i data-lucide="database" class="w-3.5 h-3.5"></i>
' + (t('index.initCurrent') || 'Init Current Project') + '\ ${t('index.initCurrent') || 'Init Current Project'}
</button>\ </button>
<button onclick="cleanAllIndexesConfirm()" \ <button onclick="cleanAllIndexesConfirm()"
class="text-xs px-3 py-1.5 bg-destructive/10 text-destructive hover:bg-destructive/20 rounded transition-colors flex items-center gap-1.5">\ class="text-xs px-3 py-1.5 bg-destructive/10 text-destructive hover:bg-destructive/20 rounded transition-colors flex items-center gap-1.5">
<i data-lucide="trash" class="w-3.5 h-3.5"></i>\ <i data-lucide="trash" class="w-3.5 h-3.5"></i>
' + (t('index.cleanAll') || 'Clean All') + '\ ${t('index.cleanAll') || 'Clean All'}
</button>\ </button>
</div>\ </div>
</div>\ </div>
</div>\ </div>
'; `;
// Reinitialize Lucide icons // Reinitialize Lucide icons
if (typeof lucide !== 'undefined') { if (typeof lucide !== 'undefined') {
@@ -185,24 +205,24 @@ function renderIndexCardError(errorMessage) {
const container = document.getElementById('indexCard'); const container = document.getElementById('indexCard');
if (!container) return; if (!container) return;
container.innerHTML = '\ container.innerHTML = `
<div class="bg-card border border-border rounded-lg overflow-hidden">\ <div class="bg-card border border-border rounded-lg overflow-hidden">
<div class="bg-muted/30 border-b border-border px-4 py-3 flex items-center gap-2">\ <div class="bg-muted/30 border-b border-border px-4 py-3 flex items-center gap-2">
<i data-lucide="database" class="w-4 h-4 text-primary"></i>\ <i data-lucide="database" class="w-4 h-4 text-primary"></i>
<span class="font-medium text-foreground">' + (t('index.manager') || 'Index Manager') + '</span>\ <span class="font-medium text-foreground">${t('index.manager') || 'Index Manager'}</span>
</div>\ </div>
<div class="p-4 text-center">\ <div class="p-4 text-center">
<div class="text-destructive mb-2">\ <div class="text-destructive mb-2">
<i data-lucide="alert-circle" class="w-8 h-8 mx-auto"></i>\ <i data-lucide="alert-circle" class="w-8 h-8 mx-auto"></i>
</div>\ </div>
<p class="text-sm text-muted-foreground mb-3">' + escapeHtml(errorMessage) + '</p>\ <p class="text-sm text-muted-foreground mb-3">${escapeHtml(errorMessage)}</p>
<button onclick="loadIndexStats()" \ <button onclick="loadIndexStats()"
class="text-xs px-3 py-1.5 bg-primary text-primary-foreground hover:bg-primary/90 rounded transition-colors">\ class="text-xs px-3 py-1.5 bg-primary text-primary-foreground hover:bg-primary/90 rounded transition-colors">
' + (t('common.retry') || 'Retry') + '\ ${t('common.retry') || 'Retry'}
</button>\ </button>
</div>\ </div>
</div>\ </div>
'; `;
// Reinitialize Lucide icons // Reinitialize Lucide icons
if (typeof lucide !== 'undefined') { if (typeof lucide !== 'undefined') {

View File

@@ -45,10 +45,26 @@ function showModal(title, content, options = {}) {
} }
function closeModal() { function closeModal() {
const overlay = document.querySelector('.generic-modal-overlay'); // Try generic modal overlay first
if (overlay) { const genericOverlay = document.querySelector('.generic-modal-overlay');
overlay.classList.remove('active'); if (genericOverlay) {
setTimeout(() => overlay.remove(), 200); genericOverlay.classList.remove('active');
setTimeout(() => genericOverlay.remove(), 200);
return;
}
// Try CodexLens config modal
const codexLensModal = document.getElementById('codexlensConfigModal');
if (codexLensModal) {
codexLensModal.remove();
return;
}
// Try any modal-backdrop
const modalBackdrop = document.querySelector('.modal-backdrop');
if (modalBackdrop) {
modalBackdrop.remove();
return;
} }
} }

View File

@@ -19,9 +19,10 @@ async function showCodexLensConfigModal() {
const modalHtml = buildCodexLensConfigContent(config); const modalHtml = buildCodexLensConfigContent(config);
// Create and show modal // Create and show modal
const modalContainer = document.createElement('div'); const tempContainer = document.createElement('div');
modalContainer.innerHTML = modalHtml; tempContainer.innerHTML = modalHtml;
document.body.appendChild(modalContainer); const modal = tempContainer.firstElementChild;
document.body.appendChild(modal);
// Initialize icons // Initialize icons
if (window.lucide) lucide.createIcons(); if (window.lucide) lucide.createIcons();
@@ -65,11 +66,21 @@ function buildCodexLensConfigContent(config) {
'<div class="flex items-center gap-4 text-sm">' + '<div class="flex items-center gap-4 text-sm">' +
'<div class="flex items-center gap-2">' + '<div class="flex items-center gap-2">' +
'<span class="text-muted-foreground">' + t('codexlens.currentWorkspace') + ':</span>' + '<span class="text-muted-foreground">' + t('codexlens.currentWorkspace') + ':</span>' +
'<span class="font-medium">' + (isInstalled ? t('codexlens.installed') : t('codexlens.notInstalled')) + '</span>' + (isInstalled
? '<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-success/10 text-success border border-success/20">' +
'<i data-lucide="check-circle" class="w-3.5 h-3.5"></i>' +
t('codexlens.installed') +
'</span>'
: '<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-muted text-muted-foreground border border-border">' +
'<i data-lucide="circle" class="w-3.5 h-3.5"></i>' +
t('codexlens.notInstalled') +
'</span>') +
'</div>' + '</div>' +
'<div class="flex items-center gap-2">' + '<div class="flex items-center gap-2">' +
'<span class="text-muted-foreground">' + t('codexlens.indexes') + ':</span>' + '<span class="text-muted-foreground">' + t('codexlens.indexes') + ':</span>' +
'<span class="font-medium">' + indexCount + '</span>' + '<span class="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium bg-primary/10 text-primary border border-primary/20">' +
indexCount +
'</span>' +
'</div>' + '</div>' +
'</div>' + '</div>' +
'</div>' + '</div>' +
@@ -106,20 +117,20 @@ function buildCodexLensConfigContent(config) {
'<h4>' + t('codexlens.actions') + '</h4>' + '<h4>' + t('codexlens.actions') + '</h4>' +
'<div class="tool-config-actions">' + '<div class="tool-config-actions">' +
(isInstalled (isInstalled
? '<button class="btn-sm btn-outline" onclick="initCodexLensIndex()">' + ? '<button class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border border-primary/30 bg-primary/5 text-primary hover:bg-primary/10 transition-colors" onclick="initCodexLensIndex()">' +
'<i data-lucide="database" class="w-3 h-3"></i> ' + t('codexlens.initializeIndex') + '<i data-lucide="database" class="w-3.5 h-3.5"></i> ' + t('codexlens.initializeIndex') +
'</button>' + '</button>' +
'<button class="btn-sm btn-outline" onclick="cleanCurrentWorkspaceIndex()">' + '<button class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border border-border bg-background hover:bg-muted/50 transition-colors" onclick="cleanCurrentWorkspaceIndex()">' +
'<i data-lucide="folder-x" class="w-3 h-3"></i> ' + t('codexlens.cleanCurrentWorkspace') + '<i data-lucide="folder-x" class="w-3.5 h-3.5"></i> ' + t('codexlens.cleanCurrentWorkspace') +
'</button>' + '</button>' +
'<button class="btn-sm btn-outline" onclick="cleanCodexLensIndexes()">' + '<button class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border border-border bg-background hover:bg-muted/50 transition-colors" onclick="cleanCodexLensIndexes()">' +
'<i data-lucide="trash" class="w-3 h-3"></i> ' + t('codexlens.cleanAllIndexes') + '<i data-lucide="trash" class="w-3.5 h-3.5"></i> ' + t('codexlens.cleanAllIndexes') +
'</button>' + '</button>' +
'<button class="btn-sm btn-outline btn-danger" onclick="uninstallCodexLens()">' + '<button class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border border-destructive/30 bg-destructive/5 text-destructive hover:bg-destructive/10 transition-colors" onclick="uninstallCodexLens()">' +
'<i data-lucide="trash-2" class="w-3 h-3"></i> ' + t('cli.uninstall') + '<i data-lucide="trash-2" class="w-3.5 h-3.5"></i> ' + t('cli.uninstall') +
'</button>' '</button>'
: '<button class="btn-sm btn-primary" onclick="installCodexLens()">' + : '<button class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors" onclick="installCodexLens()">' +
'<i data-lucide="download" class="w-3 h-3"></i> ' + t('codexlens.installCodexLens') + '<i data-lucide="download" class="w-3.5 h-3.5"></i> ' + t('codexlens.installCodexLens') +
'</button>') + '</button>') +
'</div>' + '</div>' +
'</div>' + '</div>' +
@@ -172,12 +183,12 @@ function buildCodexLensConfigContent(config) {
'</button>' + '</button>' +
'</div>' + '</div>' +
'<div id="searchResults" class="hidden">' + '<div id="searchResults" class="hidden">' +
'<div class="bg-muted/30 rounded-lg p-3 max-h-64 overflow-y-auto">' + '<div>' +
'<div class="flex items-center justify-between mb-2">' + '<div class="flex items-center justify-between">' +
'<p class="text-sm font-medium">' + t('codexlens.results') + ':</p>' + '<p class="text-sm font-medium">' + t('codexlens.results') + ':</p>' +
'<span id="searchResultCount" class="text-xs text-muted-foreground"></span>' + '<span id="searchResultCount" class="text-xs text-muted-foreground"></span>' +
'</div>' + '</div>' +
'<pre id="searchResultContent" class="text-xs font-mono whitespace-pre-wrap break-all"></pre>' + '<pre id="searchResultContent"></pre>' +
'</div>' + '</div>' +
'</div>' + '</div>' +
'</div>' + '</div>' +

View File

@@ -758,11 +758,12 @@ async function executeCliTool(
const startTime = Date.now(); const startTime = Date.now();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Direct spawn without shell - CLI tools (codex/gemini/qwen) don't need shell wrapper // Windows requires shell: true for npm global commands (.cmd files)
// This avoids Windows cmd.exe ENOENT errors and simplifies argument handling // Unix-like systems can use shell: false for direct execution
const isWindows = process.platform === 'win32';
const child = spawn(command, args, { const child = spawn(command, args, {
cwd: workingDir, cwd: workingDir,
shell: false, shell: isWindows, // Enable shell on Windows for .cmd files
stdio: [useStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'] stdio: [useStdin ? 'pipe' : 'ignore', 'pipe', 'pipe']
}); });

View File

@@ -39,10 +39,16 @@ const ParamsSchema = z.object({
'init', 'init',
'search', 'search',
'search_files', 'search_files',
'status',
'symbol',
'check',
'update',
'bootstrap',
]), ]),
path: z.string().optional(), path: z.string().optional(),
query: z.string().optional(), query: z.string().optional(),
mode: z.enum(['auto', 'text', 'semantic', 'exact', 'fuzzy', 'hybrid', 'vector', 'pure-vector']).default('auto'), mode: z.enum(['auto', 'text', 'semantic', 'exact', 'fuzzy', 'hybrid', 'vector', 'pure-vector']).default('auto'),
format: z.enum(['json', 'text', 'pretty']).default('json'),
languages: z.array(z.string()).optional(), languages: z.array(z.string()).optional(),
limit: z.number().default(20), limit: z.number().default(20),
// Additional fields for internal functions // Additional fields for internal functions
@@ -790,8 +796,13 @@ Note: For advanced operations (config, status, clean), use CLI directly: codexle
'init', 'init',
'search', 'search',
'search_files', 'search_files',
'status',
'symbol',
'check',
'update',
'bootstrap',
], ],
description: 'Action to perform: init (index directory), search (search code), search_files (search files only)', description: 'Action to perform: init/update (index directory), search (search code), search_files (search files only), status (index status), symbol (extract symbols), check (check if ready), bootstrap (setup venv)',
}, },
path: { path: {
type: 'string', type: 'string',
@@ -807,6 +818,12 @@ Note: For advanced operations (config, status, clean), use CLI directly: codexle
description: 'Search mode: auto (default, hybrid if embeddings exist), text/exact (FTS), hybrid (best), fuzzy, vector, semantic/pure-vector', description: 'Search mode: auto (default, hybrid if embeddings exist), text/exact (FTS), hybrid (best), fuzzy, vector, semantic/pure-vector',
default: 'auto', default: 'auto',
}, },
format: {
type: 'string',
enum: ['json', 'text', 'pretty'],
description: 'Output format: json (default), text, pretty',
default: 'json',
},
languages: { languages: {
type: 'array', type: 'array',
items: { type: 'string' }, items: { type: 'string' },
@@ -847,9 +864,41 @@ export async function handler(params: Record<string, unknown>): Promise<ToolResu
result = await searchFiles(parsed.data); result = await searchFiles(parsed.data);
break; break;
case 'status':
result = await getStatus(parsed.data);
break;
case 'symbol':
result = await extractSymbols(parsed.data);
break;
case 'check':
const checkStatus = await ensureReady();
result = {
success: checkStatus.ready,
ready: checkStatus.ready,
version: checkStatus.version,
error: checkStatus.error,
};
break;
case 'update':
// Update is an alias for init (incremental update)
result = await initIndex(parsed.data);
break;
case 'bootstrap':
const bootstrapResult = await bootstrapVenv();
result = {
success: bootstrapResult.success,
message: bootstrapResult.message,
error: bootstrapResult.error,
};
break;
default: default:
throw new Error( throw new Error(
`Unknown action: ${action}. Valid actions: init, search, search_files` `Unknown action: ${action}. Valid actions: init, search, search_files, status, symbol, check, update, bootstrap`
); );
} }

View File

@@ -141,9 +141,11 @@ async function checkIndexStatus(path: string = '.'): Promise<IndexStatus> {
try { try {
// Strip ANSI color codes from JSON output // Strip ANSI color codes from JSON output
const cleanOutput = (result.output || '{}').replace(/\x1b\[[0-9;]*m/g, ''); const cleanOutput = (result.output || '{}').replace(/\x1b\[[0-9;]*m/g, '');
const status = JSON.parse(cleanOutput); const parsed = JSON.parse(cleanOutput);
const indexed = status.indexed === true || status.file_count > 0; // Handle both direct and nested response formats (status returns {success, result: {...}})
const status = parsed.result || parsed;
const indexed = status.projects_count > 0 || status.total_files > 0;
// Get embeddings coverage from comprehensive status // Get embeddings coverage from comprehensive status
const embeddingsData = status.embeddings || {}; const embeddingsData = status.embeddings || {};
const embeddingsCoverage = embeddingsData.coverage_percent || 0; const embeddingsCoverage = embeddingsData.coverage_percent || 0;
@@ -161,7 +163,7 @@ async function checkIndexStatus(path: string = '.'): Promise<IndexStatus> {
return { return {
indexed, indexed,
has_embeddings, has_embeddings,
file_count: status.file_count, file_count: status.total_files,
embeddings_coverage_percent: embeddingsCoverage, embeddings_coverage_percent: embeddingsCoverage,
warning, warning,
}; };

View File

@@ -376,7 +376,7 @@ class DirIndexStore:
conn.execute("DELETE FROM symbols WHERE file_id=?", (file_id,)) conn.execute("DELETE FROM symbols WHERE file_id=?", (file_id,))
if symbols: if symbols:
# Insert symbols without token_count and symbol_type # Insert symbols
symbol_rows = [] symbol_rows = []
for s in symbols: for s in symbols:
symbol_rows.append( symbol_rows.append(
@@ -819,22 +819,23 @@ class DirIndexStore:
return results return results
else: else:
# Fallback to original query for backward compatibility # Fallback using normalized tables with contains matching (slower but more flexible)
keyword_pattern = f"%{keyword}%" keyword_pattern = f"%{keyword}%"
rows = conn.execute( rows = conn.execute(
""" """
SELECT f.id, f.name, f.full_path, f.language, f.mtime, f.line_count, sm.keywords SELECT f.id, f.name, f.full_path, f.language, f.mtime, f.line_count,
GROUP_CONCAT(k.keyword, ',') as keywords
FROM files f FROM files f
JOIN semantic_metadata sm ON f.id = sm.file_id JOIN file_keywords fk ON f.id = fk.file_id
WHERE sm.keywords LIKE ? COLLATE NOCASE JOIN keywords k ON fk.keyword_id = k.id
WHERE k.keyword LIKE ? COLLATE NOCASE
GROUP BY f.id, f.name, f.full_path, f.language, f.mtime, f.line_count
ORDER BY f.name ORDER BY f.name
""", """,
(keyword_pattern,), (keyword_pattern,),
).fetchall() ).fetchall()
import json
results = [] results = []
for row in rows: for row in rows:
file_entry = FileEntry( file_entry = FileEntry(
@@ -845,7 +846,7 @@ class DirIndexStore:
mtime=float(row["mtime"]) if row["mtime"] else 0.0, mtime=float(row["mtime"]) if row["mtime"] else 0.0,
line_count=int(row["line_count"]) if row["line_count"] else 0, line_count=int(row["line_count"]) if row["line_count"] else 0,
) )
keywords = json.loads(row["keywords"]) if row["keywords"] else [] keywords = row["keywords"].split(',') if row["keywords"] else []
results.append((file_entry, keywords)) results.append((file_entry, keywords))
return results return results
@@ -1432,7 +1433,7 @@ class DirIndexStore:
""" """
) )
# Symbols table (v5: removed token_count and symbol_type) # Symbols table with token metadata
conn.execute( conn.execute(
""" """
CREATE TABLE IF NOT EXISTS symbols ( CREATE TABLE IF NOT EXISTS symbols (

View File

@@ -143,6 +143,9 @@ class IndexTreeBuilder:
index_root = self.mapper.source_to_index_dir(source_root) index_root = self.mapper.source_to_index_dir(source_root)
project_info = self.registry.register_project(source_root, index_root) project_info = self.registry.register_project(source_root, index_root)
# Report progress: discovering files (5%)
print("Discovering files...", flush=True)
# Collect directories by depth # Collect directories by depth
dirs_by_depth = self._collect_dirs_by_depth(source_root, languages) dirs_by_depth = self._collect_dirs_by_depth(source_root, languages)
@@ -157,6 +160,13 @@ class IndexTreeBuilder:
errors=["No indexable directories found"], errors=["No indexable directories found"],
) )
# Calculate total directories for progress tracking
total_dirs_to_process = sum(len(dirs) for dirs in dirs_by_depth.values())
processed_dirs = 0
# Report progress: building index (10%)
print("Building index...", flush=True)
total_files = 0 total_files = 0
total_dirs = 0 total_dirs = 0
all_errors: List[str] = [] all_errors: List[str] = []
@@ -179,10 +189,17 @@ class IndexTreeBuilder:
for result in results: for result in results:
if result.error: if result.error:
all_errors.append(f"{result.source_path}: {result.error}") all_errors.append(f"{result.source_path}: {result.error}")
processed_dirs += 1
continue continue
total_files += result.files_count total_files += result.files_count
total_dirs += 1 total_dirs += 1
processed_dirs += 1
# Report progress for each processed directory (10-80%)
# Use "Processing file" format for frontend parser compatibility
progress_percent = 10 + int((processed_dirs / total_dirs_to_process) * 70)
print(f"Processing file {processed_dirs}/{total_dirs_to_process}: {result.source_path.name}", flush=True)
# Register directory in registry # Register directory in registry
self.registry.register_dir( self.registry.register_dir(
@@ -193,6 +210,9 @@ class IndexTreeBuilder:
files_count=result.files_count, files_count=result.files_count,
) )
# Report progress: linking subdirectories (80%)
print("Linking subdirectories...", flush=True)
# After building all directories, link subdirectories to parents # After building all directories, link subdirectories to parents
# This needs to happen after all indexes exist # This needs to happen after all indexes exist
for result in all_results: for result in all_results:
@@ -203,6 +223,8 @@ class IndexTreeBuilder:
# Cleanup deleted files if in incremental mode # Cleanup deleted files if in incremental mode
if use_incremental: if use_incremental:
# Report progress: cleaning up (90%)
print("Cleaning up deleted files...", flush=True)
self.logger.info("Cleaning up deleted files...") self.logger.info("Cleaning up deleted files...")
total_deleted = 0 total_deleted = 0
for result in all_results: for result in all_results:
@@ -220,9 +242,15 @@ class IndexTreeBuilder:
if total_deleted > 0: if total_deleted > 0:
self.logger.info("Removed %d deleted files from index", total_deleted) self.logger.info("Removed %d deleted files from index", total_deleted)
# Report progress: finalizing (95%)
print("Finalizing...", flush=True)
# Update project statistics # Update project statistics
self.registry.update_project_stats(source_root, total_files, total_dirs) self.registry.update_project_stats(source_root, total_files, total_dirs)
# Report completion (100%)
print(f"Indexed {total_files} files", flush=True)
self.logger.info( self.logger.info(
"Index build complete: %d files, %d directories, %d errors", "Index build complete: %d files, %d directories, %d errors",
total_files, total_files,

View File

@@ -102,7 +102,7 @@ class MigrationManager:
This method checks the current database version and applies all This method checks the current database version and applies all
subsequent migrations in order. Each migration is applied within subsequent migrations in order. Each migration is applied within
a transaction. a transaction, unless the migration manages its own transactions.
""" """
current_version = self.get_current_version() current_version = self.get_current_version()
log.info(f"Current database schema version: {current_version}") log.info(f"Current database schema version: {current_version}")
@@ -111,21 +111,36 @@ class MigrationManager:
if migration.version > current_version: if migration.version > current_version:
log.info(f"Applying migration {migration.version}: {migration.name}...") log.info(f"Applying migration {migration.version}: {migration.name}...")
try: try:
self.db_conn.execute("BEGIN") # Check if a transaction is already in progress
in_transaction = self.db_conn.in_transaction
# Only start transaction if not already in one
if not in_transaction:
self.db_conn.execute("BEGIN")
migration.upgrade(self.db_conn) migration.upgrade(self.db_conn)
self.set_version(migration.version) self.set_version(migration.version)
self.db_conn.execute("COMMIT")
# Only commit if we started the transaction and it's still active
if not in_transaction and self.db_conn.in_transaction:
self.db_conn.execute("COMMIT")
log.info( log.info(
f"Successfully applied migration {migration.version}: {migration.name}" f"Successfully applied migration {migration.version}: {migration.name}"
) )
except Exception as e: except Exception as e:
log.error( log.error(
f"Failed to apply migration {migration.version}: {migration.name}. Rolling back. Error: {e}", f"Failed to apply migration {migration.version}: {migration.name}. Error: {e}",
exc_info=True, exc_info=True,
) )
self.db_conn.execute("ROLLBACK") # Try to rollback if transaction is active
try:
if self.db_conn.in_transaction:
self.db_conn.execute("ROLLBACK")
except Exception:
pass # Ignore rollback errors
raise raise
latest_migration_version = self.migrations[-1].version if self.migrations else 0 latest_migration_version = self.migrations[-1].version if self.migrations else 0
if current_version < latest_migration_version: if current_version < latest_migration_version:
# This case can be hit if migrations were applied but the loop was exited # This case can be hit if migrations were applied but the loop was exited

View File

@@ -64,6 +64,14 @@ def upgrade(db_conn: Connection):
log.info("No 'semantic_metadata' table found, skipping data migration.") log.info("No 'semantic_metadata' table found, skipping data migration.")
return return
# Check if 'keywords' column exists in semantic_metadata table
# (current schema may already use normalized tables without this column)
cursor.execute("PRAGMA table_info(semantic_metadata)")
columns = {row[1] for row in cursor.fetchall()}
if "keywords" not in columns:
log.info("No 'keywords' column in semantic_metadata table, skipping data migration.")
return
cursor.execute("SELECT file_id, keywords FROM semantic_metadata WHERE keywords IS NOT NULL AND keywords != ''") cursor.execute("SELECT file_id, keywords FROM semantic_metadata WHERE keywords IS NOT NULL AND keywords != ''")
files_to_migrate = cursor.fetchall() files_to_migrate = cursor.fetchall()

View File

@@ -36,22 +36,27 @@ log = logging.getLogger(__name__)
def upgrade(db_conn: Connection): def upgrade(db_conn: Connection):
"""Remove unused and redundant fields from schema. """Remove unused and redundant fields from schema.
Note: Transaction management is handled by MigrationManager.
This migration should NOT start its own transaction.
Args: Args:
db_conn: The SQLite database connection. db_conn: The SQLite database connection.
""" """
cursor = db_conn.cursor() cursor = db_conn.cursor()
try: # Step 1: Remove semantic_metadata.keywords (if column exists)
cursor.execute("BEGIN TRANSACTION") log.info("Checking semantic_metadata.keywords column...")
# Step 1: Remove semantic_metadata.keywords cursor.execute(
log.info("Removing semantic_metadata.keywords column...") "SELECT name FROM sqlite_master WHERE type='table' AND name='semantic_metadata'"
)
if cursor.fetchone():
# Check if keywords column exists
cursor.execute("PRAGMA table_info(semantic_metadata)")
columns = {row[1] for row in cursor.fetchall()}
# Check if semantic_metadata table exists if "keywords" in columns:
cursor.execute( log.info("Removing semantic_metadata.keywords column...")
"SELECT name FROM sqlite_master WHERE type='table' AND name='semantic_metadata'"
)
if cursor.fetchone():
cursor.execute(""" cursor.execute("""
CREATE TABLE semantic_metadata_new ( CREATE TABLE semantic_metadata_new (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -79,16 +84,23 @@ def upgrade(db_conn: Connection):
) )
log.info("Removed semantic_metadata.keywords column") log.info("Removed semantic_metadata.keywords column")
else: else:
log.info("semantic_metadata table does not exist, skipping") log.info("semantic_metadata.keywords column does not exist, skipping")
else:
log.info("semantic_metadata table does not exist, skipping")
# Step 2: Remove symbols.token_count and symbols.symbol_type # Step 2: Remove symbols.token_count and symbols.symbol_type (if columns exist)
log.info("Removing symbols.token_count and symbols.symbol_type columns...") log.info("Checking symbols.token_count and symbols.symbol_type columns...")
# Check if symbols table exists cursor.execute(
cursor.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='symbols'"
"SELECT name FROM sqlite_master WHERE type='table' AND name='symbols'" )
) if cursor.fetchone():
if cursor.fetchone(): # Check if token_count or symbol_type columns exist
cursor.execute("PRAGMA table_info(symbols)")
columns = {row[1] for row in cursor.fetchall()}
if "token_count" in columns or "symbol_type" in columns:
log.info("Removing symbols.token_count and symbols.symbol_type columns...")
cursor.execute(""" cursor.execute("""
CREATE TABLE symbols_new ( CREATE TABLE symbols_new (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -110,21 +122,28 @@ def upgrade(db_conn: Connection):
cursor.execute("DROP TABLE symbols") cursor.execute("DROP TABLE symbols")
cursor.execute("ALTER TABLE symbols_new RENAME TO symbols") cursor.execute("ALTER TABLE symbols_new RENAME TO symbols")
# Recreate indexes (excluding idx_symbols_type which indexed symbol_type) # Recreate indexes
cursor.execute("CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_id)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)")
log.info("Removed symbols.token_count and symbols.symbol_type columns") log.info("Removed symbols.token_count and symbols.symbol_type columns")
else: else:
log.info("symbols table does not exist, skipping") log.info("symbols.token_count/symbol_type columns do not exist, skipping")
else:
log.info("symbols table does not exist, skipping")
# Step 3: Remove subdirs.direct_files # Step 3: Remove subdirs.direct_files (if column exists)
log.info("Removing subdirs.direct_files column...") log.info("Checking subdirs.direct_files column...")
# Check if subdirs table exists cursor.execute(
cursor.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='subdirs'"
"SELECT name FROM sqlite_master WHERE type='table' AND name='subdirs'" )
) if cursor.fetchone():
if cursor.fetchone(): # Check if direct_files column exists
cursor.execute("PRAGMA table_info(subdirs)")
columns = {row[1] for row in cursor.fetchall()}
if "direct_files" in columns:
log.info("Removing subdirs.direct_files column...")
cursor.execute(""" cursor.execute("""
CREATE TABLE subdirs_new ( CREATE TABLE subdirs_new (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -148,26 +167,15 @@ def upgrade(db_conn: Connection):
cursor.execute("CREATE INDEX IF NOT EXISTS idx_subdirs_name ON subdirs(name)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_subdirs_name ON subdirs(name)")
log.info("Removed subdirs.direct_files column") log.info("Removed subdirs.direct_files column")
else: else:
log.info("subdirs table does not exist, skipping") log.info("subdirs.direct_files column does not exist, skipping")
else:
log.info("subdirs table does not exist, skipping")
cursor.execute("COMMIT") log.info("Migration 005 completed successfully")
log.info("Migration 005 completed successfully")
# Vacuum to reclaim space (outside transaction) # Vacuum to reclaim space (outside transaction, optional)
try: # Note: VACUUM cannot run inside a transaction, so we skip it here
log.info("Running VACUUM to reclaim space...") # The caller can run VACUUM separately if desired
cursor.execute("VACUUM")
log.info("VACUUM completed successfully")
except Exception as e:
log.warning(f"VACUUM failed (non-critical): {e}")
except Exception as e:
log.error(f"Migration 005 failed: {e}")
try:
cursor.execute("ROLLBACK")
except Exception:
pass
raise
def downgrade(db_conn: Connection): def downgrade(db_conn: Connection):

View File

@@ -7,7 +7,7 @@ import threading
import time import time
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional from typing import Any, Dict, List, Optional
from codexlens.errors import StorageError from codexlens.errors import StorageError
@@ -462,6 +462,66 @@ class RegistryStore:
row = conn.execute(query, paths_to_check).fetchone() row = conn.execute(query, paths_to_check).fetchone()
return self._row_to_dir_mapping(row) if row else None return self._row_to_dir_mapping(row) if row else None
def find_by_source_path(self, source_path: str) -> Optional[Dict[str, str]]:
"""Find project by source path (exact or nearest match).
Searches for a project whose source_root matches or contains
the given source_path.
Args:
source_path: Source directory path as string
Returns:
Dict with project info including 'index_root', or None if not found
"""
with self._lock:
conn = self._get_connection()
source_path_resolved = str(Path(source_path).resolve())
# First try exact match on projects table
row = conn.execute(
"SELECT * FROM projects WHERE source_root=?", (source_path_resolved,)
).fetchone()
if row:
return {
"id": str(row["id"]),
"source_root": row["source_root"],
"index_root": row["index_root"],
"status": row["status"] or "active",
}
# Try finding project that contains this path
# Build list of all parent paths
paths_to_check = []
current = Path(source_path_resolved)
while True:
paths_to_check.append(str(current))
parent = current.parent
if parent == current:
break
current = parent
if paths_to_check:
placeholders = ','.join('?' * len(paths_to_check))
query = f"""
SELECT * FROM projects
WHERE source_root IN ({placeholders})
ORDER BY LENGTH(source_root) DESC
LIMIT 1
"""
row = conn.execute(query, paths_to_check).fetchone()
if row:
return {
"id": str(row["id"]),
"source_root": row["source_root"],
"index_root": row["index_root"],
"status": row["status"] or "active",
}
return None
def get_project_dirs(self, project_id: int) -> List[DirMapping]: def get_project_dirs(self, project_id: int) -> List[DirMapping]:
"""Get all directory mappings for a project. """Get all directory mappings for a project.

View File

@@ -204,13 +204,11 @@ class SQLiteStore:
if indexed_file.symbols: if indexed_file.symbols:
conn.executemany( conn.executemany(
""" """
INSERT INTO symbols(file_id, name, kind, start_line, end_line, token_count, symbol_type) INSERT INTO symbols(file_id, name, kind, start_line, end_line)
VALUES(?, ?, ?, ?, ?, ?, ?) VALUES(?, ?, ?, ?, ?)
""", """,
[ [
(file_id, s.name, s.kind, s.range[0], s.range[1], (file_id, s.name, s.kind, s.range[0], s.range[1])
getattr(s, 'token_count', None),
getattr(s, 'symbol_type', None) or s.kind)
for s in indexed_file.symbols for s in indexed_file.symbols
], ],
) )
@@ -255,13 +253,11 @@ class SQLiteStore:
if indexed_file.symbols: if indexed_file.symbols:
conn.executemany( conn.executemany(
""" """
INSERT INTO symbols(file_id, name, kind, start_line, end_line, token_count, symbol_type) INSERT INTO symbols(file_id, name, kind, start_line, end_line)
VALUES(?, ?, ?, ?, ?, ?, ?) VALUES(?, ?, ?, ?, ?)
""", """,
[ [
(file_id, s.name, s.kind, s.range[0], s.range[1], (file_id, s.name, s.kind, s.range[0], s.range[1])
getattr(s, 'token_count', None),
getattr(s, 'symbol_type', None) or s.kind)
for s in indexed_file.symbols for s in indexed_file.symbols
], ],
) )
@@ -611,15 +607,12 @@ class SQLiteStore:
name TEXT NOT NULL, name TEXT NOT NULL,
kind TEXT NOT NULL, kind TEXT NOT NULL,
start_line INTEGER NOT NULL, start_line INTEGER NOT NULL,
end_line INTEGER NOT NULL, end_line INTEGER NOT NULL
token_count INTEGER,
symbol_type TEXT
) )
""" """
) )
conn.execute("CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)") conn.execute("CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_symbols_kind ON symbols(kind)") conn.execute("CREATE INDEX IF NOT EXISTS idx_symbols_kind ON symbols(kind)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_symbols_type ON symbols(symbol_type)")
conn.execute( conn.execute(
""" """
CREATE TABLE IF NOT EXISTS code_relationships ( CREATE TABLE IF NOT EXISTS code_relationships (

View File

@@ -62,8 +62,9 @@ class TestDetectEncoding:
# Should detect GBK or fallback to UTF-8 # Should detect GBK or fallback to UTF-8
assert isinstance(encoding, str) assert isinstance(encoding, str)
if ENCODING_DETECTION_AVAILABLE: if ENCODING_DETECTION_AVAILABLE:
# With chardet, should detect GBK, GB2312, Big5, or UTF-8 (all valid) # With chardet, should detect CJK encoding or UTF-8 (chardet may detect similar encodings)
assert encoding.lower() in ["gbk", "gb2312", "big5", "utf-8", "utf8"] valid_encodings = ["gbk", "gb2312", "gb18030", "big5", "utf-8", "utf8", "cp949", "euc-kr", "iso-8859-1"]
assert encoding.lower() in valid_encodings, f"Got unexpected encoding: {encoding}"
else: else:
# Without chardet, should fallback to UTF-8 # Without chardet, should fallback to UTF-8
assert encoding.lower() in ["utf-8", "utf8"] assert encoding.lower() in ["utf-8", "utf8"]

View File

@@ -203,6 +203,7 @@ class TestEntitySerialization:
"name": "test", "name": "test",
"kind": "function", "kind": "function",
"range": (1, 10), "range": (1, 10),
"file": None,
"token_count": None, "token_count": None,
"symbol_type": None, "symbol_type": None,
} }

View File

@@ -135,7 +135,7 @@ class TestKeywordNormalization:
assert len(indexes) == 3 assert len(indexes) == 3
def test_add_semantic_metadata_populates_normalized_tables(self, temp_index_db): def test_add_semantic_metadata_populates_normalized_tables(self, temp_index_db):
"""Test that adding metadata populates both old and new tables.""" """Test that adding metadata populates the normalized keyword tables."""
# Add a file # Add a file
file_id = temp_index_db.add_file( file_id = temp_index_db.add_file(
name="test.py", name="test.py",
@@ -156,13 +156,15 @@ class TestKeywordNormalization:
conn = temp_index_db._get_connection() conn = temp_index_db._get_connection()
# Check semantic_metadata table (backward compatibility) # Check semantic_metadata table (without keywords column in current schema)
row = conn.execute( row = conn.execute(
"SELECT keywords FROM semantic_metadata WHERE file_id=?", "SELECT summary, purpose, llm_tool FROM semantic_metadata WHERE file_id=?",
(file_id,) (file_id,)
).fetchone() ).fetchone()
assert row is not None assert row is not None
assert json.loads(row["keywords"]) == keywords assert row["summary"] == "Test summary"
assert row["purpose"] == "Testing"
assert row["llm_tool"] == "gemini"
# Check normalized keywords table # Check normalized keywords table
keyword_rows = conn.execute(""" keyword_rows = conn.execute("""
@@ -347,21 +349,33 @@ class TestMigrationManager:
assert current_version >= 0 assert current_version >= 0
def test_migration_001_can_run(self, temp_index_db): def test_migration_001_can_run(self, temp_index_db):
"""Test that migration_001 can be applied.""" """Test that migration_001 is idempotent on current schema.
Note: Current schema already has normalized keywords tables created
during initialize(), so migration_001 should be a no-op but not fail.
The original migration was designed to migrate from semantic_metadata.keywords
to normalized tables, but new databases use normalized tables directly.
"""
conn = temp_index_db._get_connection() conn = temp_index_db._get_connection()
# Add some test data to semantic_metadata first # Add some test data using the current normalized schema
conn.execute(""" conn.execute("""
INSERT INTO files(id, name, full_path, language, content, mtime, line_count) INSERT INTO files(id, name, full_path, language, content, mtime, line_count)
VALUES(100, 'test.py', '/test_migration.py', 'python', 'def test(): pass', 0, 10) VALUES(100, 'test.py', '/test_migration.py', 'python', 'def test(): pass', 0, 10)
""") """)
conn.execute("""
INSERT INTO semantic_metadata(file_id, keywords) # Insert directly into normalized tables (current schema)
VALUES(100, ?) conn.execute("INSERT OR IGNORE INTO keywords(keyword) VALUES(?)", ("test",))
""", (json.dumps(["test", "keyword"]),)) conn.execute("INSERT OR IGNORE INTO keywords(keyword) VALUES(?)", ("keyword",))
kw1_id = conn.execute("SELECT id FROM keywords WHERE keyword=?", ("test",)).fetchone()[0]
kw2_id = conn.execute("SELECT id FROM keywords WHERE keyword=?", ("keyword",)).fetchone()[0]
conn.execute("INSERT OR IGNORE INTO file_keywords(file_id, keyword_id) VALUES(?, ?)", (100, kw1_id))
conn.execute("INSERT OR IGNORE INTO file_keywords(file_id, keyword_id) VALUES(?, ?)", (100, kw2_id))
conn.commit() conn.commit()
# Run migration (should be idempotent, tables already created by initialize()) # Run migration (should be idempotent - tables already exist)
try: try:
migration_001_normalize_keywords.upgrade(conn) migration_001_normalize_keywords.upgrade(conn)
success = True success = True
@@ -371,7 +385,7 @@ class TestMigrationManager:
assert success assert success
# Verify data was migrated # Verify data still exists
keyword_count = conn.execute(""" keyword_count = conn.execute("""
SELECT COUNT(*) as c FROM file_keywords WHERE file_id=100 SELECT COUNT(*) as c FROM file_keywords WHERE file_id=100
""").fetchone()["c"] """).fetchone()["c"]

View File

@@ -89,7 +89,12 @@ class TestTokenMetadataStorage:
assert file_entry.name == "math.py" assert file_entry.name == "math.py"
def test_migration_adds_token_columns(self): def test_migration_adds_token_columns(self):
"""Test that migration 002 adds token_count and symbol_type columns.""" """Test that migrations properly handle token_count and symbol_type columns.
Note: Migration 002 adds these columns, but migration 005 removes them
as they were identified as unused/redundant. New databases should not
have these columns.
"""
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db" db_path = Path(tmpdir) / "test.db"
store = SQLiteStore(db_path) store = SQLiteStore(db_path)
@@ -100,19 +105,21 @@ class TestTokenMetadataStorage:
manager = MigrationManager(conn) manager = MigrationManager(conn)
manager.apply_migrations() manager.apply_migrations()
# Verify columns exist # Verify columns do NOT exist after all migrations
# (migration_005 removes token_count and symbol_type)
cursor = conn.execute("PRAGMA table_info(symbols)") cursor = conn.execute("PRAGMA table_info(symbols)")
columns = {row[1] for row in cursor.fetchall()} columns = {row[1] for row in cursor.fetchall()}
assert "token_count" in columns # These columns should NOT be present after migration_005
assert "symbol_type" in columns assert "token_count" not in columns, "token_count should be removed by migration_005"
assert "symbol_type" not in columns, "symbol_type should be removed by migration_005"
# Verify index exists # Index on symbol_type should also not exist
cursor = conn.execute( cursor = conn.execute(
"SELECT name FROM sqlite_master WHERE type='index' AND name='idx_symbols_type'" "SELECT name FROM sqlite_master WHERE type='index' AND name='idx_symbols_type'"
) )
index = cursor.fetchone() index = cursor.fetchone()
assert index is not None assert index is None, "idx_symbols_type should not exist after migration_005"
def test_batch_insert_preserves_token_metadata(self): def test_batch_insert_preserves_token_metadata(self):
"""Test that batch insert preserves token metadata.""" """Test that batch insert preserves token metadata."""
@@ -258,23 +265,30 @@ class TestTokenMetadataStorage:
class TestTokenCountAccuracy: class TestTokenCountAccuracy:
"""Tests for token count accuracy in storage.""" """Tests for symbol storage accuracy.
Note: token_count and symbol_type columns were removed in migration_005
as they were identified as unused/redundant. These tests now verify
that symbols are stored correctly with their basic fields.
"""
def test_stored_token_count_matches_original(self): def test_stored_token_count_matches_original(self):
"""Test that stored token_count matches the original value.""" """Test that symbols are stored correctly (token_count no longer stored).
Note: token_count field was removed from schema. This test verifies
that symbols are still stored correctly with basic fields.
"""
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db" db_path = Path(tmpdir) / "test.db"
store = SQLiteStore(db_path) store = SQLiteStore(db_path)
with store: with store:
expected_token_count = 256
symbols = [ symbols = [
Symbol( Symbol(
name="complex_func", name="complex_func",
kind="function", kind="function",
range=(1, 20), range=(1, 20),
token_count=expected_token_count token_count=256 # This field is accepted but not stored
), ),
] ]
@@ -287,41 +301,42 @@ class TestTokenCountAccuracy:
content = "def complex_func():\n # Some complex logic\n pass\n" content = "def complex_func():\n # Some complex logic\n pass\n"
store.add_file(indexed_file, content) store.add_file(indexed_file, content)
# Verify by querying the database directly # Verify symbol is stored with basic fields
conn = store._get_connection() conn = store._get_connection()
cursor = conn.execute( cursor = conn.execute(
"SELECT token_count FROM symbols WHERE name = ?", "SELECT name, kind, start_line, end_line FROM symbols WHERE name = ?",
("complex_func",) ("complex_func",)
) )
row = cursor.fetchone() row = cursor.fetchone()
assert row is not None assert row is not None
stored_token_count = row[0] assert row["name"] == "complex_func"
assert stored_token_count == expected_token_count assert row["kind"] == "function"
assert row["start_line"] == 1
assert row["end_line"] == 20
def test_100_percent_storage_accuracy(self): def test_100_percent_storage_accuracy(self):
"""Test that 100% of token counts are stored correctly.""" """Test that 100% of symbols are stored correctly.
Note: token_count field was removed from schema. This test verifies
that symbols are stored correctly with basic fields.
"""
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "_index.db" db_path = Path(tmpdir) / "_index.db"
store = DirIndexStore(db_path) store = DirIndexStore(db_path)
with store: with store:
# Create a mapping of expected token counts # Store symbols
expected_counts = {}
# Store symbols with known token counts
file_entries = [] file_entries = []
for i in range(100): for i in range(100):
token_count = 10 + i * 3
symbol_name = f"func{i}" symbol_name = f"func{i}"
expected_counts[symbol_name] = token_count
symbols = [ symbols = [
Symbol( Symbol(
name=symbol_name, name=symbol_name,
kind="function", kind="function",
range=(1, 2), range=(1, 2),
token_count=token_count token_count=10 + i * 3 # Accepted but not stored
) )
] ]
@@ -337,17 +352,17 @@ class TestTokenCountAccuracy:
count = store.add_files_batch(file_entries) count = store.add_files_batch(file_entries)
assert count == 100 assert count == 100
# Verify all token counts are stored correctly # Verify all symbols are stored correctly
conn = store._get_connection() conn = store._get_connection()
cursor = conn.execute( cursor = conn.execute(
"SELECT name, token_count FROM symbols ORDER BY name" "SELECT name, kind, start_line, end_line FROM symbols ORDER BY name"
) )
rows = cursor.fetchall() rows = cursor.fetchall()
assert len(rows) == 100 assert len(rows) == 100
# Verify each stored token_count matches what we set # Verify each symbol has correct basic fields
for name, token_count in rows: for row in rows:
expected = expected_counts[name] assert row["kind"] == "function"
assert token_count == expected, \ assert row["start_line"] == 1
f"Symbol {name} has token_count {token_count}, expected {expected}" assert row["end_line"] == 2

View File

@@ -86,7 +86,7 @@ class TestEmbedder:
def test_embedder_initialization(self, embedder): def test_embedder_initialization(self, embedder):
"""Test embedder initializes correctly.""" """Test embedder initializes correctly."""
assert embedder.model_name == "BAAI/bge-small-en-v1.5" assert embedder.model_name == "BAAI/bge-small-en-v1.5"
assert embedder.EMBEDDING_DIM == 384 assert embedder.embedding_dim == 384
assert embedder._model is None # Lazy loading assert embedder._model is None # Lazy loading
def test_embed_single_returns_correct_dimension(self, embedder): def test_embed_single_returns_correct_dimension(self, embedder):