Skip to main content
Glama
MIT License
27,120
19,789
  • Linux
  • Apple
TryItResultContent.vue13.4 kB
<script setup lang="ts"> import ace, { type Ace } from 'ace-builds'; import themeTomorrowUrl from 'ace-builds/src-noconflict/theme-tomorrow?url'; import themeTomorrowNightUrl from 'ace-builds/src-noconflict/theme-tomorrow_night?url'; import { BarChart2, Copy, Download, GitFork, HeartHandshake, PackageSearch, Share, Star } from 'lucide-vue-next'; import { useData } from 'vitepress'; import { computed, onMounted, onUnmounted, ref, watch } from 'vue'; import { VAceEditor } from 'vue3-ace-editor'; import type { PackResult } from '../api/client'; import { canShareFiles, copyToClipboard, downloadResult, formatTimestamp, getEditorOptions, shareResult, } from '../utils/resultViewer'; ace.config.setModuleUrl('ace/theme/tomorrow', themeTomorrowUrl); ace.config.setModuleUrl('ace/theme/tomorrow_night', themeTomorrowNightUrl); const lightTheme = 'tomorrow'; const darkTheme = 'tomorrow_night'; const props = defineProps<{ result: PackResult; }>(); const copied = ref(false); const shared = ref(false); const canShare = ref(canShareFiles()); const { isDark } = useData(); const editorInstance = ref<Ace.Editor | null>(null); const isMobile = ref(false); const tooltipContainer = ref<HTMLElement | null>(null); const tooltipContent = ref<HTMLElement | null>(null); const editorOptions = computed(() => ({ ...getEditorOptions(), theme: isDark.value ? `ace/theme/${darkTheme}` : `ace/theme/${lightTheme}`, })); watch(isDark, (newIsDark) => { if (editorInstance.value) { editorInstance.value.setTheme(newIsDark ? `ace/theme/${darkTheme}` : `ace/theme/${lightTheme}`); } }); const formattedTimestamp = computed(() => { return formatTimestamp(props.result.metadata.timestamp); }); const handleCopy = async (event: Event) => { event.preventDefault(); event.stopPropagation(); const success = await copyToClipboard(props.result.content, props.result.format); if (success) { copied.value = true; setTimeout(() => { copied.value = false; }, 2000); } }; const handleDownload = (event: Event) => { event.preventDefault(); event.stopPropagation(); downloadResult(props.result.content, props.result.format, props.result); }; const handleShare = async (event: Event) => { event.preventDefault(); event.stopPropagation(); // Only allow sharing on mobile devices with Web Share API support if (!isMobile.value || !canShare.value) { console.log('Share is only available on mobile devices'); return; } const success = await shareResult(props.result.content, props.result.format, props.result); if (success) { shared.value = true; setTimeout(() => { shared.value = false; }, 2000); } else { console.log('Share was cancelled or failed'); } }; const handleEditorMount = (editor: Ace.Editor) => { editorInstance.value = editor; }; const updateTooltipPosition = () => { if (!tooltipContainer.value || !tooltipContent.value || isMobile.value) return; const containerRect = tooltipContainer.value.getBoundingClientRect(); const tooltipEl = tooltipContent.value; // Position above the button with proper spacing for the arrow (like existing tooltips) tooltipEl.style.top = `${containerRect.top - 46}px`; tooltipEl.style.left = `${containerRect.left + containerRect.width / 2}px`; // Show tooltip (override CSS hover states) tooltipEl.style.opacity = '1'; tooltipEl.style.visibility = 'visible'; }; const hideTooltip = () => { if (tooltipContent.value) { tooltipContent.value.style.opacity = '0'; tooltipContent.value.style.visibility = 'hidden'; } }; const messages = [ { type: 'sponsor', link: 'https://github.com/sponsors/yamadashy', icon: HeartHandshake, text: 'Your support helps maintain and improve it. Thank you!', color: '#b04386', }, { type: 'star', link: 'https://github.com/yamadashy/repomix', icon: Star, text: 'If you like Repomix, please give us a star on GitHub!', color: '#f1c40f', }, ]; const currentMessageIndex = ref(Math.floor(Math.random() * messages.length)); const supportMessage = computed(() => ({ type: messages[currentMessageIndex.value].type, link: messages[currentMessageIndex.value].link, icon: messages[currentMessageIndex.value].icon, text: messages[currentMessageIndex.value].text, color: messages[currentMessageIndex.value].color, })); const handleResize = () => { isMobile.value = window.innerWidth <= 768; }; const handleScroll = () => { // Hide tooltip on scroll to prevent detachment from button if (tooltipContent.value) { tooltipContent.value.style.opacity = '0'; tooltipContent.value.style.visibility = 'hidden'; } }; onMounted(() => { isMobile.value = window.innerWidth <= 768; window.addEventListener('resize', handleResize); window.addEventListener('scroll', handleScroll, { passive: true }); }); onUnmounted(() => { window.removeEventListener('resize', handleResize); window.removeEventListener('scroll', handleScroll); }); </script> <template> <div class="content-wrapper"> <div class="metadata-panel"> <div class="metadata-section"> <h3><GitFork :size="16" class="section-icon" /> Repository Info</h3> <dl> <dt>Repository</dt> <dd>{{ result.metadata.repository }}</dd> <dt>Generated At</dt> <dd>{{ formattedTimestamp }}</dd> <dt>Format</dt> <dd>{{ result.format }}</dd> </dl> </div> <div class="metadata-section"> <h3><PackageSearch :size="16" class="section-icon" /> Pack Summary</h3> <dl v-if="result.metadata.summary"> <dt>Total Files</dt> <dd>{{ result.metadata.summary.totalFiles.toLocaleString() }} <span class="unit">files</span></dd> <dt>Total Tokens</dt> <dd>{{ result.metadata.summary.totalTokens.toLocaleString() }} <span class="unit">tokens</span></dd> <dt>Total Size</dt> <dd>{{ result.metadata.summary.totalCharacters.toLocaleString() }} <span class="unit">chars</span></dd> </dl> </div> <div class="metadata-section" v-if="result.metadata.topFiles"> <h3><BarChart2 :size="16" class="section-icon" /> Top {{ result.metadata.topFiles.length }} Files</h3> <ol class="top-files-list"> <li v-for="file in result.metadata.topFiles" :key="file.path"> <div class="file-path">{{ file.path }}</div> <div class="file-stats"> {{ file.tokenCount.toLocaleString() }} <span class="unit">tokens</span> <span class="separator-unit">|</span> {{ file.charCount.toLocaleString() }} <span class="unit">chars</span> <span class="separator-unit">|</span> {{ ((file.tokenCount / result.metadata.summary.totalTokens) * 100).toFixed(1) }}<span class="unit">%</span> </div> </li> </ol> </div> </div> <div class="output-panel"> <div class="output-actions"> <button class="action-button" @click="handleCopy" :class="{ copied }" > <Copy :size="16" /> {{ copied ? 'Copied!' : 'Copy' }} </button> <button class="action-button" @click="handleDownload" > <Download :size="16" /> Download </button> <div v-if="canShare" class="mobile-only" style="flex-basis: 100%"></div> <div v-if="canShare" class="tooltip-container" ref="tooltipContainer" @mouseenter="updateTooltipPosition" @mouseleave="hideTooltip"> <button class="action-button" @click="handleShare" :class="{ shared }" :disabled="!isMobile" aria-label="Share output via mobile apps" > <Share :size="16" /> {{ shared ? 'Shared!' : 'Open with your app' }} </button> <div class="tooltip-content desktop-only" ref="tooltipContent"> Only available on mobile devices <div class="tooltip-arrow"></div> </div> </div> </div> <div class="editor-container"> <VAceEditor v-model:value="result.content" :lang="'text'" :style="{ height: '100%', width: '100%' }" :options="editorOptions" @mount="handleEditorMount" /> </div> </div> <div class="support-notice"> <div class="support-message"> <a :href="supportMessage.link" target="_blank" rel="noopener noreferrer" class="support-link"> <component :is="supportMessage.icon" :size="14" class="support-icon" /> {{ supportMessage.text }} </a> </div> </div> </div> </template> <style scoped> .content-wrapper { display: grid; grid-template-columns: 300px 1fr; grid-template-rows: 445px auto; } .metadata-panel { padding: 16px; border-right: 1px solid var(--vp-c-border); background: var(--vp-c-bg-soft); overflow-y: auto; } .metadata-section { margin-bottom: 24px; } .metadata-section:last-child { margin-bottom: 0; } .metadata-section h3 { font-size: 14px; font-weight: 600; margin: 0 0 12px; color: var(--vp-c-text-1); display: flex; align-items: center; gap: 6px; } .section-icon { color: var(--vp-c-text-2); } dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 8px; font-size: 13px; } dt { color: var(--vp-c-text-2); font-weight: 500; } dd { margin: 0; color: var(--vp-c-text-1); text-transform: lowercase; } .unit { color: var(--vp-c-text-2); margin-left: 0.3em; } .separator-unit { color: var(--vp-c-text-3); margin: 0 0.5em; } .top-files-list { margin: 0; padding: 0 0 0 0; font-size: 13px; } .top-files-list li { margin-bottom: 8px; border-left: 2px solid var(--vp-c-divider); padding-left: 8px; } .file-path { color: var(--vp-c-text-1); margin-bottom: 2px; word-break: break-all; } .file-stats { font-size: 12px; color: var(--vp-c-text-1); display: flex; align-items: center; } .output-panel { display: flex; flex-direction: column; height: 100%; max-height: 500px; background: var(--vp-c-bg); overflow: hidden; } .output-actions { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px; background: var(--vp-c-bg); border-bottom: 1px solid var(--vp-c-border); flex-shrink: 0; } .action-button { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border: 1px solid var(--vp-c-border); border-radius: 6px; background: var(--vp-c-bg-soft); color: var(--vp-c-text-1); cursor: pointer; transition: all 0.2s ease; } .action-button:hover { border-color: var(--vp-c-brand-1); } .action-button.copied { background: var(--vp-c-brand-1); color: white; border-color: var(--vp-c-brand-1); } .action-button.shared { background: var(--vp-c-brand-1); color: white; border-color: var(--vp-c-brand-1); } .editor-container { height: 100%; width: 100%; font-family: var(--vp-font-family-mono); } .support-notice { grid-column: 1 / -1; padding: 8px; background: var(--vp-c-bg-soft); border-top: 1px solid var(--vp-c-border); display: flex; justify-content: center; align-items: center; text-align: center; min-height: 45px; } .support-message { display: flex; align-items: center; justify-content: center; color: var(--vp-c-text-2); font-size: 12px; width: 100%; } .support-icon { flex-shrink: 0; transition: color 0.3s ease; color: v-bind('supportMessage.color'); } .support-link { text-decoration: none; font-weight: normal; transition: color 0.3s ease; display: flex; align-items: center; gap: 6px; } .support-link:hover { color: var(--vp-c-brand-1); } .mobile-only { display: none; } @media (max-width: 768px) { .content-wrapper { grid-template-columns: 1fr; grid-template-rows: auto minmax(500px, auto) auto; height: auto; } .metadata-panel { border-right: none; border-bottom: 1px solid var(--vp-c-border); max-height: 400px; overflow-y: auto; } .output-panel { height: 500px; } .support-notice { padding: 16px; } .support-message { max-width: 100%; } .mobile-only { display: inline-flex; } } .tooltip-container { position: relative; display: inline-block; } .tooltip-content { position: fixed; transform: translateX(-50%); margin-bottom: 8px; padding: 8px 12px; background: #333; color: white; font-size: 0.875rem; white-space: nowrap; border-radius: 4px; opacity: 0; visibility: hidden; transition: opacity 0.2s, visibility 0.2s; z-index: 9999; pointer-events: none; text-align: left; } .tooltip-arrow { position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border-width: 8px; border-style: solid; border-color: #333 transparent transparent transparent; } .tooltip-container:hover .tooltip-content { opacity: 1; visibility: visible; } .desktop-only { display: block; } @media (max-width: 768px) { .desktop-only { display: none; } } .action-button:disabled { opacity: 0.5; cursor: not-allowed; } .action-button:disabled:hover { opacity: 0.5; } /* Dark mode support for tooltip */ html.dark .tooltip-content { background: #333; color: #ffffff; } html.dark .tooltip-arrow { border-color: #333 transparent transparent transparent; } </style>

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yamadashy/repomix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server