|
@@ -75,8 +75,19 @@ function autoResize(e) {
|
|
|
el.style.height = Math.min(el.scrollHeight, 180) + 'px'
|
|
el.style.height = Math.min(el.scrollHeight, 180) + 'px'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 尝试把 JSON 字符串格式化为缩进形式,失败则原样返回
|
|
|
|
|
+function prettyJson(str) {
|
|
|
|
|
+ const s = (str || '').trim()
|
|
|
|
|
+ if (!s) return ''
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.stringify(JSON.parse(s), null, 2)
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return s
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
watch(
|
|
watch(
|
|
|
- () => messages.map((m) => m.content).join('|'),
|
|
|
|
|
|
|
+ () => messages.map((m) => `${m.content}|${m.args || ''}|${m.result || ''}|${m.status || ''}`).join('~'),
|
|
|
async () => {
|
|
async () => {
|
|
|
await nextTick()
|
|
await nextTick()
|
|
|
if (listEl.value) listEl.value.scrollTop = listEl.value.scrollHeight
|
|
if (listEl.value) listEl.value.scrollTop = listEl.value.scrollHeight
|
|
@@ -155,8 +166,23 @@ watch(
|
|
|
<!-- 工具调用 -->
|
|
<!-- 工具调用 -->
|
|
|
<div v-else-if="m.role === 'tool'" class="msg-row tool">
|
|
<div v-else-if="m.role === 'tool'" class="msg-row tool">
|
|
|
<div class="tool-card">
|
|
<div class="tool-card">
|
|
|
- <span class="tool-icon">⚙</span>
|
|
|
|
|
- {{ m.content }}
|
|
|
|
|
|
|
+ <div class="tool-head">
|
|
|
|
|
+ <span class="tool-icon">🔧</span>
|
|
|
|
|
+ <span class="tool-title">工具调用</span>
|
|
|
|
|
+ <span class="tool-name">{{ m.name }}</span>
|
|
|
|
|
+ <span class="tool-badge" :class="m.status">
|
|
|
|
|
+ <span v-if="m.status !== 'done'" class="tool-spinner"></span>
|
|
|
|
|
+ {{ m.status === 'done' ? '完成' : '调用中' }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="prettyJson(m.args)" class="tool-section">
|
|
|
|
|
+ <div class="tool-section-label">参数</div>
|
|
|
|
|
+ <pre class="tool-code">{{ prettyJson(m.args) }}</pre>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="prettyJson(m.result)" class="tool-section">
|
|
|
|
|
+ <div class="tool-section-label">结果</div>
|
|
|
|
|
+ <pre class="tool-code">{{ prettyJson(m.result) }}</pre>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
@@ -564,19 +590,107 @@ watch(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.tool-card {
|
|
.tool-card {
|
|
|
- display: inline-flex;
|
|
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ max-width: 640px;
|
|
|
|
|
+ background: #fbfcff;
|
|
|
|
|
+ border: 1px solid #e6eaf5;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ box-shadow: 0 1px 2px rgba(20, 24, 40, 0.04);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-head {
|
|
|
|
|
+ display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
gap: 8px;
|
|
|
- background: #f8f8ff;
|
|
|
|
|
- border: 1px solid #e8eaf6;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- padding: 7px 14px;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- color: #5c6bc0;
|
|
|
|
|
|
|
+ padding: 10px 14px;
|
|
|
|
|
+ background: #f4f6fd;
|
|
|
|
|
+ border-bottom: 1px solid #e6eaf5;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.tool-icon {
|
|
.tool-icon {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-title {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #8a93a6;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-name {
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #3b4256;
|
|
|
|
|
+ font-family: 'SFMono-Regular', ui-monospace, Menlo, Consolas, monospace;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-badge {
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 5px;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ padding: 3px 9px;
|
|
|
|
|
+ border-radius: 999px;
|
|
|
|
|
+ background: #eef1f7;
|
|
|
|
|
+ color: #7a8296;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-badge.running {
|
|
|
|
|
+ background: #fff5e6;
|
|
|
|
|
+ color: #d98a1f;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-badge.done {
|
|
|
|
|
+ background: #e8f7ee;
|
|
|
|
|
+ color: #2f9e5f;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-spinner {
|
|
|
|
|
+ width: 9px;
|
|
|
|
|
+ height: 9px;
|
|
|
|
|
+ border: 1.5px solid currentColor;
|
|
|
|
|
+ border-top-color: transparent;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ animation: tool-spin 0.7s linear infinite;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@keyframes tool-spin {
|
|
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-section {
|
|
|
|
|
+ padding: 10px 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-section + .tool-section {
|
|
|
|
|
+ border-top: 1px dashed #e6eaf5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-section-label {
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: #9ca3af;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ letter-spacing: 0.4px;
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tool-code {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ background: #f7f8fc;
|
|
|
|
|
+ border: 1px solid #eceef4;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 10px 12px;
|
|
|
|
|
+ font-family: 'SFMono-Regular', ui-monospace, Menlo, Consolas, monospace;
|
|
|
|
|
+ font-size: 12.5px;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+ color: #3b4256;
|
|
|
|
|
+ white-space: pre-wrap;
|
|
|
|
|
+ word-break: break-word;
|
|
|
|
|
+ overflow-x: auto;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* 错误条 */
|
|
/* 错误条 */
|