@quidgest/chatbot 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/components/ChatBot/types.d.ts +3 -1
- package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +3 -3
- package/dist/components/ChatBotInput/__tests__/ChatBotInput.spec.d.ts +1 -0
- package/dist/components/ChatBotInput/index.d.ts +2 -2
- package/dist/components/ChatBotInput/types.d.ts +4 -4
- package/dist/components/ChatBotMessage/__tests__/ChatBotMessage.spec.d.ts +1 -0
- package/dist/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.d.ts +1 -0
- package/dist/components/ChatBotMessage/types.d.ts +3 -2
- package/dist/components/ChatToolBar/__tests__/ChatToolBar.spec.d.ts +1 -0
- package/dist/components/FieldPreview/__tests__/FieldPreview.spec.d.ts +1 -0
- package/dist/components/MarkdownRender/__tests__/MarkdownRender.spec.d.ts +1 -0
- package/dist/components/PulseDots/__tests__/PulseDots.spec.d.ts +1 -0
- package/dist/composables/__tests__/useChatMessages.spec.d.ts +1 -0
- package/dist/composables/__tests__/useSSE.spec.d.ts +1 -0
- package/dist/composables/useChatMessages.d.ts +2 -1
- package/dist/composables/useSSE.d.ts +1 -2
- package/dist/composables/useTexts.d.ts +2 -0
- package/dist/index.js +16 -16
- package/dist/index.mjs +2924 -1770
- package/dist/style.css +1 -1
- package/dist/test/setup.d.ts +1 -0
- package/dist/utils/__tests__/parseFieldValue.spec.d.ts +1 -0
- package/package.json +27 -5
- package/src/assets/styles/preview-file.scss +70 -0
- package/src/assets/styles/styles.scss +190 -222
- package/src/components/ChatBot/ChatBot.vue +345 -368
- package/src/components/ChatBot/types.ts +35 -33
- package/src/components/ChatBotInput/ChatBotInput.vue +188 -190
- package/src/components/ChatBotInput/__tests__/ChatBotInput.spec.ts +279 -0
- package/src/components/ChatBotInput/__tests__/__snapshots__/ChatBotInput.spec.ts.snap +25 -0
- package/src/components/ChatBotInput/index.ts +2 -2
- package/src/components/ChatBotInput/types.ts +25 -25
- package/src/components/ChatBotMessage/ChatBotMessage.vue +159 -134
- package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +179 -164
- package/src/components/ChatBotMessage/__tests__/ChatBotMessage.spec.ts +256 -0
- package/src/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.ts +199 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessage.spec.ts.snap +35 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap +25 -0
- package/src/components/ChatBotMessage/types.ts +54 -53
- package/src/components/ChatToolBar/ChatToolBar.vue +68 -64
- package/src/components/ChatToolBar/__tests__/ChatToolBar.spec.ts +118 -0
- package/src/components/ChatToolBar/__tests__/__snapshots__/ChatToolBar.spec.ts.snap +11 -0
- package/src/components/ChatToolBar/types.ts +12 -12
- package/src/components/FieldPreview/FieldPreview.vue +56 -58
- package/src/components/FieldPreview/__tests__/FieldPreview.spec.ts +72 -0
- package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +25 -0
- package/src/components/FieldPreview/field-preview.scss +26 -26
- package/src/components/FieldPreview/types.ts +5 -5
- package/src/components/MarkdownRender/MarkdownRender.vue +15 -15
- package/src/components/MarkdownRender/__tests__/MarkdownRender.spec.ts +68 -0
- package/src/components/MarkdownRender/__tests__/__snapshots__/MarkdownRender.spec.ts.snap +8 -0
- package/src/components/MarkdownRender/markdown-render.scss +19 -20
- package/src/components/MarkdownRender/types.ts +3 -3
- package/src/components/PulseDots/PulseDots.vue +17 -17
- package/src/components/PulseDots/__tests__/PulseDots.spec.ts +35 -0
- package/src/components/PulseDots/__tests__/__snapshots__/PulseDots.spec.ts.snap +7 -0
- package/src/components/PulseDots/__tests__/__snapshots__/pulse-dots.spec.ts.snap +7 -0
- package/src/components/PulseDots/pulse-dots.scss +24 -23
- package/src/composables/__tests__/useChatMessages.spec.ts +51 -0
- package/src/composables/__tests__/useSSE.spec.ts +132 -0
- package/src/composables/useChatApi.ts +128 -134
- package/src/composables/useChatMessages.ts +46 -48
- package/src/composables/useSSE.ts +75 -76
- package/src/composables/useTexts.ts +30 -30
- package/src/test/setup.ts +36 -0
- package/src/utils/__tests__/parseFieldValue.spec.ts +27 -0
- package/src/utils/parseFieldValue.ts +12 -0
- package/src/utils/helper.ts +0 -12
- /package/dist/utils/{helper.d.ts → parseFieldValue.d.ts} +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { AvailableAgents } from '../ChatBot/types'
|
|
2
2
|
|
|
3
3
|
export type ChatToolBarProps = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/**
|
|
5
|
+
* If true, the toolbar will be disabled and not clickable.
|
|
6
|
+
*/
|
|
7
|
+
disabled?: boolean
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Availabe Agents to select from.
|
|
11
|
+
*/
|
|
12
|
+
availableAgents?: AvailableAgents[]
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The currently selected agent.
|
|
16
|
+
*/
|
|
17
|
+
selectedAgentKey?: string
|
|
18
18
|
}
|
|
@@ -1,78 +1,76 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
<div class="q-field-preview">
|
|
3
|
+
<div class="q-field-preview__toolbar">
|
|
4
|
+
<span>
|
|
5
|
+
{{ texts.suggestionsForField }} <b>{{ props.name }}</b>
|
|
6
|
+
</span>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="q-field-preview__content">
|
|
9
|
+
<component
|
|
10
|
+
:is="previewComponent"
|
|
11
|
+
v-bind="previewComponentProps" />
|
|
12
|
+
</div>
|
|
13
|
+
<div class="q-field-preview__footer">
|
|
14
|
+
<q-button-group borderless>
|
|
15
|
+
<!-- <q-button
|
|
16
16
|
:title="texts.regenerateResponse"
|
|
17
17
|
:disabled="props.disabled"
|
|
18
18
|
borderless
|
|
19
19
|
@click="console.log('Regenerate response')">
|
|
20
20
|
<q-icon icon="reset" />
|
|
21
21
|
</q-button> -->
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
<q-button
|
|
23
|
+
data-testid="apply-button"
|
|
24
|
+
:label="texts.apply"
|
|
25
|
+
:disabled="blockApplyButton"
|
|
26
|
+
:readonly="blockApplyButton"
|
|
27
|
+
@click="emitApply">
|
|
28
|
+
<q-icon icon="apply" />
|
|
29
|
+
</q-button>
|
|
30
|
+
</q-button-group>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
32
33
|
</template>
|
|
33
34
|
|
|
34
35
|
<script setup lang="ts">
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
import { QButton, QIcon, QButtonGroup } from '@quidgest/ui/components'
|
|
37
|
+
import { MarkdownRender } from '@/components/MarkdownRender'
|
|
38
|
+
import { FieldPreviewProps } from './types'
|
|
39
|
+
import { useTexts } from '@/composables/useTexts'
|
|
40
|
+
import { computed, ref } from 'vue'
|
|
41
|
+
import { parseFieldValue } from '@/utils/parseFieldValue'
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
const props = defineProps<FieldPreviewProps>()
|
|
44
|
+
const emit = defineEmits<{
|
|
45
|
+
(e: 'apply', text: unknown): void
|
|
46
|
+
}>()
|
|
47
|
+
const texts = useTexts()
|
|
48
|
+
const blockApply = ref(props.applied)
|
|
49
|
+
const blockApplyButton = computed(() => {
|
|
50
|
+
return props.disabled || blockApply.value
|
|
51
|
+
})
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return MarkdownRender
|
|
53
|
+
const previewComponent = computed(() => {
|
|
54
|
+
if (props.type === 'text' || props.type === 'multiline_text') return MarkdownRender
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
return 'div'
|
|
57
|
+
})
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
const previewComponentProps = computed(() => {
|
|
60
|
+
const componentProps: Record<string, unknown> = {}
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
else componentProps.innerHTML = props.text
|
|
62
|
+
if (previewComponent.value === MarkdownRender) componentProps.source = props.text
|
|
63
|
+
else componentProps.innerHTML = props.text
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
return componentProps
|
|
66
|
+
})
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
function emitApply() {
|
|
69
|
+
if (blockApply.value) return
|
|
70
|
+
blockApply.value = true
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
if (blockApply.value) return
|
|
72
|
-
blockApply.value = true
|
|
72
|
+
const text = parseFieldValue(props.type, props.text)
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
emits('apply', text)
|
|
77
|
-
}
|
|
74
|
+
emit('apply', text)
|
|
75
|
+
}
|
|
78
76
|
</script>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Componets
|
|
2
|
+
import { FieldPreview } from '..'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { mount } from '@vue/test-utils'
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
import type { FieldPreviewProps } from '..'
|
|
10
|
+
|
|
11
|
+
describe('FieldPreview', () => {
|
|
12
|
+
const props: FieldPreviewProps = {
|
|
13
|
+
name: 'Test Field',
|
|
14
|
+
text: 'Hello, World!',
|
|
15
|
+
type: 'text',
|
|
16
|
+
disabled: false,
|
|
17
|
+
applied: false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
it('renders correctly with default props', () => {
|
|
21
|
+
const wrapper = mount(FieldPreview, {
|
|
22
|
+
props: { ...props }
|
|
23
|
+
})
|
|
24
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('renders the correct componentd for text fields', async () => {
|
|
28
|
+
const wrapper = mount(FieldPreview, {
|
|
29
|
+
props: { ...props }
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
expect(wrapper.find('.markdown-renderer').exists()).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('renders the correct componentd for multiline_text fields', async () => {
|
|
36
|
+
const wrapper = mount(FieldPreview, {
|
|
37
|
+
props: { ...props, type: 'multiline_text' }
|
|
38
|
+
})
|
|
39
|
+
expect(wrapper.find('.markdown-renderer').exists()).toBe(true)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('renders the correct default component for unknown field types', async () => {
|
|
43
|
+
const wrapper = mount(FieldPreview, {
|
|
44
|
+
props: { ...props, type: 'boolean' }
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const textElement = wrapper.find('.q-field-preview__content div')
|
|
48
|
+
expect(textElement.exists()).toBe(true)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('emits the right event on apply buttons is clicked', async () => {
|
|
52
|
+
const wrapper = mount(FieldPreview, {
|
|
53
|
+
props: { ...props }
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const applyButton = wrapper.find('[data-testid="apply-button"]')
|
|
57
|
+
await applyButton.trigger('click')
|
|
58
|
+
|
|
59
|
+
expect(wrapper.emitted()['apply']).toBeTruthy()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('does not emit the apply event when the button is disabled', async () => {
|
|
63
|
+
const wrapper = mount(FieldPreview, {
|
|
64
|
+
props: { ...props, applied: true }
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const applyButton = wrapper.find('[data-testid="apply-button"]')
|
|
68
|
+
await applyButton.trigger('click')
|
|
69
|
+
|
|
70
|
+
expect(wrapper.emitted()['apply']).toBeFalsy()
|
|
71
|
+
})
|
|
72
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`FieldPreview > renders correctly with default props 1`] = `
|
|
4
|
+
"<div class="q-field-preview">
|
|
5
|
+
<div class="q-field-preview__toolbar"><span>Suggestions for field: <b>Test Field</b></span></div>
|
|
6
|
+
<div class="q-field-preview__content">
|
|
7
|
+
<div class="markdown-renderer">
|
|
8
|
+
<p>Hello, World!</p>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="q-field-preview__footer">
|
|
12
|
+
<div class="q-button-group" role="group">
|
|
13
|
+
<!-- <q-button
|
|
14
|
+
:title="texts.regenerateResponse"
|
|
15
|
+
:disabled="props.disabled"
|
|
16
|
+
borderless
|
|
17
|
+
@click="console.log('Regenerate response')">
|
|
18
|
+
<q-icon icon="reset" />
|
|
19
|
+
</q-button> --><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless" data-testid="apply-button">
|
|
20
|
+
<!--v-if--><span class="q-button__content"><span data-test="apply"></span> Apply</span>
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>"
|
|
25
|
+
`;
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
.q-field-preview {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
position: relative;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
margin: 1rem 0.25rem;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
&__toolbar {
|
|
8
|
+
z-index: 1;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: row;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: space-between;
|
|
13
|
+
padding: 0.1rem 0.2rem;
|
|
14
|
+
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
&__content {
|
|
17
|
+
position: relative;
|
|
18
|
+
background-color: #f5f5f5;
|
|
19
|
+
border: 1px solid #e0e0e0;
|
|
20
|
+
border-radius: 6px;
|
|
21
|
+
padding: 0.75rem 1rem;
|
|
22
|
+
font-size: 0.875rem;
|
|
23
|
+
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
&__footer {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: row;
|
|
28
|
+
margin-top: 0.25rem;
|
|
29
|
+
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
.q-field-preview:first-child {
|
|
33
|
-
|
|
33
|
+
margin: 0 1rem 0.25rem 0.25rem;
|
|
34
34
|
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
<div
|
|
3
|
+
class="markdown-renderer"
|
|
4
|
+
v-html="renderedContent"></div>
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { ref, computed } from 'vue'
|
|
9
|
+
import type { MarkdownRenderProps } from './types'
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import MarkdownIt from 'markdown-it'
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
const props = defineProps<MarkdownRenderProps>()
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
const md = ref(
|
|
16
|
+
new MarkdownIt({
|
|
17
|
+
html: true,
|
|
18
|
+
...(props.options ?? {})
|
|
19
|
+
})
|
|
20
|
+
)
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
if (props.plugins) props.plugins.forEach((plugin) => md.value.use(plugin))
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
const renderedContent = computed(() => md.value.render(props.source))
|
|
25
25
|
</script>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
import { MarkdownRender } from '..'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { mount } from '@vue/test-utils'
|
|
6
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
import type MarkdownIt from 'markdown-it'
|
|
10
|
+
|
|
11
|
+
// Mock a simple markdown-it plugin
|
|
12
|
+
const mockPlugin = vi.fn((md: MarkdownIt) => {
|
|
13
|
+
md.core.ruler.push('test:rule', (state) => {
|
|
14
|
+
state.tokens.push({
|
|
15
|
+
type: 'test_token',
|
|
16
|
+
content: 'test',
|
|
17
|
+
tag: '',
|
|
18
|
+
attrs: null,
|
|
19
|
+
map: null,
|
|
20
|
+
nesting: 0,
|
|
21
|
+
level: 0,
|
|
22
|
+
children: null,
|
|
23
|
+
markup: '',
|
|
24
|
+
info: '',
|
|
25
|
+
meta: undefined,
|
|
26
|
+
block: false,
|
|
27
|
+
hidden: false,
|
|
28
|
+
attrIndex: function () {
|
|
29
|
+
throw new Error()
|
|
30
|
+
},
|
|
31
|
+
attrPush: function () {
|
|
32
|
+
throw new Error()
|
|
33
|
+
},
|
|
34
|
+
attrSet: function () {
|
|
35
|
+
throw new Error()
|
|
36
|
+
},
|
|
37
|
+
attrGet: function (): string | null {
|
|
38
|
+
throw new Error()
|
|
39
|
+
},
|
|
40
|
+
attrJoin: function () {
|
|
41
|
+
throw new Error()
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('MarkdownRender', () => {
|
|
48
|
+
it('renders correctly with default props', () => {
|
|
49
|
+
const wrapper = mount(MarkdownRender, {
|
|
50
|
+
props: {
|
|
51
|
+
source: '# Hello World\nThis is a **markdown** test.'
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('renders correctly with custom markdown-it plugins', () => {
|
|
58
|
+
const wrapper = mount(MarkdownRender, {
|
|
59
|
+
props: {
|
|
60
|
+
source: '',
|
|
61
|
+
plugins: [mockPlugin]
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(mockPlugin).toHaveBeenCalled()
|
|
66
|
+
expect(wrapper.props('plugins')).toHaveLength(1)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
.markdown-renderer {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
2
|
+
pre,
|
|
3
|
+
code {
|
|
4
|
+
white-space: pre-wrap;
|
|
5
|
+
overflow-wrap: anywhere;
|
|
6
|
+
overflow-x: auto;
|
|
7
|
+
}
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
pre {
|
|
10
|
+
position: relative;
|
|
11
|
+
background-color: #f5f5f5;
|
|
12
|
+
border: 1px solid #e0e0e0;
|
|
13
|
+
border-radius: 6px;
|
|
14
|
+
padding: 0.75rem 1rem;
|
|
15
|
+
font-size: 0.875rem;
|
|
16
|
+
}
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
code {
|
|
19
|
+
padding: 0.2rem 0.4rem;
|
|
20
|
+
border-radius: 4px;
|
|
21
|
+
font-size: 0.875rem;
|
|
22
|
+
}
|
|
24
23
|
}
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
<div class="pulsing-dots">
|
|
3
|
+
<span class="generating-text">
|
|
4
|
+
{{ texts.generatingResponse }}
|
|
5
|
+
</span>
|
|
6
|
+
<div class="dots-container">
|
|
7
|
+
<span
|
|
8
|
+
v-for="(_, index) in dots"
|
|
9
|
+
:key="index"
|
|
10
|
+
class="dot"
|
|
11
|
+
:style="{ animationDelay: index * 0.2 + 's' }">
|
|
12
|
+
•
|
|
13
|
+
</span>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
16
|
</template>
|
|
17
17
|
|
|
18
18
|
<script setup lang="ts">
|
|
19
|
-
|
|
19
|
+
import { useTexts } from '@/composables/useTexts'
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
const dots = [1, 2, 3]
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
const texts = useTexts()
|
|
24
24
|
</script>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
import PulseDots from '../PulseDots.vue'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { mount } from '@vue/test-utils'
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import { useTexts } from '@/composables/useTexts'
|
|
8
|
+
|
|
9
|
+
describe('PulseDots', () => {
|
|
10
|
+
it('renders correctly', () => {
|
|
11
|
+
const wrapper = mount(PulseDots)
|
|
12
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('renders the correct number of dots', () => {
|
|
16
|
+
const wrapper = mount(PulseDots)
|
|
17
|
+
const dots = wrapper.findAll('.dot')
|
|
18
|
+
expect(dots.length).toBe(3)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('uses the correct generating text', () => {
|
|
22
|
+
const { generatingResponse } = useTexts()
|
|
23
|
+
const wrapper = mount(PulseDots)
|
|
24
|
+
expect(wrapper.text()).toContain(generatingResponse)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('applies correct animation delays to dots', () => {
|
|
28
|
+
const wrapper = mount(PulseDots)
|
|
29
|
+
const dots = wrapper.findAll('.dot')
|
|
30
|
+
|
|
31
|
+
dots.forEach((dot, index) => {
|
|
32
|
+
expect(dot.attributes('style')).toContain(`animation-delay: ${index * 0.2}s`)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`PulseDots > renders correctly 1`] = `
|
|
4
|
+
"<div class="pulsing-dots"><span class="generating-text">Generating</span>
|
|
5
|
+
<div class="dots-container"><span class="dot" style="animation-delay: 0s;"> • </span><span class="dot" style="animation-delay: 0.2s;"> • </span><span class="dot" style="animation-delay: 0.4s;"> • </span></div>
|
|
6
|
+
</div>"
|
|
7
|
+
`;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`PulseDots > renders correctly 1`] = `
|
|
4
|
+
"<div class="pulsing-dots"><span class="generating-text">Generating</span>
|
|
5
|
+
<div class="dots-container"><span class="dot" style="animation-delay: 0s;"> • </span><span class="dot" style="animation-delay: 0.2s;"> • </span><span class="dot" style="animation-delay: 0.4s;"> • </span></div>
|
|
6
|
+
</div>"
|
|
7
|
+
`;
|