oxios 1.15.0

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { useQuery } from '@tanstack/react-query'
import { Code, FileText, Pencil } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { Button } from '@/components/ui/button'
import { api } from '@/lib/api-client'
import { cn } from '@/lib/utils'
import type { Skill } from '@/types'

interface SkillContentProps {
  skill: Skill
  onEdit?: () => void
}

/**
 * Content tab of the skill inspector (design F4).
 *
 * Lazy-fetches the raw SKILL.md body only when this component mounts (i.e. when
 * the user opens the Content tab), then renders it as markdown. A Raw toggle
 * shows the verbatim source. The Edit button opens the inline editor (F3).
 */
export function SkillContent({ skill, onEdit }: SkillContentProps) {
  const { t } = useTranslation()
  const [raw, setRaw] = useState(false)

  const {
    data: content,
    isLoading,
    isError,
  } = useQuery({
    queryKey: ['skill', skill.name, 'content'],
    queryFn: async () => {
      const r = await api.get<{ name: string; content: string }>(
        `/api/skills/${encodeURIComponent(skill.name)}/content`,
      )
      return r?.content ?? ''
    },
    staleTime: 30_000,
  })

  if (isLoading) {
    return (
      <div className="space-y-2">
        <div className="h-3 bg-muted rounded w-3/4 animate-pulse" />
        <div className="h-3 bg-muted rounded w-1/2 animate-pulse" />
        <div className="h-3 bg-muted rounded w-2/3 animate-pulse" />
      </div>
    )
  }

  if (isError) {
    return <p className="text-sm text-destructive">{t('skills.loadContentFailed')}</p>
  }

  return (
    <div className="space-y-3">
      <div className="flex items-center gap-2">
        <Button variant="ghost" size="sm" className="gap-1.5" onClick={() => setRaw((v) => !v)}>
          {raw ? <FileText className="h-3.5 w-3.5" /> : <Code className="h-3.5 w-3.5" />}
          {raw ? t('skills.content') : t('skills.viewRaw')}
        </Button>
        {onEdit && (
          <Button variant="outline" size="sm" className="gap-1.5 ml-auto" onClick={onEdit}>
            <Pencil className="h-3.5 w-3.5" />
            {t('skills.edit')}
          </Button>
        )}
      </div>

      {raw ? (
        <pre
          className={cn(
            'text-xs font-mono whitespace-pre-wrap break-words',
            'bg-background border rounded-md p-3 max-h-[420px] overflow-auto',
            'text-muted-foreground leading-relaxed',
          )}
        >
          {content}
        </pre>
      ) : (
        <div className="prose prose-sm dark:prose-invert max-w-none text-muted-foreground [&_code]:font-mono [&_code]:text-xs [&_pre]:bg-background [&_pre]:border">
          <ReactMarkdown remarkPlugins={[remarkGfm]}>{content ?? ''}</ReactMarkdown>
        </div>
      )}
    </div>
  )
}