Newer
Older
navi-1 / webclient / src / components / messages / ToolCard.vue
<template>
  <details
    ref="detailsEl"
    class="tool-card"
    :class="{
      'is-success': !tool.pending && tool.success,
      'is-error': !tool.pending && !tool.success
    }"
  >
    <summary>
      <span class="tool-status-icon">
        <span v-if="tool.pending" class="spinner"></span>
        <i v-else-if="tool.success" class="ph ph-check-circle"></i>
        <i v-else class="ph ph-x-circle"></i>
      </span>
      <span class="tool-emoji">{{ toolIcon }}</span>
      <span class="tool-name">{{ tool.name }}</span>
      <i class="ph ph-caret-down tool-chevron"></i>
    </summary>

    <div class="tool-card-body">
      <div v-if="tool.args" class="tool-section">
        <div class="tool-section-label">Arguments</div>
        <pre class="tool-code">{{ formatJson(tool.args) }}</pre>
      </div>
      <div v-if="tool.result != null" class="tool-section">
        <div class="tool-section-label">Result</div>
        <pre class="tool-code">{{ formatResult(tool.result) }}</pre>
      </div>
      <!-- Subagent steps nested inside spawn_agent card -->
      <div v-if="tool.steps?.length" class="subagent-steps">
        <template v-for="(step, i) in tool.steps" :key="i">
          <ThinkingCard v-if="step.kind === 'turn_thinking'" :msg="step" />
          <SubagentStep v-else :tool="step" />
        </template>
      </div>
    </div>
  </details>
</template>

<script setup>
import { ref, computed, watch } from 'vue'
import SubagentStep from './SubagentStep.vue'
import ThinkingCard from './ThinkingCard.vue'

const props = defineProps({ tool: { type: Object, required: true } })

const detailsEl = ref(null)

// Auto-open when tool starts, auto-close when done
watch(
  () => props.tool.pending,
  (pending, wasPending) => {
    if (!detailsEl.value) return
    if (pending) {
      detailsEl.value.setAttribute('open', '')
    } else if (wasPending !== undefined) {
      // Tool just completed — collapse
      detailsEl.value.removeAttribute('open')
    }
  },
  { immediate: true }
)

const TOOL_ICONS = {
  web_search: '🔍',
  web_fetch: '🌐',
  filesystem: '📁',
  read_file: '📄',
  write_file: '📝',
  code_exec: '⚙️',
  terminal: '💻',
  ssh_exec: '🖥️',
  spawn_agent: '🤖',
  switch_profile: '🔄',
}

const toolIcon = computed(() => TOOL_ICONS[props.tool.name] ?? '🔧')

function formatJson(val) {
  if (typeof val === 'string') {
    try { return JSON.stringify(JSON.parse(val), null, 2) } catch { return val }
  }
  return JSON.stringify(val, null, 2)
}

function formatResult(val) {
  if (typeof val === 'string') return val
  return JSON.stringify(val, null, 2)
}
</script>