ToolCall.vue 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. <script setup lang="ts">
  2. import { computed, ref } from 'vue'
  3. import type { TimelineItem } from '../store'
  4. import { stringifyInput, toolLabel } from '../util'
  5. const props = defineProps<{ item: Extract<TimelineItem, { kind: 'tool' }> }>()
  6. // 失败或被拒的工具默认展开
  7. const open = ref(false)
  8. const expanded = computed(() => open.value || props.item.status === 'error')
  9. const full = ref(false)
  10. const inputText = computed(() => {
  11. const input = props.item.input as Record<string, unknown> | null
  12. if (input && typeof input === 'object' && typeof input.command === 'string') return input.command
  13. return stringifyInput(input)
  14. })
  15. </script>
  16. <template>
  17. <div class="tool">
  18. <button class="tool-head" type="button" :aria-expanded="expanded" @click="open = !open">
  19. <span class="tool-name">{{ toolLabel(item.name) }}</span>
  20. <span class="tool-title">{{ item.title }}</span>
  21. <span v-if="item.status === 'running'" class="codicon codicon-loading codicon-modifier-spin tool-spin" />
  22. <span v-else-if="item.status === 'denied'" class="tool-tag">已拒绝</span>
  23. <span class="codicon tool-chevron" :class="expanded ? 'codicon-chevron-down' : 'codicon-chevron-right'" />
  24. </button>
  25. <div v-if="expanded" class="tool-body">
  26. <div class="row">
  27. <div class="row-label">IN</div>
  28. <pre class="row-content" :class="{ clip: !full }" @click="full = true">{{ inputText }}</pre>
  29. </div>
  30. <div v-if="item.status !== 'running'" class="row">
  31. <div class="row-label">OUT</div>
  32. <pre class="row-content" :class="{ clip: !full, error: item.status !== 'ok' }" @click="full = true">{{ item.output || '(无输出)' }}<template v-if="item.truncated">
  33. …(输出过长,已截断)</template></pre>
  34. </div>
  35. </div>
  36. </div>
  37. </template>
  38. <style scoped>
  39. .tool { width: 100%; }
  40. .tool-head {
  41. display: flex;
  42. align-items: baseline;
  43. gap: 6px;
  44. width: 100%;
  45. padding: 0;
  46. border: none;
  47. background: none;
  48. cursor: pointer;
  49. text-align: left;
  50. color: var(--app-primary-foreground);
  51. }
  52. .tool-name { font-weight: 700; flex: none; }
  53. .tool-title {
  54. flex: 1;
  55. min-width: 0;
  56. font-family: var(--app-monospace-font-family);
  57. font-size: 0.85em;
  58. color: var(--app-secondary-foreground);
  59. overflow: hidden;
  60. text-overflow: ellipsis;
  61. white-space: nowrap;
  62. }
  63. .tool-spin { font-size: 12px; color: var(--app-secondary-foreground); }
  64. .tool-tag { font-size: 11px; color: var(--app-danger); flex: none; }
  65. .tool-chevron { font-size: 12px; color: var(--app-secondary-foreground); opacity: 0; flex: none; }
  66. .tool-head:hover .tool-chevron, .tool-head[aria-expanded='true'] .tool-chevron { opacity: 1; }
  67. .tool-body {
  68. margin-top: 6px;
  69. display: grid;
  70. grid-template-columns: max-content 1fr;
  71. border: 1px solid var(--app-input-border);
  72. border-radius: var(--corner-radius-medium);
  73. background: var(--app-code-background);
  74. overflow: hidden;
  75. }
  76. .row { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; padding: 4px; border-top: 0.5px solid var(--app-input-border); }
  77. .row:first-child { border-top: none; }
  78. .row-label {
  79. grid-column: 1;
  80. font-family: var(--app-monospace-font-family);
  81. font-size: 0.85em;
  82. opacity: 0.5;
  83. padding: 4px 8px 4px 4px;
  84. }
  85. .row-content {
  86. grid-column: 2;
  87. margin: 0;
  88. padding: 4px;
  89. min-width: 0;
  90. white-space: pre-wrap;
  91. word-break: break-word;
  92. font-family: var(--app-monospace-font-family);
  93. font-size: var(--app-monospace-font-size);
  94. }
  95. .row-content.clip {
  96. max-height: 60px;
  97. overflow: hidden;
  98. cursor: pointer;
  99. mask-image: linear-gradient(to bottom, #000 40px, transparent 60px);
  100. }
  101. .row-content.error { color: var(--app-error-foreground); }
  102. </style>