feat(temple): add Temple modal with Tools browser and Skills management
This commit is contained in:
69
frontend/src/api/tools.ts
Normal file
69
frontend/src/api/tools.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import api from './index'
|
||||
import type { AxiosResponse } from 'axios'
|
||||
|
||||
/** 单个工具命令 */
|
||||
export interface ToolCommand {
|
||||
name: string
|
||||
description: string
|
||||
parameters: Record<string, unknown>
|
||||
}
|
||||
|
||||
/** 工具调用统计 */
|
||||
export interface ToolStats {
|
||||
call_count: number
|
||||
error_count: number
|
||||
total_duration_ms: number
|
||||
avg_duration_ms: number
|
||||
error_rate: number
|
||||
}
|
||||
|
||||
/** 工具信息 */
|
||||
export interface ToolInfo {
|
||||
name: string
|
||||
display_name: string
|
||||
description: string
|
||||
category: string
|
||||
subcategory: string
|
||||
source: 'manifest' | 'agent'
|
||||
source_file: string
|
||||
tags: string[]
|
||||
enabled: boolean
|
||||
commands: ToolCommand[]
|
||||
stats: ToolStats | null
|
||||
config: Record<string, unknown>
|
||||
}
|
||||
|
||||
/** 工具子分类 */
|
||||
export interface ToolSubgroup {
|
||||
name: string
|
||||
display_name: string
|
||||
tools: ToolInfo[]
|
||||
}
|
||||
|
||||
/** 工具大分类 */
|
||||
export interface ToolCategory {
|
||||
name: string
|
||||
display_name: string
|
||||
subgroups: ToolSubgroup[]
|
||||
}
|
||||
|
||||
/** 工具统计摘要 */
|
||||
export interface ToolSummary {
|
||||
total_commands: number
|
||||
active_commands: number
|
||||
total_tools: number
|
||||
manifest_tools: number
|
||||
agent_tools: number
|
||||
}
|
||||
|
||||
/** GET /api/tools 响应 */
|
||||
export interface ToolsResponse {
|
||||
categories: ToolCategory[]
|
||||
summary: ToolSummary
|
||||
}
|
||||
|
||||
export const toolsApi = {
|
||||
list: (): Promise<AxiosResponse<ToolsResponse>> => {
|
||||
return api.get('/api/tools')
|
||||
},
|
||||
}
|
||||
133
frontend/src/pages/temple/composables/useTemple.ts
Normal file
133
frontend/src/pages/temple/composables/useTemple.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { toolsApi, type ToolCategory, type ToolInfo, type ToolsResponse } from '@/api/tools'
|
||||
|
||||
export type TabType = 'tools' | 'skills'
|
||||
|
||||
export function useTemple() {
|
||||
// ===== State =====
|
||||
const activeTab = ref<TabType>('tools')
|
||||
const toolsLoading = ref(false)
|
||||
const toolsError = ref<string | null>(null)
|
||||
|
||||
// Tools data
|
||||
const categories = ref<ToolCategory[]>([])
|
||||
const summary = ref({
|
||||
total_commands: 0,
|
||||
active_commands: 0,
|
||||
total_tools: 0,
|
||||
manifest_tools: 0,
|
||||
agent_tools: 0,
|
||||
})
|
||||
|
||||
// Selection state (Tools Tab)
|
||||
const selectedCategory = ref<string | null>(null) // 大分类名,如 "注册层"
|
||||
const selectedSubgroup = ref<string | null>(null) // 子分类名,如 "文件操作"
|
||||
const selectedTool = ref<ToolInfo | null>(null)
|
||||
|
||||
// ===== Computed =====
|
||||
|
||||
/** 展平所有工具列表 */
|
||||
const allTools = computed(() => {
|
||||
return categories.value.flatMap((cat) =>
|
||||
cat.subgroups.flatMap((sub) => sub.tools)
|
||||
)
|
||||
})
|
||||
|
||||
/** 当前选中的大分类下的子分类 */
|
||||
const currentSubgroups = computed(() => {
|
||||
if (!selectedCategory.value) return []
|
||||
const cat = categories.value.find((c) => c.name === selectedCategory.value)
|
||||
return cat?.subgroups ?? []
|
||||
})
|
||||
|
||||
/** 当前选中子分类下的工具 */
|
||||
const currentTools = computed(() => {
|
||||
if (!selectedSubgroup.value) return []
|
||||
for (const cat of categories.value) {
|
||||
const sub = cat.subgroups.find((s) => s.name === selectedSubgroup.value)
|
||||
if (sub) return sub.tools
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
/** 当前选中工具的详情 */
|
||||
const currentToolDetail = computed(() => selectedTool.value)
|
||||
|
||||
// ===== Actions =====
|
||||
|
||||
async function fetchTools() {
|
||||
toolsLoading.value = true
|
||||
toolsError.value = null
|
||||
try {
|
||||
const res = await toolsApi.list()
|
||||
const data: ToolsResponse = res.data
|
||||
categories.value = data.categories
|
||||
summary.value = data.summary
|
||||
// 默认选中第一个分类和子分类
|
||||
if (categories.value.length > 0) {
|
||||
const firstCat = categories.value[0]
|
||||
selectedCategory.value = firstCat.name
|
||||
if (firstCat.subgroups.length > 0) {
|
||||
selectedSubgroup.value = firstCat.subgroups[0].name
|
||||
}
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
toolsError.value = e instanceof Error ? e.message : 'Failed to load tools'
|
||||
console.error('[useTemple] fetchTools error:', e)
|
||||
} finally {
|
||||
toolsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function selectCategory(name: string) {
|
||||
selectedCategory.value = name
|
||||
selectedSubgroup.value = null
|
||||
selectedTool.value = null
|
||||
// 自动选中第一个子分类
|
||||
const cat = categories.value.find((c) => c.name === name)
|
||||
if (cat && cat.subgroups.length > 0) {
|
||||
selectedSubgroup.value = cat.subgroups[0].name
|
||||
}
|
||||
}
|
||||
|
||||
function selectSubgroup(name: string) {
|
||||
selectedSubgroup.value = name
|
||||
selectedTool.value = null
|
||||
}
|
||||
|
||||
function selectTool(tool: ToolInfo) {
|
||||
selectedTool.value = tool
|
||||
}
|
||||
|
||||
function clearToolSelection() {
|
||||
selectedTool.value = null
|
||||
}
|
||||
|
||||
function switchTab(tab: TabType) {
|
||||
activeTab.value = tab
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
activeTab,
|
||||
toolsLoading,
|
||||
toolsError,
|
||||
categories,
|
||||
summary,
|
||||
selectedCategory,
|
||||
selectedSubgroup,
|
||||
selectedTool,
|
||||
// Computed
|
||||
allTools,
|
||||
currentSubgroups,
|
||||
currentTools,
|
||||
currentToolDetail,
|
||||
// Actions
|
||||
fetchTools,
|
||||
selectCategory,
|
||||
selectSubgroup,
|
||||
selectTool,
|
||||
clearToolSelection,
|
||||
switchTab,
|
||||
}
|
||||
}
|
||||
@@ -1,56 +1,641 @@
|
||||
<script setup lang="ts">
|
||||
// 智慧神殿 - Temple of Wisdom
|
||||
import { ref, watch } from 'vue'
|
||||
import { X, Bot, Plus, Edit2, Trash2, Eye, EyeOff, Copy } from 'lucide-vue-next'
|
||||
import { useTemple } from './composables/useTemple'
|
||||
import { useSkillsPage } from '../skills/composables/useSkillsPage'
|
||||
import { type Skill, type SkillCreate } from '@/api/skill'
|
||||
|
||||
// ===== Props / Emits =====
|
||||
const props = defineProps<{ visible: boolean }>()
|
||||
const emit = defineEmits<{ close: [] }>()
|
||||
|
||||
// ===== Temple (Tools) =====
|
||||
const {
|
||||
activeTab,
|
||||
toolsLoading,
|
||||
toolsError,
|
||||
categories,
|
||||
summary,
|
||||
selectedSubgroup,
|
||||
selectedTool,
|
||||
currentTools,
|
||||
fetchTools,
|
||||
selectCategory,
|
||||
selectSubgroup,
|
||||
selectTool,
|
||||
switchTab,
|
||||
} = useTemple()
|
||||
|
||||
// ===== Skills (inline from useSkillsPage) =====
|
||||
const skillsPage = useSkillsPage()
|
||||
const {
|
||||
skills,
|
||||
loading: skillsLoading,
|
||||
modalOpen,
|
||||
editingSkill,
|
||||
closeModal,
|
||||
createSkill,
|
||||
updateSkill,
|
||||
deleteSkill,
|
||||
toggleActive,
|
||||
copySkill,
|
||||
} = skillsPage
|
||||
|
||||
// ===== Fetch tools when modal opens =====
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
void fetchTools()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// ===== Close =====
|
||||
function handleClose() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// ===== Skills form state (local for modal) =====
|
||||
const skillForm = ref<SkillCreate>({
|
||||
name: '',
|
||||
description: '',
|
||||
instructions: '',
|
||||
agent_type: 'general',
|
||||
tools: [],
|
||||
visibility: 'private',
|
||||
})
|
||||
|
||||
function openNewSkillModal() {
|
||||
skillForm.value = {
|
||||
name: '',
|
||||
description: '',
|
||||
instructions: '',
|
||||
agent_type: 'general',
|
||||
tools: [],
|
||||
visibility: 'private',
|
||||
}
|
||||
editingSkill.value = null
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
function openEditSkillModal(skill: Skill) {
|
||||
skillForm.value = {
|
||||
name: skill.name,
|
||||
description: skill.description ?? '',
|
||||
instructions: skill.instructions,
|
||||
agent_type: skill.agent_type,
|
||||
tools: [...skill.tools],
|
||||
visibility: skill.visibility,
|
||||
}
|
||||
editingSkill.value = skill
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
async function handleSaveSkill() {
|
||||
if (editingSkill.value) {
|
||||
await updateSkill()
|
||||
} else {
|
||||
await createSkill()
|
||||
}
|
||||
closeModal()
|
||||
}
|
||||
|
||||
async function handleDeleteSkill(skill: Skill) {
|
||||
if (confirm(`Delete skill "${skill.name}"?`)) {
|
||||
await deleteSkill(skill)
|
||||
}
|
||||
}
|
||||
|
||||
const AGENT_TYPES = ['general', 'schedule_planner', 'executor', 'librarian', 'analyst']
|
||||
const AVAILABLE_TOOLS = ['file_operations', 'web_search', 'code_execution', 'database', 'api_calls', 'shell', 'git', 'calendar', 'tasks']
|
||||
const VISIBILITY_OPTIONS = ['private', 'team', 'market'] as const
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="temple-page">
|
||||
<div class="page-header">
|
||||
<h1>⛩️ 智慧神殿</h1>
|
||||
<p class="subtitle">深邃智慧,永恒传承</p>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<div class="placeholder-content">
|
||||
<div class="temple-icon">🏛️</div>
|
||||
<p>智慧神殿 - 敬请期待</p>
|
||||
<Teleport to="body">
|
||||
<div v-if="visible" class="temple-modal-overlay" @click.self="handleClose">
|
||||
<div class="temple-modal" role="dialog" aria-modal="true" aria-label="智慧神殿">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="temple-header">
|
||||
<div class="temple-header-title">
|
||||
<span class="temple-title-icon">◈</span>
|
||||
<span class="temple-title-text">智慧神殿</span>
|
||||
</div>
|
||||
<button class="temple-close-btn" aria-label="关闭" @click="handleClose">
|
||||
<X :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<div class="temple-tabs">
|
||||
<button
|
||||
class="temple-tab"
|
||||
:class="{ active: activeTab === 'tools' }"
|
||||
@click="switchTab('tools')"
|
||||
>
|
||||
<span class="temple-tab-icon">◈</span>
|
||||
<span>Tools</span>
|
||||
</button>
|
||||
<button
|
||||
class="temple-tab"
|
||||
:class="{ active: activeTab === 'skills' }"
|
||||
@click="switchTab('skills')"
|
||||
>
|
||||
<span class="temple-tab-icon">✦</span>
|
||||
<span>Skills</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Metrics Strip -->
|
||||
<div class="temple-metrics">
|
||||
<div class="temple-metric">
|
||||
<span class="temple-metric-label">TOTAL</span>
|
||||
<span class="temple-metric-value">{{ summary.total_commands }}</span>
|
||||
</div>
|
||||
<div class="temple-metric">
|
||||
<span class="temple-metric-label">ACTIVE</span>
|
||||
<span class="temple-metric-value">{{ summary.active_commands }}</span>
|
||||
</div>
|
||||
<div class="temple-metric">
|
||||
<span class="temple-metric-label">MANIFEST</span>
|
||||
<span class="temple-metric-value">{{ summary.manifest_tools }}</span>
|
||||
</div>
|
||||
<div class="temple-metric">
|
||||
<span class="temple-metric-label">AGENT</span>
|
||||
<span class="temple-metric-value">{{ summary.agent_tools }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="temple-body">
|
||||
|
||||
<!-- ========== TOOLS TAB ========== -->
|
||||
<div v-if="activeTab === 'tools'" class="temple-tools-layout">
|
||||
|
||||
<!-- Left: Category Tree -->
|
||||
<nav class="temple-tree">
|
||||
<template v-for="cat in categories" :key="cat.name">
|
||||
<div class="temple-tree-section-title">{{ cat.display_name || cat.name }}</div>
|
||||
<div
|
||||
v-for="sub in cat.subgroups"
|
||||
:key="sub.name"
|
||||
class="temple-tree-item temple-tree-subgroup"
|
||||
:class="{ active: selectedSubgroup === sub.name }"
|
||||
@click="selectCategory(cat.name); selectSubgroup(sub.name)"
|
||||
>
|
||||
<span class="temple-tree-dot"></span>
|
||||
{{ sub.display_name || sub.name }}
|
||||
</div>
|
||||
</template>
|
||||
</nav>
|
||||
|
||||
<!-- Middle: Tool List -->
|
||||
<div class="temple-tool-list">
|
||||
<div v-if="toolsLoading" class="temple-loading">
|
||||
<span>Loading tools...</span>
|
||||
</div>
|
||||
<div v-else-if="toolsError" class="temple-empty" style="color:#f87171;">
|
||||
<div class="temple-empty-icon">⚠</div>
|
||||
<span>加载失败: {{ toolsError }}</span>
|
||||
</div>
|
||||
<div v-else-if="categories.length === 0" class="temple-empty">
|
||||
<div class="temple-empty-icon">◈</div>
|
||||
<span>暂无可用工具</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="tool in currentTools"
|
||||
:key="tool.name"
|
||||
class="temple-tool-card"
|
||||
:class="{ selected: selectedTool?.name === tool.name }"
|
||||
@click="selectTool(tool)"
|
||||
>
|
||||
<div class="temple-tool-card-icon">⚙</div>
|
||||
<div class="temple-tool-card-info">
|
||||
<div class="temple-tool-card-name">{{ tool.display_name || tool.name }}</div>
|
||||
<div class="temple-tool-card-desc">{{ tool.description }}</div>
|
||||
</div>
|
||||
<div class="temple-tool-card-commands">{{ tool.commands.length }} cmds</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Tool Detail -->
|
||||
<div class="temple-detail">
|
||||
<div v-if="!selectedTool" class="temple-empty">
|
||||
<div class="temple-empty-icon">◈</div>
|
||||
<span>选择工具查看详情</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="temple-detail-header">
|
||||
<div>
|
||||
<div class="temple-detail-name">{{ selectedTool.display_name || selectedTool.name }}</div>
|
||||
<div class="temple-detail-display-name">{{ selectedTool.name }}</div>
|
||||
</div>
|
||||
<span class="temple-detail-source">{{ selectedTool.source }}</span>
|
||||
</div>
|
||||
|
||||
<p class="temple-detail-desc">{{ selectedTool.description }}</p>
|
||||
|
||||
<div class="temple-detail-section">
|
||||
<div class="temple-detail-section-title">STATS</div>
|
||||
<div class="temple-detail-stats">
|
||||
<div class="temple-detail-stat">
|
||||
<span class="temple-detail-stat-value">{{ selectedTool.stats?.call_count ?? 0 }}</span>
|
||||
<span class="temple-detail-stat-label">Calls</span>
|
||||
</div>
|
||||
<div class="temple-detail-stat">
|
||||
<span class="temple-detail-stat-value">{{ selectedTool.stats?.error_rate ?? 0 }}%</span>
|
||||
<span class="temple-detail-stat-label">Error Rate</span>
|
||||
</div>
|
||||
<div class="temple-detail-stat">
|
||||
<span class="temple-detail-stat-value">{{ selectedTool.stats?.avg_duration_ms ?? 0 }}ms</span>
|
||||
<span class="temple-detail-stat-label">Avg Duration</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="temple-detail-section">
|
||||
<div class="temple-detail-section-title">COMMANDS ({{ selectedTool.commands.length }})</div>
|
||||
<div class="temple-commands">
|
||||
<div v-for="cmd in selectedTool.commands" :key="cmd.name" class="temple-command-item">
|
||||
<div class="temple-command-name">/{{ cmd.name }}</div>
|
||||
<div class="temple-command-desc">{{ cmd.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="temple-detail-section">
|
||||
<div class="temple-detail-section-title">TAGS</div>
|
||||
<div class="temple-tags">
|
||||
<span v-for="tag in selectedTool.tags" :key="tag" class="temple-tag">{{ tag }}</span>
|
||||
<span class="temple-tag" style="background:rgba(0,245,212,0.1);color:#00f5d4;border-color:rgba(0,245,212,0.2)">
|
||||
{{ selectedTool.source }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== SKILLS TAB ========== -->
|
||||
<div v-if="activeTab === 'skills'" class="temple-skills-container">
|
||||
|
||||
<!-- Skills Toolbar -->
|
||||
<div class="toolbar" style="padding:10px 16px;border-bottom:1px solid rgba(0,245,212,0.08);display:flex;gap:8px;align-items:center;">
|
||||
<button class="btn-add" style="display:flex;align-items:center;gap:6px;padding:6px 14px;border-radius:6px;background:rgba(0,245,212,0.1);border:1px solid rgba(0,245,212,0.3);color:#00f5d4;cursor:pointer;font-size:12px;font-weight:500;" @click="openNewSkillModal">
|
||||
<Plus :size="13" />
|
||||
<span>新建技能</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Skills Table -->
|
||||
<div v-if="skillsLoading" class="temple-loading" style="padding:40px;">
|
||||
<span>Loading skills...</span>
|
||||
</div>
|
||||
<div v-else-if="skills.length === 0" class="temple-empty">
|
||||
<div class="temple-empty-icon">✦</div>
|
||||
<span>暂无技能</span>
|
||||
</div>
|
||||
<div v-else class="skills-table-wrap" style="flex:1;overflow-y:auto;padding:0 16px;">
|
||||
<table class="skills-table" style="width:100%;border-collapse:collapse;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid rgba(0,245,212,0.1);">
|
||||
<th style="text-align:left;padding:8px 10px;font-size:10px;color:#5a6b7a;letter-spacing:1px;font-weight:600;">NAME</th>
|
||||
<th style="text-align:left;padding:8px 10px;font-size:10px;color:#5a6b7a;letter-spacing:1px;font-weight:600;">TYPE</th>
|
||||
<th style="text-align:left;padding:8px 10px;font-size:10px;color:#5a6b7a;letter-spacing:1px;font-weight:600;">VISIBILITY</th>
|
||||
<th style="text-align:left;padding:8px 10px;font-size:10px;color:#5a6b7a;letter-spacing:1px;font-weight:600;">STATUS</th>
|
||||
<th style="text-align:left;padding:8px 10px;font-size:10px;color:#5a6b7a;letter-spacing:1px;font-weight:600;">SOURCE</th>
|
||||
<th style="text-align:right;padding:8px 10px;font-size:10px;color:#5a6b7a;letter-spacing:1px;font-weight:600;">ACTIONS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="skill in skills"
|
||||
:key="skill.id"
|
||||
style="border-bottom:1px solid rgba(0,245,212,0.05);transition:background 0.12s;"
|
||||
:style="{ opacity: skill.is_active ? 1 : 0.5 }"
|
||||
>
|
||||
<td style="padding:10px 10px;">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<Bot :size="13" style="color:#00f5d4;flex-shrink:0;" />
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:600;color:#e8f4f8;">{{ skill.name }}</div>
|
||||
<div style="font-size:11px;color:#5a6b7a;margin-top:1px;">{{ skill.description || '无描述' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding:10px 10px;">
|
||||
<span class="mono-pill" style="font-size:10px;padding:2px 8px;border-radius:3px;background:rgba(0,245,212,0.06);color:#8a9bae;border:1px solid rgba(0,245,212,0.1);font-family:monospace;">{{ skill.agent_type }}</span>
|
||||
</td>
|
||||
<td style="padding:10px 10px;">
|
||||
<span style="font-size:10px;padding:2px 8px;border-radius:3px;"
|
||||
:style="skill.visibility === 'private' ? 'background:rgba(168,85,247,0.12);color:#c084fc;border:1px solid rgba(168,85,247,0.2)' : skill.visibility === 'team' ? 'background:rgba(34,197,94,0.12);color:#4ade80;border:1px solid rgba(34,197,94,0.2)' : 'background:rgba(59,130,246,0.12);color:#60a5fa;border:1px solid rgba(59,130,246,0.2)'">
|
||||
{{ skill.visibility }}
|
||||
</span>
|
||||
</td>
|
||||
<td style="padding:10px 10px;">
|
||||
<span style="display:flex;align-items:center;gap:5px;font-size:11px;" :style="{ color: skill.is_active ? '#4ade80' : '#6b7280' }">
|
||||
<span style="width:5px;height:5px;border-radius:50%;flex-shrink:0;" :style="{ background: skill.is_active ? '#4ade80' : '#6b7280' }"></span>
|
||||
{{ skill.is_active ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</td>
|
||||
<td style="padding:10px 10px;">
|
||||
<span style="font-size:10px;padding:2px 8px;border-radius:3px;" :style="skill.is_builtin ? 'background:rgba(251,191,36,0.1);color:#fbbf24;border:1px solid rgba(251,191,36,0.2)' : 'background:rgba(0,245,212,0.06);color:#8a9bae;border:1px solid rgba(0,245,212,0.1)'">
|
||||
{{ skill.is_builtin ? 'builtin' : 'custom' }}
|
||||
</span>
|
||||
</td>
|
||||
<td style="padding:10px 10px;">
|
||||
<div style="display:flex;align-items:center;justify-content:flex-end;gap:4px;">
|
||||
<button class="action-btn" :style="{ color: skill.is_active ? '#4ade80' : '#6b7280' }" :title="skill.is_active ? 'Disable' : 'Enable'" @click="toggleActive(skill)">
|
||||
<Eye v-if="skill.is_active" :size="13" />
|
||||
<EyeOff v-else :size="13" />
|
||||
</button>
|
||||
<button class="action-btn" title="Copy" style="color:#8a9bae;" @click="copySkill(skill)">
|
||||
<Copy :size="13" />
|
||||
</button>
|
||||
<button class="action-btn edit" title="Edit" style="color:#60a5fa;" @click="openEditSkillModal(skill)">
|
||||
<Edit2 :size="13" />
|
||||
</button>
|
||||
<button class="action-btn delete" title="Delete" style="color:#f87171;" :disabled="skill.is_builtin" @click="handleDeleteSkill(skill)">
|
||||
<Trash2 :size="13" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Skills Create/Edit Modal (inline) -->
|
||||
<Transition :css="false" @enter="(el: Element, done: () => void) => { (el as HTMLElement).style.opacity = '1'; done() }" @leave="(el: Element, done: () => void) => { (el as HTMLElement).style.opacity = '0'; done() }">
|
||||
<div v-if="modalOpen" class="modal-overlay" @click.self="closeModal">
|
||||
<div class="modal-card" style="width:min(560px,90vw);max-height:85vh;overflow-y:auto;">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">{{ editingSkill ? '// 编辑技能' : '// 新建技能' }}</span>
|
||||
<button class="btn-close" aria-label="关闭" @click="closeModal"><X :size="16" /></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="skill-name">// NAME</label>
|
||||
<input id="skill-name" v-model="skillForm.name" type="text" class="form-input" placeholder="Skill name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="skill-desc">// DESCRIPTION</label>
|
||||
<textarea id="skill-desc" v-model="skillForm.description" class="form-textarea" rows="2" placeholder="Describe what this skill does..."></textarea>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="skill-type">// AGENT TYPE</label>
|
||||
<select id="skill-type" v-model="skillForm.agent_type" class="form-select">
|
||||
<option v-for="t in AGENT_TYPES" :key="t" :value="t">{{ t }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="skill-vis">// VISIBILITY</label>
|
||||
<select id="skill-vis" v-model="skillForm.visibility" class="form-select">
|
||||
<option v-for="v in VISIBILITY_OPTIONS" :key="v" :value="v">{{ v }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">// TOOLS</label>
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:6px;">
|
||||
<label v-for="tool in AVAILABLE_TOOLS" :key="tool" style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:5px 8px;border-radius:4px;border:1px solid rgba(0,245,212,0.08);transition:all 0.12s;" :style="{ background: skillForm.tools?.includes(tool) ? 'rgba(0,245,212,0.08)' : 'transparent' }">
|
||||
<input type="checkbox" :checked="skillForm.tools?.includes(tool)" @change="() => { if (!skillForm.tools) skillForm.tools = []; const idx = skillForm.tools.indexOf(tool); if (idx >= 0) skillForm.tools.splice(idx, 1); else skillForm.tools.push(tool); }" style="accent-color:#00f5d4;" />
|
||||
<span style="font-size:11px;color:#8a9bae;">{{ tool }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group flex-1">
|
||||
<label class="form-label" for="skill-inst">// INSTRUCTIONS</label>
|
||||
<textarea id="skill-inst" v-model="skillForm.instructions" class="form-textarea code-textarea" rows="6" placeholder="Enter skill instructions..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" @click="closeModal">Cancel</button>
|
||||
<button
|
||||
class="btn-primary"
|
||||
:disabled="!skillForm.name || !skillForm.instructions"
|
||||
@click="handleSaveSkill"
|
||||
>
|
||||
{{ editingSkill ? 'Update' : 'Create' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped src="./templePage.css">
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.temple-page {
|
||||
padding: 24px;
|
||||
min-height: 100vh;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 28px;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.placeholder-content {
|
||||
/* Skills modal overrides */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
color: var(--text-secondary);
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.temple-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
.modal-card {
|
||||
background: #0f1117;
|
||||
border: 1px solid rgba(0, 245, 212, 0.15);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
</style>
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid rgba(0, 245, 212, 0.1);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #00f5d4;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 1px solid rgba(0, 245, 212, 0.15);
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: #8a9bae;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-close:hover {
|
||||
border-color: #00f5d4;
|
||||
color: #00f5d4;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 16px 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 12px 18px;
|
||||
border-top: 1px solid rgba(0, 245, 212, 0.1);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.form-row .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 10px;
|
||||
color: #5a6b7a;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-select,
|
||||
.form-textarea {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(0, 245, 212, 0.12);
|
||||
border-radius: 6px;
|
||||
color: #e8f4f8;
|
||||
font-size: 12.5px;
|
||||
padding: 7px 10px;
|
||||
outline: none;
|
||||
transition: border-color 0.15s;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-select:focus,
|
||||
.form-textarea:focus {
|
||||
border-color: rgba(0, 245, 212, 0.4);
|
||||
}
|
||||
|
||||
.form-select {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235a6b7a' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 8px center;
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.code-textarea {
|
||||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
padding: 7px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 245, 212, 0.2);
|
||||
background: transparent;
|
||||
color: #8a9bae;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
border-color: rgba(0, 245, 212, 0.4);
|
||||
color: #e8f4f8;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 7px 18px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 245, 212, 0.3);
|
||||
background: rgba(0, 245, 212, 0.1);
|
||||
color: #00f5d4;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: rgba(0, 245, 212, 0.15);
|
||||
border-color: #00f5d4;
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border: 1px solid rgba(0, 245, 212, 0.1);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: #8a9bae;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.action-btn:hover:not(:disabled) {
|
||||
background: rgba(0, 245, 212, 0.06);
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
533
frontend/src/pages/temple/templePage.css
Normal file
533
frontend/src/pages/temple/templePage.css
Normal file
@@ -0,0 +1,533 @@
|
||||
/* ============================================================
|
||||
Temple Modal - 悬浮弹窗样式
|
||||
============================================================ */
|
||||
|
||||
/* CSS Variables 复用 jarvis 体系 */
|
||||
.temple-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(4px);
|
||||
animation: overlayFadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes overlayFadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.temple-modal {
|
||||
width: min(95vw, 1400px);
|
||||
height: min(88vh, 900px);
|
||||
background: var(--bg-void, #0a0a0f);
|
||||
border: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.15));
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0, 245, 212, 0.05),
|
||||
0 24px 64px rgba(0, 0, 0, 0.6),
|
||||
0 0 80px rgba(0, 245, 212, 0.04);
|
||||
animation: modalSlideIn 0.22s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
@keyframes modalSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.96) translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Header ---- */
|
||||
.temple-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px 12px;
|
||||
border-bottom: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.1));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.temple-header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.temple-title-icon {
|
||||
font-size: 18px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.temple-title-text {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #e8f4f8);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.temple-close-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.2));
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary, #8a9bae);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.temple-close-btn:hover {
|
||||
border-color: var(--accent-cyan, #00f5d4);
|
||||
color: var(--accent-cyan, #00f5d4);
|
||||
background: rgba(0, 245, 212, 0.06);
|
||||
}
|
||||
|
||||
/* ---- Tab Bar ---- */
|
||||
.temple-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 10px 20px 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.temple-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 16px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: none;
|
||||
background: transparent;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
position: relative;
|
||||
bottom: -1px;
|
||||
}
|
||||
|
||||
.temple-tab:hover {
|
||||
color: var(--text-secondary, #8a9bae);
|
||||
}
|
||||
|
||||
.temple-tab.active {
|
||||
color: var(--accent-cyan, #00f5d4);
|
||||
background: rgba(0, 245, 212, 0.06);
|
||||
border-color: var(--border-subtle, rgba(0, 245, 212, 0.15));
|
||||
}
|
||||
|
||||
.temple-tab-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ---- Metrics Strip ---- */
|
||||
.temple-metrics {
|
||||
display: flex;
|
||||
gap: 1px;
|
||||
padding: 10px 20px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.08));
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.temple-metric {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 245, 212, 0.03);
|
||||
border: 1px solid rgba(0, 245, 212, 0.06);
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.temple-metric-label {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
letter-spacing: 1px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.temple-metric-value {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-cyan, #00f5d4);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ---- Main Content ---- */
|
||||
.temple-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* ---- Tools Tab Layout ---- */
|
||||
.temple-tools-layout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Category Tree (Left sidebar) */
|
||||
.temple-tree {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
padding: 12px 0;
|
||||
border-right: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.08));
|
||||
}
|
||||
|
||||
.temple-tree::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.temple-tree::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 245, 212, 0.15);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.temple-tree-section {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.temple-tree-section-title {
|
||||
padding: 6px 16px 4px;
|
||||
font-size: 10px;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
letter-spacing: 1.2px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.temple-tree-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 16px;
|
||||
font-size: 12.5px;
|
||||
color: var(--text-secondary, #8a9bae);
|
||||
cursor: pointer;
|
||||
transition: all 0.12s ease;
|
||||
border-left: 2px solid transparent;
|
||||
}
|
||||
|
||||
.temple-tree-item:hover {
|
||||
background: rgba(0, 245, 212, 0.05);
|
||||
color: var(--text-primary, #e8f4f8);
|
||||
}
|
||||
|
||||
.temple-tree-item.active {
|
||||
background: rgba(0, 245, 212, 0.08);
|
||||
color: var(--accent-cyan, #00f5d4);
|
||||
border-left-color: var(--accent-cyan, #00f5d4);
|
||||
}
|
||||
|
||||
.temple-tree-subgroup {
|
||||
padding-left: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.temple-tree-dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
opacity: 0.5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Tools List & Detail (Right panel) */
|
||||
.temple-tools-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.temple-tool-list {
|
||||
padding: 12px 16px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.temple-tool-list::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.temple-tool-list::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 245, 212, 0.15);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.temple-tool-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.08));
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.temple-tool-card:hover {
|
||||
border-color: rgba(0, 245, 212, 0.25);
|
||||
background: rgba(0, 245, 212, 0.04);
|
||||
}
|
||||
|
||||
.temple-tool-card.selected {
|
||||
border-color: var(--accent-cyan, #00f5d4);
|
||||
background: rgba(0, 245, 212, 0.07);
|
||||
}
|
||||
|
||||
.temple-tool-card-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
background: rgba(0, 245, 212, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.temple-tool-card-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.temple-tool-card-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #e8f4f8);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.temple-tool-card-desc {
|
||||
font-size: 11.5px;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.temple-tool-card-commands {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
background: rgba(0, 245, 212, 0.06);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Tool Detail Panel */
|
||||
.temple-detail {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.08));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.temple-detail::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.temple-detail::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 245, 212, 0.15);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.temple-detail-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.temple-detail-name {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #e8f4f8);
|
||||
}
|
||||
|
||||
.temple-detail-display-name {
|
||||
font-size: 13px;
|
||||
color: var(--accent-cyan, #00f5d4);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.temple-detail-source {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
background: rgba(0, 245, 212, 0.05);
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid rgba(0, 245, 212, 0.1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.temple-detail-desc {
|
||||
font-size: 12.5px;
|
||||
color: var(--text-secondary, #8a9bae);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.temple-detail-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.temple-detail-section-title {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
letter-spacing: 1.2px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.temple-detail-stats {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.temple-detail-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 8px 14px;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 245, 212, 0.06);
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.temple-detail-stat-value {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #e8f4f8);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.temple-detail-stat-label {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Commands List */
|
||||
.temple-commands {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.temple-command-item {
|
||||
padding: 10px 14px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 245, 212, 0.06);
|
||||
}
|
||||
|
||||
.temple-command-name {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-cyan, #00f5d4);
|
||||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.temple-command-desc {
|
||||
font-size: 11.5px;
|
||||
color: var(--text-secondary, #8a9bae);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
.temple-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.temple-tag {
|
||||
font-size: 10px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
background: rgba(123, 44, 191, 0.15);
|
||||
color: #c084fc;
|
||||
border: 1px solid rgba(123, 44, 191, 0.2);
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.temple-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
height: 100%;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.temple-empty-icon {
|
||||
font-size: 32px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.temple-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
font-size: 13px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Skills Tab overrides */
|
||||
.temple-skills-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Section label */
|
||||
.temple-section-label {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted, #5a6b7a);
|
||||
letter-spacing: 1.2px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
padding: 10px 16px 6px;
|
||||
}
|
||||
Reference in New Issue
Block a user