Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`ChatThoughtChain > :props > :items - array 1`] = `
<div
class="t-chat__thought-chain"
>

<div
class="t-chat__thought-chain-item t-chat__thought-chain-item--success"
>
<div
class="t-chat__thought-chain-item__node"
>
<span
class="t-chat__thought-chain-item__icon"
>
<svg
class="t-icon t-icon-check-circle"
fill="none"
height="1em"
style="fill: none;"
viewBox="0 0 24 24"
width="1em"
>
<g
id="check-circle"
>
<path
d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12Z"
fill="transparent"
id="fill1"
/>
<path
d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12Z"
id="stroke1"
stroke="currentColor"
stroke-linecap="square"
stroke-width="2"
/>
<path
d="M16.5 9L10.5 15L7.5 12"
id="stroke2"
stroke="currentColor"
stroke-linecap="square"
stroke-width="2"
/>
</g>
</svg>
</span>
<span
class="t-chat__thought-chain-item__line"
/>
</div>
<div
class="t-chat__thought-chain-item__detail"
>
<div
class="t-chat__thought-chain-item__header t-chat__thought-chain-item__header--collapsible"
>
<span
class="t-chat__thought-chain-item__title"
>
分析问题
</span>
<svg
class="t-icon t-icon-chevron-down t-chat__thought-chain-item__arrow"
fill="none"
height="1em"
style="fill: none;"
viewBox="0 0 24 24"
width="1em"
>
<g
id="chevron-down"
>
<path
d="M17.5 9.5L12 15L6.5 9.5"
id="stroke1"
stroke="currentColor"
stroke-linecap="square"
stroke-width="2"
/>
</g>
</svg>
</div>
<!---->
</div>
</div>
<div
class="t-chat__thought-chain-item t-chat__thought-chain-item--processing"
>
<div
class="t-chat__thought-chain-item__node"
>
<span
class="t-chat__thought-chain-item__icon"
>
<svg
class="t-icon t-icon-loading"
fill="none"
height="1em"
style="fill: none;"
viewBox="0 0 24 24"
width="1em"
>
<path
d="M12 2.25C6.61556 2.25 2.25 6.61556 2.25 12C2.25 17.3844 6.61556 21.75 12 21.75V19.3125C7.96142 19.3125 4.6875 16.0386 4.6875 12C4.6875 7.96142 7.96142 4.6875 12 4.6875C16.0386 4.6875 19.3125 7.96142 19.3125 12H21.75C21.75 6.61556 17.3844 2.25 12 2.25Z"
fill="currentColor"
id="loading"
/>
</svg>
</span>
<span
class="t-chat__thought-chain-item__line"
/>
</div>
<div
class="t-chat__thought-chain-item__detail"
>
<div
class="t-chat__thought-chain-item__header t-chat__thought-chain-item__header--collapsible"
>
<span
class="t-chat__thought-chain-item__title"
>
检索资料
</span>
<svg
class="t-icon t-icon-chevron-down t-chat__thought-chain-item__arrow"
fill="none"
height="1em"
style="fill: none;"
viewBox="0 0 24 24"
width="1em"
>
<g
id="chevron-down"
>
<path
d="M17.5 9.5L12 15L6.5 9.5"
id="stroke1"
stroke="currentColor"
stroke-linecap="square"
stroke-width="2"
/>
</g>
</svg>
</div>
<!---->
</div>
</div>
<div
class="t-chat__thought-chain-item t-chat__thought-chain-item--pending"
>
<div
class="t-chat__thought-chain-item__node"
>
<span
class="t-chat__thought-chain-item__icon"
>
<span
class="t-chat__thought-chain-dot"
/>
</span>
<!---->
</div>
<div
class="t-chat__thought-chain-item__detail"
>
<div
class="t-chat__thought-chain-item__header"
>
<span
class="t-chat__thought-chain-item__title"
>
输出回答
</span>
<!---->
</div>
<!---->
</div>
</div>

</div>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { mount } from '@vue/test-utils';
import { vi } from 'vitest';
import ChatThoughtChain from '@tdesign/pro-components-chat/chat-thought-chain/index';

const items = [
{ key: 'a', title: '分析问题', content: '分析中的详细内容', status: 'success' },
{ key: 'b', title: '检索资料', content: '检索的详细内容', status: 'processing' },
{ key: 'c', title: '输出回答', status: 'pending' },
];

describe('ChatThoughtChain', () => {
describe(':props', () => {
it(':items - array', () => {
const wrapper = mount(ChatThoughtChain, {
props: { items },
});
const nodes = wrapper.findAll('.t-chat__thought-chain-item');
expect(nodes.length).toBe(3);
expect(nodes[0].classes()).toContain('t-chat__thought-chain-item--success');
expect(nodes[1].classes()).toContain('t-chat__thought-chain-item--processing');
expect(nodes[2].classes()).toContain('t-chat__thought-chain-item--pending');
expect(wrapper.element).toMatchSnapshot();
});

it(':defaultExpandedValue - array', () => {
const wrapper = mount(ChatThoughtChain, {
props: { items, defaultExpandedValue: ['a'] },
});
const contents = wrapper.findAll('.t-chat__thought-chain-item__content');
expect(contents.length).toBe(1);
expect(contents[0].text()).toBe('分析中的详细内容');
});

it(':collapsible - false', async () => {
const wrapper = mount(ChatThoughtChain, {
props: { items, collapsible: false },
});
await wrapper.findAll('.t-chat__thought-chain-item__header')[0].trigger('click');
expect(wrapper.findAll('.t-chat__thought-chain-item__content').length).toBe(0);
expect(wrapper.findAll('.t-chat__thought-chain-item__arrow').length).toBe(0);
});

it(':icon - custom slot', () => {
const wrapper = mount(ChatThoughtChain, {
props: { items },
slots: {
icon: ({ item }) => <span class="custom-icon">{item.key}</span>,
},
});
expect(wrapper.findAll('.custom-icon').length).toBe(3);
});
});

describe('@event', () => {
it('onExpandChange', async () => {
const onExpandChange = vi.fn();
const wrapper = mount(ChatThoughtChain, {
props: { items, onExpandChange },
});
await wrapper.findAll('.t-chat__thought-chain-item__header')[0].trigger('click');
expect(onExpandChange).toHaveBeenCalledWith(['a'], expect.objectContaining({ index: 0, expanded: true }));
expect(wrapper.findAll('.t-chat__thought-chain-item__content').length).toBe(1);

await wrapper.findAll('.t-chat__thought-chain-item__header')[0].trigger('click');
expect(onExpandChange).toHaveBeenCalledWith([], expect.objectContaining({ index: 0, expanded: false }));
});

it('expandedValue - controlled', async () => {
const wrapper = mount(ChatThoughtChain, {
props: { items, expandedValue: ['b'] },
});
expect(wrapper.findAll('.t-chat__thought-chain-item__content').length).toBe(1);
// 受控模式下点击不直接改变展开状态
await wrapper.findAll('.t-chat__thought-chain-item__header')[0].trigger('click');
expect(wrapper.findAll('.t-chat__thought-chain-item__content').length).toBe(1);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<t-chat-thought-chain :items="items" :default-expanded-value="[1]">
<template #icon="{ item }">
<SearchIcon v-if="item.key === 'search'" />
</template>
<template #content="{ item }">
<div v-if="item.key === 'search'">
<t-tag v-for="doc in docs" :key="doc" theme="primary" variant="light" style="margin: 0 8px 4px 0">
{{ doc }}
</t-tag>
</div>
</template>
</t-chat-thought-chain>
</template>
<script setup>
import { SearchIcon } from 'tdesign-icons-vue-next';

const docs = ['产品白皮书.pdf', '性能测试报告.xlsx', 'API 设计文档.md'];

const items = [
{ title: '理解问题', content: '识别为产品技术选型类问题。', status: 'success' },
{ key: 'search', title: '检索知识库', status: 'success' },
{ title: '生成回答', status: 'error', content: '生成超时,已自动重试。' },
];
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<t-space direction="vertical" style="width: 100%">
<t-button :disabled="running" @click="start">{{ running ? '规划执行中...' : '模拟 Agent 执行' }}</t-button>
<t-chat-thought-chain v-model:expanded-value="expandedValue" :items="items" />
</t-space>
</template>
<script setup>
import { ref } from 'vue';

const PLAN = [
{ key: 'intent', title: '理解任务目标', content: '解析用户输入,确认任务为多步骤数据分析。' },
{ key: 'plan', title: '制定执行计划', content: '拆分为数据拉取、清洗、统计、生成报告 4 个子任务。' },
{ key: 'tool', title: '调用工具执行', content: '调用 SQL 工具拉取近 30 天数据并完成聚合统计。' },
{ key: 'report', title: '汇总生成报告', content: '整合各步骤结果,生成最终分析报告。' },
];

const items = ref([]);
const expandedValue = ref([]);
const running = ref(false);

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const start = async () => {
running.value = true;
items.value = [];
expandedValue.value = [];
for (const step of PLAN) {
items.value = [...items.value, { ...step, status: 'processing' }];
expandedValue.value = [...expandedValue.value, step.key];
await sleep(1200);
items.value = items.value.map((it) => (it.key === step.key ? { ...it, status: 'success' } : it));
}
running.value = false;
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<t-chat-thought-chain :items="items" :default-expanded-value="['search']" @expand-change="onExpandChange" />
</template>
<script setup>
const items = [
{
key: 'analyze',
title: '分析用户问题',
content: '拆解用户意图:需要对比 3 款产品在性能、价格、生态上的差异。',
status: 'success',
},
{
key: 'search',
title: '检索相关资料',
content: '已检索 12 篇文档,筛选出 4 篇高相关内容作为回答依据。',
status: 'success',
},
{
key: 'plan',
title: '生成回答计划',
content: '按"结论先行 + 维度对比表格 + 选型建议"的结构组织回答。',
status: 'processing',
},
{
key: 'answer',
title: '输出最终回答',
status: 'pending',
},
];

const onExpandChange = (value, context) => {
console.log('expand-change', value, context);
};
</script>
Loading
Loading