@gitlab/ui 78.1.0 → 78.1.2
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/CHANGELOG.md +14 -0
- package/dist/components/base/table/table.js +2 -4
- package/dist/components/experimental/duo/chat/duo_chat.js +28 -9
- package/dist/components/experimental/duo/chat/mock_data.js +41 -14
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/tokens/css/tokens.css +1 -1
- package/dist/tokens/css/tokens.dark.css +1 -1
- package/dist/tokens/js/tokens.dark.js +1 -1
- package/dist/tokens/js/tokens.js +1 -1
- package/dist/tokens/scss/_tokens.dark.scss +1 -1
- package/dist/tokens/scss/_tokens.scss +1 -1
- package/package.json +2 -2
- package/src/components/base/popover/popover.stories.js +18 -3
- package/src/components/base/table/table.scss +24 -0
- package/src/components/base/table/table.spec.js +7 -11
- package/src/components/base/table/table.stories.js +15 -4
- package/src/components/base/table/table.vue +19 -11
- package/src/components/experimental/duo/chat/duo_chat.spec.js +35 -7
- package/src/components/experimental/duo/chat/duo_chat.stories.js +39 -20
- package/src/components/experimental/duo/chat/duo_chat.vue +26 -11
- package/src/components/experimental/duo/chat/mock_data.js +28 -12
package/dist/tokens/js/tokens.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "78.1.
|
|
3
|
+
"version": "78.1.2",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"@gitlab/eslint-plugin": "19.4.0",
|
|
103
103
|
"@gitlab/fonts": "^1.3.0",
|
|
104
104
|
"@gitlab/stylelint-config": "6.1.0",
|
|
105
|
-
"@gitlab/svgs": "3.
|
|
105
|
+
"@gitlab/svgs": "3.89.0",
|
|
106
106
|
"@rollup/plugin-commonjs": "^11.1.0",
|
|
107
107
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
|
108
108
|
"@rollup/plugin-replace": "^2.3.2",
|
|
@@ -7,9 +7,7 @@ const defaultValue = (prop) => GlPopover.props[prop].default;
|
|
|
7
7
|
const components = { GlPopover, GlButton };
|
|
8
8
|
|
|
9
9
|
const contentString = `
|
|
10
|
-
|
|
11
|
-
pellentesque. Pellentesque efficitur vulputate rutrum. Fusce nisl magna, porttitor in
|
|
12
|
-
massa ac, porta condimentum libero. Ut id lacus tristique, egestas arcu non, molestie nisi.
|
|
10
|
+
A popover is used to provide supplemental, useful, unique information about an element.
|
|
13
11
|
`;
|
|
14
12
|
|
|
15
13
|
const getTemplate = (id, slots = '') => `
|
|
@@ -58,6 +56,23 @@ WithCloseButton.args = generateProps({
|
|
|
58
56
|
showCloseButton: true,
|
|
59
57
|
});
|
|
60
58
|
|
|
59
|
+
export const TextLinks = (_args, { viewMode, argTypes }) => ({
|
|
60
|
+
viewMode,
|
|
61
|
+
components,
|
|
62
|
+
props: Object.keys(argTypes),
|
|
63
|
+
template: getTemplate(
|
|
64
|
+
'popover-with-text-links',
|
|
65
|
+
`
|
|
66
|
+
<template>
|
|
67
|
+
A popover is used to provide supplemental, useful, unique information about an element. This one has a link to <a class="gl-link" href="https://design.gitlab.com/components/popover">learn more about popovers.</a>
|
|
68
|
+
</template>
|
|
69
|
+
`
|
|
70
|
+
),
|
|
71
|
+
});
|
|
72
|
+
TextLinks.args = generateProps({
|
|
73
|
+
showCloseButton: true,
|
|
74
|
+
});
|
|
75
|
+
|
|
61
76
|
export const OnClick = (_args, { viewMode, argTypes }) => ({
|
|
62
77
|
viewMode,
|
|
63
78
|
components,
|
|
@@ -17,6 +17,25 @@ table.gl-table {
|
|
|
17
17
|
th {
|
|
18
18
|
@include gl-font-weight-bold;
|
|
19
19
|
@include gl-text-gray-900;
|
|
20
|
+
|
|
21
|
+
&.gl-text-right > span {
|
|
22
|
+
@include gl-display-flex;
|
|
23
|
+
@include gl-justify-content-end;
|
|
24
|
+
|
|
25
|
+
span:first-of-type {
|
|
26
|
+
order: 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
span:nth-of-type(2) {
|
|
30
|
+
order: 0;
|
|
31
|
+
@include gl-ml-0;
|
|
32
|
+
@include gl-mr-3;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
[name='sort-icon'] {
|
|
37
|
+
user-select: none;
|
|
38
|
+
}
|
|
20
39
|
}
|
|
21
40
|
|
|
22
41
|
td {
|
|
@@ -121,3 +140,8 @@ table.gl-table {
|
|
|
121
140
|
.table.b-table > tfoot > tr > th {
|
|
122
141
|
background-image: none !important;
|
|
123
142
|
}
|
|
143
|
+
|
|
144
|
+
.table.b-table > thead > tr > [aria-sort]:not(.b-table-sort-icon-left),
|
|
145
|
+
.table.b-table > tfoot > tr > [aria-sort]:not(.b-table-sort-icon-left) {
|
|
146
|
+
padding-right: 1rem;
|
|
147
|
+
}
|
|
@@ -3,7 +3,6 @@ import { shallowMount, mount } from '@vue/test-utils';
|
|
|
3
3
|
import { BTable } from 'bootstrap-vue';
|
|
4
4
|
import { logWarning } from '../../../utils/utils';
|
|
5
5
|
import { waitForAnimationFrame } from '../../../utils/test_utils';
|
|
6
|
-
import Icon from '../icon/icon.vue';
|
|
7
6
|
import { glTableLiteWarning } from './constants';
|
|
8
7
|
import Table from './table.vue';
|
|
9
8
|
|
|
@@ -100,20 +99,18 @@ describe('GlTable', () => {
|
|
|
100
99
|
findFirstColHeader().trigger('click');
|
|
101
100
|
await nextTick();
|
|
102
101
|
|
|
103
|
-
const
|
|
102
|
+
const headerText = findFirstColHeader().text();
|
|
104
103
|
|
|
105
|
-
expect(
|
|
106
|
-
expect(icon.props('name')).toBe('arrow-up');
|
|
104
|
+
expect(headerText).toContain('↑');
|
|
107
105
|
});
|
|
108
106
|
|
|
109
107
|
it('renders the descending sort icon', async () => {
|
|
110
108
|
findFirstColHeader().trigger('click');
|
|
111
109
|
findFirstColHeader().trigger('click');
|
|
112
110
|
await nextTick();
|
|
113
|
-
const
|
|
111
|
+
const headerText = findFirstColHeader().text();
|
|
114
112
|
|
|
115
|
-
expect(
|
|
116
|
-
expect(icon.props('name')).toBe('arrow-down');
|
|
113
|
+
expect(headerText).toContain('↓');
|
|
117
114
|
});
|
|
118
115
|
});
|
|
119
116
|
|
|
@@ -136,11 +133,10 @@ describe('GlTable', () => {
|
|
|
136
133
|
findFirstColHeader().trigger('click');
|
|
137
134
|
await nextTick();
|
|
138
135
|
|
|
139
|
-
const
|
|
136
|
+
const headerText = findFirstColHeader().text();
|
|
140
137
|
|
|
141
|
-
expect(
|
|
142
|
-
expect(
|
|
143
|
-
expect(findFirstColHeader().text()).toContain(customSlotContent);
|
|
138
|
+
expect(headerText).toContain('↑');
|
|
139
|
+
expect(headerText).toContain(customSlotContent);
|
|
144
140
|
});
|
|
145
141
|
});
|
|
146
142
|
});
|
|
@@ -7,15 +7,18 @@ const components = { GlTable };
|
|
|
7
7
|
const tableItems = [
|
|
8
8
|
{
|
|
9
9
|
column_one: 'test',
|
|
10
|
-
col_2:
|
|
10
|
+
col_2: 'ABC',
|
|
11
|
+
col_three: 1234,
|
|
11
12
|
},
|
|
12
13
|
{
|
|
13
14
|
column_one: 'test2',
|
|
14
|
-
col_2:
|
|
15
|
+
col_2: 'DEF',
|
|
16
|
+
col_three: 5678,
|
|
15
17
|
},
|
|
16
18
|
{
|
|
17
19
|
column_one: 'test3',
|
|
18
|
-
col_2:
|
|
20
|
+
col_2: 'GHI',
|
|
21
|
+
col_three: 9101,
|
|
19
22
|
},
|
|
20
23
|
];
|
|
21
24
|
|
|
@@ -44,8 +47,9 @@ export const Default = (args, { argTypes }) => ({
|
|
|
44
47
|
:fixed="fixed"
|
|
45
48
|
:stacked="stacked"
|
|
46
49
|
:foot-clone="footClone"
|
|
47
|
-
sort-by="
|
|
50
|
+
sort-by="col_three"
|
|
48
51
|
sort-desc
|
|
52
|
+
no-sort-reset
|
|
49
53
|
hover
|
|
50
54
|
selectable
|
|
51
55
|
selected-variant="primary"
|
|
@@ -65,9 +69,16 @@ export const Default = (args, { argTypes }) => ({
|
|
|
65
69
|
},
|
|
66
70
|
{
|
|
67
71
|
key: 'col_2',
|
|
72
|
+
label: 'Column 2',
|
|
73
|
+
formatter: (value) => value,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: 'col_three',
|
|
68
77
|
sortable: true,
|
|
69
78
|
label: 'Column 2',
|
|
70
79
|
formatter: (value) => value,
|
|
80
|
+
thClass: 'gl-text-right',
|
|
81
|
+
tdClass: 'gl-text-right',
|
|
71
82
|
},
|
|
72
83
|
],
|
|
73
84
|
items: tableItems,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<!-- eslint-disable vue/multi-word-component-names -->
|
|
2
2
|
<script>
|
|
3
3
|
import { BTable } from 'bootstrap-vue';
|
|
4
|
-
import GlIcon from '../icon/icon.vue';
|
|
5
4
|
import { logWarning, isDev } from '../../../utils/utils';
|
|
6
5
|
import { tableFullSlots, tableFullProps, glTableLiteWarning } from './constants';
|
|
7
6
|
|
|
@@ -18,7 +17,6 @@ export default {
|
|
|
18
17
|
name: 'GlTable',
|
|
19
18
|
components: {
|
|
20
19
|
BTable,
|
|
21
|
-
GlIcon,
|
|
22
20
|
},
|
|
23
21
|
inheritAttrs: false,
|
|
24
22
|
props: {
|
|
@@ -98,17 +96,27 @@ export default {
|
|
|
98
96
|
<slot :name="slotName" v-bind="scope"></slot>
|
|
99
97
|
</template>
|
|
100
98
|
<template v-for="headSlotName in headSlots" #[headSlotName]="scope">
|
|
101
|
-
<
|
|
102
|
-
<slot :name="headSlotName" v-bind="scope"
|
|
99
|
+
<span :key="headSlotName">
|
|
100
|
+
<slot :name="headSlotName" v-bind="scope"
|
|
101
|
+
><span>{{ scope.label }}</span></slot
|
|
103
102
|
><template v-if="isSortable(scope)">
|
|
104
|
-
<
|
|
105
|
-
v-if="getSortingIcon(scope)"
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
<span
|
|
104
|
+
v-if="getSortingIcon(scope) && getSortingIcon(scope) === 'arrow-up'"
|
|
105
|
+
class="gl-ml-3 gl-min-w-5 gl-text-gray-900 gl-text-center"
|
|
106
|
+
name="sort-icon"
|
|
107
|
+
>
|
|
108
|
+
↑
|
|
109
|
+
</span>
|
|
110
|
+
<span
|
|
111
|
+
v-else-if="getSortingIcon(scope) && getSortingIcon(scope) === 'arrow-down'"
|
|
112
|
+
class="gl-ml-3 gl-min-w-5 gl-text-gray-900 gl-text-center"
|
|
113
|
+
name="sort-icon"
|
|
114
|
+
>
|
|
115
|
+
↓
|
|
116
|
+
</span>
|
|
117
|
+
<span v-else class="gl-display-inline-block gl-w-5 gl-h-3 gl-ml-3"></span>
|
|
110
118
|
</template>
|
|
111
|
-
</
|
|
119
|
+
</span>
|
|
112
120
|
</template>
|
|
113
121
|
</b-table>
|
|
114
122
|
</template>
|
|
@@ -8,6 +8,7 @@ import DuoChatLoader from './components/duo_chat_loader/duo_chat_loader.vue';
|
|
|
8
8
|
import DuoChatPredefinedPrompts from './components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue';
|
|
9
9
|
import DuoChatConversation from './components/duo_chat_conversation/duo_chat_conversation.vue';
|
|
10
10
|
import GlDuoChat from './duo_chat.vue';
|
|
11
|
+
import { MOCK_RESPONSE_MESSAGE, MOCK_USER_PROMPT_MESSAGE } from './mock_data';
|
|
11
12
|
|
|
12
13
|
import { MESSAGE_MODEL_ROLES, CHAT_RESET_MESSAGE } from './constants';
|
|
13
14
|
|
|
@@ -339,11 +340,38 @@ describe('GlDuoChat', () => {
|
|
|
339
340
|
data: { prompt: promptStr },
|
|
340
341
|
});
|
|
341
342
|
trigger();
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
343
|
+
expect(wrapper.emitted('send-chat-prompt')).toEqual(expectEmitted);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it.each`
|
|
347
|
+
desc | msgs
|
|
348
|
+
${''} | ${[]}
|
|
349
|
+
${'with just a user message'} | ${[MOCK_USER_PROMPT_MESSAGE]}
|
|
350
|
+
${'with a user message, and a complete response'} | ${[MOCK_USER_PROMPT_MESSAGE, MOCK_RESPONSE_MESSAGE]}
|
|
351
|
+
`('prevents submission when loading $desc', ({ msgs } = {}) => {
|
|
352
|
+
createComponent({
|
|
353
|
+
propsData: { isChatAvailable: true, isLoading: true, messages: msgs },
|
|
354
|
+
data: { prompt: promptStr },
|
|
355
|
+
});
|
|
356
|
+
clickSubmit();
|
|
357
|
+
expect(wrapper.emitted('send-chat-prompt')).toBe(undefined);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it.each([
|
|
361
|
+
[[{ ...MOCK_RESPONSE_MESSAGE, content: undefined, chunks: [''] }]],
|
|
362
|
+
[
|
|
363
|
+
[
|
|
364
|
+
MOCK_USER_PROMPT_MESSAGE,
|
|
365
|
+
{ ...MOCK_RESPONSE_MESSAGE, content: undefined, chunks: [''] },
|
|
366
|
+
],
|
|
367
|
+
],
|
|
368
|
+
])('prevents submission when streaming (messages = "%o")', (msgs = []) => {
|
|
369
|
+
createComponent({
|
|
370
|
+
propsData: { isChatAvailable: true, messages: msgs },
|
|
371
|
+
data: { prompt: promptStr },
|
|
372
|
+
});
|
|
373
|
+
clickSubmit();
|
|
374
|
+
expect(wrapper.emitted('send-chat-prompt')).toBe(undefined);
|
|
347
375
|
});
|
|
348
376
|
});
|
|
349
377
|
|
|
@@ -428,7 +456,7 @@ describe('GlDuoChat', () => {
|
|
|
428
456
|
expect(findChatComponent().exists()).toBe(true);
|
|
429
457
|
});
|
|
430
458
|
|
|
431
|
-
it('resets the prompt when
|
|
459
|
+
it('resets the prompt when a message is loaded', async () => {
|
|
432
460
|
const prompt = 'foo';
|
|
433
461
|
createComponent({ data: { prompt } });
|
|
434
462
|
expect(findChatInput().props('value')).toBe(prompt);
|
|
@@ -436,7 +464,7 @@ describe('GlDuoChat', () => {
|
|
|
436
464
|
// reactive behavior which consistutes an exception
|
|
437
465
|
// See https://docs.gitlab.com/ee/development/fe_guide/style/vue.html#setting-component-state
|
|
438
466
|
wrapper.setProps({
|
|
439
|
-
|
|
467
|
+
isLoading: true,
|
|
440
468
|
});
|
|
441
469
|
await nextTick();
|
|
442
470
|
expect(findChatInput().props('value')).toBe('');
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import GlButton from '../../../base/button/button.vue';
|
|
2
2
|
import GlAlert from '../../../base/alert/alert.vue';
|
|
3
|
-
import { setStoryTimeout } from '../../../../utils/test_utils';
|
|
4
3
|
import { makeContainer } from '../../../../utils/story_decorators/container';
|
|
5
4
|
import GlDuoChat from './duo_chat.vue';
|
|
6
5
|
import readme from './duo_chat.md';
|
|
@@ -132,32 +131,52 @@ export const Interactive = (args, { argTypes }) => ({
|
|
|
132
131
|
this.isHidden = false;
|
|
133
132
|
this.loggerInfo += `Chat opened\n\n`;
|
|
134
133
|
},
|
|
135
|
-
onResponseRequested() {
|
|
134
|
+
async onResponseRequested() {
|
|
136
135
|
this.timeout = null;
|
|
137
|
-
this.
|
|
138
|
-
this.mockResponseFromAi();
|
|
136
|
+
await this.mockResponseFromAi();
|
|
139
137
|
this.requestId += 1;
|
|
140
138
|
},
|
|
141
|
-
mockResponseFromAi() {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
async mockResponseFromAi() {
|
|
140
|
+
const generator = generateMockResponseChunks(this.requestId);
|
|
141
|
+
|
|
142
|
+
for await (const result of generator) {
|
|
143
|
+
const { chunkId, content, ...messageAttributes } = result;
|
|
145
144
|
const existingMessageIndex = this.msgs.findIndex(
|
|
146
|
-
(msg) => msg.requestId ===
|
|
145
|
+
(msg) => msg.requestId === result.requestId && msg.role === result.role
|
|
147
146
|
);
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
this.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
147
|
+
|
|
148
|
+
if (existingMessageIndex === -1) {
|
|
149
|
+
this.addNewMessage(messageAttributes, content);
|
|
150
|
+
} else {
|
|
151
|
+
this.updateExistingMessage(existingMessageIndex, content, chunkId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
addNewMessage(messageAttributes, content) {
|
|
156
|
+
this.promptInFlight = false;
|
|
157
|
+
this.$set(this.msgs, this.msgs.length, {
|
|
158
|
+
...messageAttributes,
|
|
159
|
+
chunks: [content],
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
updateExistingMessage(index, content, chunkId) {
|
|
163
|
+
const message = this.msgs[index];
|
|
164
|
+
|
|
165
|
+
if (chunkId != null) {
|
|
166
|
+
// Ensure the chunks array exists
|
|
167
|
+
if (!message.chunks) {
|
|
168
|
+
this.$set(message, 'chunks', []);
|
|
154
169
|
} else {
|
|
155
|
-
this.
|
|
170
|
+
this.$set(message.chunks, chunkId, content);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
// Update for final message
|
|
174
|
+
this.$set(message, 'content', content);
|
|
175
|
+
|
|
176
|
+
// Remove chunks if they are not needed anymore
|
|
177
|
+
if (message.chunks) {
|
|
178
|
+
this.$delete(message, 'chunks');
|
|
156
179
|
}
|
|
157
|
-
this.logerInfo += `New response: ${JSON.stringify(newResponse)}\n\n`;
|
|
158
|
-
this.timeout = setStoryTimeout(() => {
|
|
159
|
-
this.mockResponseFromAi();
|
|
160
|
-
}, Math.floor(Math.random() * 251) + 16);
|
|
161
180
|
}
|
|
162
181
|
},
|
|
163
182
|
},
|
|
@@ -212,13 +212,20 @@ export default {
|
|
|
212
212
|
[[]]
|
|
213
213
|
);
|
|
214
214
|
},
|
|
215
|
+
lastMessage() {
|
|
216
|
+
return this.messages[this.messages.length - 1];
|
|
217
|
+
},
|
|
215
218
|
resetDisabled() {
|
|
216
219
|
if (this.isLoading || !this.hasMessages) {
|
|
217
220
|
return true;
|
|
218
221
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
return this.lastMessage?.content === CHAT_RESET_MESSAGE;
|
|
223
|
+
},
|
|
224
|
+
submitDisabled() {
|
|
225
|
+
return this.isLoading || this.isStreaming;
|
|
226
|
+
},
|
|
227
|
+
isStreaming() {
|
|
228
|
+
return Boolean(this.lastMessage?.chunks?.length > 0 && !this.lastMessage?.content);
|
|
222
229
|
},
|
|
223
230
|
filteredSlashCommands() {
|
|
224
231
|
const caseInsensitivePrompt = this.prompt.toLowerCase();
|
|
@@ -246,12 +253,13 @@ export default {
|
|
|
246
253
|
},
|
|
247
254
|
},
|
|
248
255
|
watch: {
|
|
249
|
-
isLoading() {
|
|
256
|
+
isLoading(newVal) {
|
|
250
257
|
this.isHidden = false;
|
|
251
258
|
this.scrollToBottom();
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
259
|
+
if (newVal) {
|
|
260
|
+
// We reset the prompt when we start getting the response and focus in the prompt field
|
|
261
|
+
this.setPromptAndFocus();
|
|
262
|
+
}
|
|
255
263
|
},
|
|
256
264
|
},
|
|
257
265
|
created() {
|
|
@@ -269,6 +277,9 @@ export default {
|
|
|
269
277
|
this.$emit('chat-hidden');
|
|
270
278
|
},
|
|
271
279
|
sendChatPrompt() {
|
|
280
|
+
if (this.submitDisabled) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
272
283
|
if (this.prompt) {
|
|
273
284
|
if (this.prompt === CHAT_RESET_MESSAGE && this.resetDisabled) {
|
|
274
285
|
return;
|
|
@@ -336,14 +347,18 @@ export default {
|
|
|
336
347
|
this.activeCommandIndex = 0;
|
|
337
348
|
}
|
|
338
349
|
},
|
|
350
|
+
async setPromptAndFocus(prompt = '') {
|
|
351
|
+
this.prompt = prompt;
|
|
352
|
+
await this.$nextTick();
|
|
353
|
+
this.$refs.prompt.$el.focus();
|
|
354
|
+
},
|
|
339
355
|
selectSlashCommand(index) {
|
|
340
356
|
const command = this.filteredSlashCommands[index];
|
|
341
357
|
if (command.shouldSubmit) {
|
|
342
358
|
this.prompt = command.name;
|
|
343
359
|
this.sendChatPrompt();
|
|
344
360
|
} else {
|
|
345
|
-
this.
|
|
346
|
-
this.$refs.prompt.$el.focus();
|
|
361
|
+
this.setPromptAndFocus(`${command.name} `);
|
|
347
362
|
}
|
|
348
363
|
},
|
|
349
364
|
},
|
|
@@ -494,7 +509,6 @@ export default {
|
|
|
494
509
|
class="gl-absolute gl-h-full! gl-py-4! gl-bg-transparent! gl-rounded-top-right-none gl-rounded-bottom-right-none gl-shadow-none!"
|
|
495
510
|
:class="{ 'gl-text-truncate': !prompt }"
|
|
496
511
|
:placeholder="inputPlaceholder"
|
|
497
|
-
:disabled="isLoading"
|
|
498
512
|
autofocus
|
|
499
513
|
@keydown.enter.exact.native.prevent
|
|
500
514
|
@keyup.native="onInputKeyup"
|
|
@@ -507,8 +521,9 @@ export default {
|
|
|
507
521
|
variant="confirm"
|
|
508
522
|
class="!gl-absolute gl-bottom-2 gl-right-2 gl-rounded-base!"
|
|
509
523
|
type="submit"
|
|
524
|
+
data-testid="chat-prompt-submit-button"
|
|
510
525
|
:aria-label="$options.i18n.CHAT_SUBMIT_LABEL"
|
|
511
|
-
:disabled="
|
|
526
|
+
:disabled="submitDisabled"
|
|
512
527
|
/>
|
|
513
528
|
</template>
|
|
514
529
|
</gl-form-input-group>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setStoryTimeout } from '../../../../utils/test_utils';
|
|
1
2
|
import { DOCUMENTATION_SOURCE_TYPES, MESSAGE_MODEL_ROLES } from './constants';
|
|
2
3
|
|
|
3
4
|
const MOCK_SOURCES = [
|
|
@@ -66,24 +67,39 @@ export const MOCK_RESPONSE_MESSAGE_FOR_STREAMING = {
|
|
|
66
67
|
timestamp: '2021-04-21T12:00:00.000Z',
|
|
67
68
|
};
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
// Utility function for delay
|
|
71
|
+
async function delayRandom(min = 16, max = 267) {
|
|
72
|
+
const delay = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
73
|
+
// eslint-disable-next-line no-promise-executor-return
|
|
74
|
+
return new Promise((resolve) => setStoryTimeout(resolve, delay));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function* generateMockResponseChunks(requestId = 1) {
|
|
71
78
|
const chunkSize = 5;
|
|
72
|
-
const
|
|
73
|
-
|
|
79
|
+
const contentLength = MOCK_RESPONSE_MESSAGE_FOR_STREAMING.content.length;
|
|
80
|
+
const chunkCount = Math.ceil(contentLength / chunkSize);
|
|
81
|
+
|
|
82
|
+
for (let chunkId = 0; chunkId < chunkCount; chunkId += 1) {
|
|
83
|
+
const start = chunkId * chunkSize;
|
|
84
|
+
const end = Math.min((chunkId + 1) * chunkSize, contentLength);
|
|
74
85
|
const chunk = {
|
|
75
86
|
...MOCK_RESPONSE_MESSAGE_FOR_STREAMING,
|
|
76
87
|
requestId,
|
|
77
|
-
content: MOCK_RESPONSE_MESSAGE_FOR_STREAMING.content.substring(
|
|
78
|
-
|
|
79
|
-
(i + 1) * chunkSize
|
|
80
|
-
),
|
|
81
|
-
chunkId: i,
|
|
88
|
+
content: MOCK_RESPONSE_MESSAGE_FOR_STREAMING.content.substring(start, end),
|
|
89
|
+
chunkId,
|
|
82
90
|
};
|
|
83
|
-
|
|
91
|
+
|
|
92
|
+
// eslint-disable-next-line no-await-in-loop
|
|
93
|
+
await delayRandom();
|
|
94
|
+
yield chunk;
|
|
84
95
|
}
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
yield {
|
|
97
|
+
...MOCK_RESPONSE_MESSAGE_FOR_STREAMING,
|
|
98
|
+
requestId,
|
|
99
|
+
content: MOCK_RESPONSE_MESSAGE_FOR_STREAMING.content,
|
|
100
|
+
chunkId: null,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
87
103
|
|
|
88
104
|
export const MOCK_USER_PROMPT_MESSAGE = {
|
|
89
105
|
id: '456',
|