@gitlab/ui 72.12.4 → 72.14.0
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/experimental/duo/chat/duo_chat.js +14 -24
- 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 +5 -5
- package/src/components/experimental/duo/chat/duo_chat.md +32 -0
- package/src/components/experimental/duo/chat/duo_chat.spec.js +56 -25
- package/src/components/experimental/duo/chat/duo_chat.stories.js +22 -6
- package/src/components/experimental/duo/chat/duo_chat.vue +15 -29
- package/src/components/regions/empty_state/empty_state.stories.js +2 -2
- package/tailwind.defaults.js +64 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [72.14.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v72.13.0...v72.14.0) (2024-01-30)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **tailwindcss:** extend config with colors and spacing scale ([2c7a006](https://gitlab.com/gitlab-org/gitlab-ui/commit/2c7a006e4c605c58a753d8bfbc8ceb0bdc101cf8))
|
|
7
|
+
|
|
8
|
+
# [72.13.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v72.12.4...v72.13.0) (2024-01-30)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **GlDuoChat:** slash commands for chat as a prop ([af86eb8](https://gitlab.com/gitlab-org/gitlab-ui/commit/af86eb883fad3b63b012c25fbe19f4cfa773f8ba))
|
|
14
|
+
|
|
1
15
|
## [72.12.4](https://gitlab.com/gitlab-org/gitlab-ui/compare/v72.12.3...v72.12.4) (2024-01-26)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -30,27 +30,13 @@ const i18n = {
|
|
|
30
30
|
CHAT_LEGAL_DISCLAIMER: "May provide inappropriate responses not representative of GitLab's views. Do not input personal data.",
|
|
31
31
|
CHAT_DEFAULT_PREDEFINED_PROMPTS: ['How do I change my password in GitLab?', 'How do I fork a project?', 'How do I clone a repository?', 'How do I create a template?']
|
|
32
32
|
};
|
|
33
|
-
const slashCommands = [{
|
|
34
|
-
name: '/reset',
|
|
35
|
-
shouldSubmit: true,
|
|
36
|
-
description: 'Reset conversation, ignore the previous messages.'
|
|
37
|
-
}, {
|
|
38
|
-
name: '/tests',
|
|
39
|
-
shouldSubmit: false,
|
|
40
|
-
description: 'Write tests for the selected snippet.'
|
|
41
|
-
}, {
|
|
42
|
-
name: '/refactor',
|
|
43
|
-
shouldSubmit: false,
|
|
44
|
-
description: 'Refactor the selected snippet.'
|
|
45
|
-
}, {
|
|
46
|
-
name: '/explain',
|
|
47
|
-
shouldSubmit: false,
|
|
48
|
-
description: 'Explain the selected snippet.'
|
|
49
|
-
}];
|
|
50
33
|
const isMessage = item => Boolean(item) && (item === null || item === void 0 ? void 0 : item.role);
|
|
34
|
+
const isSlashCommand = command => Boolean(command) && (command === null || command === void 0 ? void 0 : command.name) && command.description;
|
|
51
35
|
|
|
52
36
|
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
53
37
|
const itemsValidator = items => items.every(isMessage);
|
|
38
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
39
|
+
const slashCommandsValidator = commands => commands.every(isSlashCommand);
|
|
54
40
|
var script = {
|
|
55
41
|
name: 'GlDuoChat',
|
|
56
42
|
components: {
|
|
@@ -147,12 +133,13 @@ var script = {
|
|
|
147
133
|
default: i18n.CHAT_DEFAULT_TITLE
|
|
148
134
|
},
|
|
149
135
|
/**
|
|
150
|
-
*
|
|
136
|
+
* Array of slash commands to display in the chat.
|
|
151
137
|
*/
|
|
152
|
-
|
|
153
|
-
type:
|
|
138
|
+
slashCommands: {
|
|
139
|
+
type: Array,
|
|
154
140
|
required: false,
|
|
155
|
-
default:
|
|
141
|
+
default: () => [],
|
|
142
|
+
validator: slashCommandsValidator
|
|
156
143
|
}
|
|
157
144
|
},
|
|
158
145
|
data() {
|
|
@@ -164,6 +151,9 @@ var script = {
|
|
|
164
151
|
};
|
|
165
152
|
},
|
|
166
153
|
computed: {
|
|
154
|
+
withSlashCommands() {
|
|
155
|
+
return this.slashCommands.length > 0;
|
|
156
|
+
},
|
|
167
157
|
hasMessages() {
|
|
168
158
|
var _this$messages;
|
|
169
159
|
return ((_this$messages = this.messages) === null || _this$messages === void 0 ? void 0 : _this$messages.length) > 0;
|
|
@@ -188,13 +178,13 @@ var script = {
|
|
|
188
178
|
},
|
|
189
179
|
filteredSlashCommands() {
|
|
190
180
|
const caseInsensitivePrompt = this.prompt.toLowerCase();
|
|
191
|
-
return slashCommands.filter(c => c.name.toLowerCase().startsWith(caseInsensitivePrompt));
|
|
181
|
+
return this.slashCommands.filter(c => c.name.toLowerCase().startsWith(caseInsensitivePrompt));
|
|
192
182
|
},
|
|
193
183
|
shouldShowSlashCommands() {
|
|
194
184
|
if (!this.withSlashCommands) return false;
|
|
195
185
|
const caseInsensitivePrompt = this.prompt.toLowerCase();
|
|
196
186
|
const startsWithSlash = caseInsensitivePrompt.startsWith('/');
|
|
197
|
-
const startsWithSlashCommand = slashCommands.some(c => caseInsensitivePrompt.startsWith(c.name));
|
|
187
|
+
const startsWithSlashCommand = this.slashCommands.some(c => caseInsensitivePrompt.startsWith(c.name));
|
|
198
188
|
return startsWithSlash && this.filteredSlashCommands.length && !startsWithSlashCommand;
|
|
199
189
|
},
|
|
200
190
|
inputPlaceholder() {
|
|
@@ -356,4 +346,4 @@ var __vue_staticRenderFns__ = [];
|
|
|
356
346
|
);
|
|
357
347
|
|
|
358
348
|
export default __vue_component__;
|
|
359
|
-
export { i18n
|
|
349
|
+
export { i18n };
|
package/dist/tokens/js/tokens.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "72.
|
|
3
|
+
"version": "72.14.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"build-tokens": "node ./bin/build_tokens.js",
|
|
31
31
|
"clean": "rm -r dist storybook src/scss/utilities.scss",
|
|
32
32
|
"cy:a11y": "cypress run --browser chrome --env grepTags=@a11y",
|
|
33
|
-
"cy:edge": "cypress run --browser edge --env grepTags=-@a11y",
|
|
33
|
+
"cy:edge": "cypress run --browser edge --env grepTags=-@a11y+-@storybook",
|
|
34
34
|
"cy:run": "cypress run --browser firefox --env grepTags=-@a11y",
|
|
35
35
|
"start": "yarn storybook",
|
|
36
36
|
"storybook": "yarn storybook-prep && storybook dev --ci --host ${STORYBOOK_HOST:-localhost} --port 9001 -c .storybook",
|
|
@@ -91,16 +91,16 @@
|
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
93
|
"@arkweid/lefthook": "0.7.7",
|
|
94
|
-
"@babel/core": "^7.23.
|
|
94
|
+
"@babel/core": "^7.23.9",
|
|
95
95
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
|
|
96
96
|
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
|
97
|
-
"@babel/preset-env": "^7.23.
|
|
97
|
+
"@babel/preset-env": "^7.23.9",
|
|
98
98
|
"@babel/preset-react": "^7.23.3",
|
|
99
99
|
"@cypress/grep": "^4.0.1",
|
|
100
100
|
"@gitlab/eslint-plugin": "19.4.0",
|
|
101
101
|
"@gitlab/fonts": "^1.3.0",
|
|
102
102
|
"@gitlab/stylelint-config": "5.0.1",
|
|
103
|
-
"@gitlab/svgs": "3.
|
|
103
|
+
"@gitlab/svgs": "3.79.0",
|
|
104
104
|
"@rollup/plugin-commonjs": "^11.1.0",
|
|
105
105
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
|
106
106
|
"@rollup/plugin-replace": "^2.3.2",
|
|
@@ -18,6 +18,7 @@ consumer component.
|
|
|
18
18
|
:predefined-prompts="predefinedPrompts"
|
|
19
19
|
:badge-help-page-url="badgeHelpPageUrl"
|
|
20
20
|
:tool-name="toolName"
|
|
21
|
+
:slash-commands="slashCommands"
|
|
21
22
|
@chat-hidden="onChatHidden"
|
|
22
23
|
@send-chat-prompt="onSendChatPrompt"
|
|
23
24
|
@track-feedback="onTrackFeedback"
|
|
@@ -141,3 +142,34 @@ With this template in place, consumer is left with the following things to imple
|
|
|
141
142
|
|
|
142
143
|
- Send the new user's prompt. For Duo Chat, we rely on GraphQL mutation for this purpose.
|
|
143
144
|
- Send user feedback to the telemetry of your choice when `track-feedback` event arrives.
|
|
145
|
+
|
|
146
|
+
## Slash commands
|
|
147
|
+
|
|
148
|
+
One of the props accepted by the component is the `slashCommands`. This is an `Array` of
|
|
149
|
+
the commands, shown to user when they start typing the prompt with a slash (`/`)
|
|
150
|
+
character.
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
<script>
|
|
154
|
+
const slashCommands = [
|
|
155
|
+
{
|
|
156
|
+
name: '/mycommand', // This is the exact name of my command as it will be submitted
|
|
157
|
+
shouldSubmit: true, // If the command should be submitted right away without free text
|
|
158
|
+
description: 'The description of my super-duper command.',
|
|
159
|
+
},
|
|
160
|
+
...
|
|
161
|
+
];
|
|
162
|
+
export default {
|
|
163
|
+
...
|
|
164
|
+
options: {
|
|
165
|
+
slashCommands
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
<script>
|
|
169
|
+
<template>
|
|
170
|
+
<gl-duo-chat
|
|
171
|
+
...
|
|
172
|
+
:slash-commands="slashCommands"
|
|
173
|
+
/>
|
|
174
|
+
</template>
|
|
175
|
+
```
|
|
@@ -7,10 +7,42 @@ import GlDropdownItem from '../../../base/dropdown/dropdown_item.vue';
|
|
|
7
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
|
-
import GlDuoChat
|
|
10
|
+
import GlDuoChat from './duo_chat.vue';
|
|
11
11
|
|
|
12
12
|
import { MESSAGE_MODEL_ROLES, CHAT_RESET_MESSAGE } from './constants';
|
|
13
13
|
|
|
14
|
+
const slashCommands = [
|
|
15
|
+
{
|
|
16
|
+
name: '/reset',
|
|
17
|
+
shouldSubmit: true,
|
|
18
|
+
description: 'Reset conversation, ignore the previous messages.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: '/tests',
|
|
22
|
+
description: 'Write tests for the selected snippet.',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: '/refactor',
|
|
26
|
+
description: 'Refactor the selected snippet.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: '/explain',
|
|
30
|
+
description: 'Explain the selected snippet.',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const invalidSlashCommands = [
|
|
35
|
+
{
|
|
36
|
+
name: '/foo',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
description: '/bar',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
shouldSubmit: true,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
14
46
|
const generatePartialSlashCommands = () => {
|
|
15
47
|
const res = [];
|
|
16
48
|
slashCommands.forEach((command) => {
|
|
@@ -421,21 +453,14 @@ describe('GlDuoChat', () => {
|
|
|
421
453
|
slashCommandsNames.filter((name) => commands.includes(name));
|
|
422
454
|
|
|
423
455
|
describe('rendering', () => {
|
|
424
|
-
describe('without
|
|
456
|
+
describe('without slash commands', () => {
|
|
425
457
|
it('does not render slash commands by default', () => {
|
|
426
|
-
createComponent(
|
|
427
|
-
propsData: {
|
|
428
|
-
withSlashCommands: false,
|
|
429
|
-
},
|
|
430
|
-
});
|
|
458
|
+
createComponent();
|
|
431
459
|
expect(findSlashCommandsCard().exists()).toBe(false);
|
|
432
460
|
});
|
|
433
461
|
|
|
434
462
|
it('does not render slash commands when prompt is "/"', async () => {
|
|
435
463
|
createComponent({
|
|
436
|
-
propsData: {
|
|
437
|
-
withSlashCommands: false,
|
|
438
|
-
},
|
|
439
464
|
data: {
|
|
440
465
|
prompt: '/',
|
|
441
466
|
},
|
|
@@ -446,20 +471,16 @@ describe('GlDuoChat', () => {
|
|
|
446
471
|
});
|
|
447
472
|
|
|
448
473
|
it('shows default placeholder in the chat input', () => {
|
|
449
|
-
createComponent(
|
|
450
|
-
propsData: {
|
|
451
|
-
withSlashCommands: false,
|
|
452
|
-
},
|
|
453
|
-
});
|
|
474
|
+
createComponent();
|
|
454
475
|
expect(findChatInput().attributes('placeholder')).toBe('GitLab Duo Chat');
|
|
455
476
|
});
|
|
456
477
|
});
|
|
457
478
|
|
|
458
|
-
describe('with
|
|
479
|
+
describe('with slash commands', () => {
|
|
459
480
|
it('does not render slash commands by default', async () => {
|
|
460
481
|
createComponent({
|
|
461
482
|
propsData: {
|
|
462
|
-
|
|
483
|
+
slashCommands,
|
|
463
484
|
},
|
|
464
485
|
});
|
|
465
486
|
|
|
@@ -470,7 +491,7 @@ describe('GlDuoChat', () => {
|
|
|
470
491
|
it('renders all slash commands when prompt is "/"', async () => {
|
|
471
492
|
createComponent({
|
|
472
493
|
propsData: {
|
|
473
|
-
|
|
494
|
+
slashCommands,
|
|
474
495
|
},
|
|
475
496
|
data: {
|
|
476
497
|
prompt: '/',
|
|
@@ -490,19 +511,29 @@ describe('GlDuoChat', () => {
|
|
|
490
511
|
it('shows the correct placeholder in the chat input', () => {
|
|
491
512
|
createComponent({
|
|
492
513
|
propsData: {
|
|
493
|
-
|
|
514
|
+
slashCommands,
|
|
494
515
|
},
|
|
495
516
|
});
|
|
496
517
|
expect(findChatInput().attributes('placeholder')).toBe('Type "/" for slash commands');
|
|
497
518
|
});
|
|
498
519
|
|
|
520
|
+
it('prevents passing down invalid slash commands', () => {
|
|
521
|
+
expect(() => {
|
|
522
|
+
wrapper = shallowMount(GlDuoChat, {
|
|
523
|
+
propsData: {
|
|
524
|
+
slashCommands: [...slashCommands, ...invalidSlashCommands],
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
}).toHaveLength(0);
|
|
528
|
+
});
|
|
529
|
+
|
|
499
530
|
describe('when the prompt includes the "/" character or no characters', () => {
|
|
500
531
|
it.each(['', '//', '\\', 'foo', '/foo'])(
|
|
501
532
|
'does not render the slash commands if prompt is "$prompt"',
|
|
502
533
|
async (prompt) => {
|
|
503
534
|
createComponent({
|
|
504
535
|
propsData: {
|
|
505
|
-
|
|
536
|
+
slashCommands,
|
|
506
537
|
},
|
|
507
538
|
data: {
|
|
508
539
|
prompt,
|
|
@@ -521,7 +552,7 @@ describe('GlDuoChat', () => {
|
|
|
521
552
|
async (prompt) => {
|
|
522
553
|
createComponent({
|
|
523
554
|
propsData: {
|
|
524
|
-
|
|
555
|
+
slashCommands,
|
|
525
556
|
},
|
|
526
557
|
data: {
|
|
527
558
|
prompt,
|
|
@@ -540,7 +571,7 @@ describe('GlDuoChat', () => {
|
|
|
540
571
|
async (prompt) => {
|
|
541
572
|
createComponent({
|
|
542
573
|
propsData: {
|
|
543
|
-
|
|
574
|
+
slashCommands,
|
|
544
575
|
},
|
|
545
576
|
data: {
|
|
546
577
|
prompt,
|
|
@@ -575,7 +606,7 @@ describe('GlDuoChat', () => {
|
|
|
575
606
|
async ({ prompt, expectedCommands } = {}) => {
|
|
576
607
|
createComponent({
|
|
577
608
|
propsData: {
|
|
578
|
-
|
|
609
|
+
slashCommands,
|
|
579
610
|
},
|
|
580
611
|
data: {
|
|
581
612
|
prompt,
|
|
@@ -595,7 +626,7 @@ describe('GlDuoChat', () => {
|
|
|
595
626
|
beforeEach(() => {
|
|
596
627
|
createComponent({
|
|
597
628
|
propsData: {
|
|
598
|
-
|
|
629
|
+
slashCommands,
|
|
599
630
|
messages,
|
|
600
631
|
},
|
|
601
632
|
data: {
|
|
@@ -664,7 +695,7 @@ describe('GlDuoChat', () => {
|
|
|
664
695
|
beforeEach(() => {
|
|
665
696
|
createComponent({
|
|
666
697
|
propsData: {
|
|
667
|
-
|
|
698
|
+
slashCommands,
|
|
668
699
|
messages,
|
|
669
700
|
},
|
|
670
701
|
data: {
|
|
@@ -12,6 +12,26 @@ import {
|
|
|
12
12
|
renderMarkdown,
|
|
13
13
|
} from './mock_data';
|
|
14
14
|
|
|
15
|
+
const slashCommands = [
|
|
16
|
+
{
|
|
17
|
+
name: '/reset',
|
|
18
|
+
shouldSubmit: true,
|
|
19
|
+
description: 'Reset conversation, ignore the previous messages.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: '/tests',
|
|
23
|
+
description: 'Write tests for the selected snippet.',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: '/refactor',
|
|
27
|
+
description: 'Refactor the selected snippet.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: '/explain',
|
|
31
|
+
description: 'Explain the selected snippet.',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
15
35
|
const defaultValue = (prop) =>
|
|
16
36
|
typeof GlDuoChat.props[prop].default === 'function'
|
|
17
37
|
? GlDuoChat.props[prop].default()
|
|
@@ -27,7 +47,6 @@ const generateProps = ({
|
|
|
27
47
|
badgeHelpPageUrl = defaultValue('badgeHelpPageUrl'),
|
|
28
48
|
badgeType = defaultValue('badgeType'),
|
|
29
49
|
toolName = defaultValue('toolName'),
|
|
30
|
-
withSlashCommands = defaultValue('withSlashCommands'),
|
|
31
50
|
} = {}) => ({
|
|
32
51
|
title,
|
|
33
52
|
messages,
|
|
@@ -38,7 +57,7 @@ const generateProps = ({
|
|
|
38
57
|
badgeHelpPageUrl,
|
|
39
58
|
badgeType,
|
|
40
59
|
toolName,
|
|
41
|
-
|
|
60
|
+
slashCommands,
|
|
42
61
|
});
|
|
43
62
|
|
|
44
63
|
export const Default = (args, { argTypes }) => ({
|
|
@@ -59,7 +78,6 @@ export const Default = (args, { argTypes }) => ({
|
|
|
59
78
|
:badge-help-page-url="badgeHelpPageUrl"
|
|
60
79
|
:badge-type="badgeType"
|
|
61
80
|
:tool-name="toolName"
|
|
62
|
-
:with-slash-commands="withSlashCommands"
|
|
63
81
|
/>`,
|
|
64
82
|
});
|
|
65
83
|
Default.args = generateProps({
|
|
@@ -154,7 +172,6 @@ export const Interactive = (args, { argTypes }) => ({
|
|
|
154
172
|
:badge-help-page-url="badgeHelpPageUrl"
|
|
155
173
|
:badge-type="badgeType"
|
|
156
174
|
:tool-name="toolName"
|
|
157
|
-
:with-slash-commands="withSlashCommands"
|
|
158
175
|
@send-chat-prompt="onSendChatPrompt"
|
|
159
176
|
@chat-hidden="onChatHidden"
|
|
160
177
|
/>
|
|
@@ -180,8 +197,7 @@ export const Slots = (args, { argTypes }) => ({
|
|
|
180
197
|
:predefined-prompts="predefinedPrompts"
|
|
181
198
|
:badge-help-page-url="badgeHelpPageUrl"
|
|
182
199
|
:badge-type="badgeType"
|
|
183
|
-
:tool-name="toolName"
|
|
184
|
-
:with-slash-commands="withSlashCommands">
|
|
200
|
+
:tool-name="toolName">
|
|
185
201
|
|
|
186
202
|
<template #hero>
|
|
187
203
|
<pre class="code-block rounded code highlight gl-border-b gl-rounded-0! gl-mb-0 gl-overflow-y-auto solarized-light" style="max-height: 20rem; overflow-y: auto;">
|
|
@@ -37,33 +37,13 @@ export const i18n = {
|
|
|
37
37
|
],
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
export const slashCommands = [
|
|
41
|
-
{
|
|
42
|
-
name: '/reset',
|
|
43
|
-
shouldSubmit: true,
|
|
44
|
-
description: 'Reset conversation, ignore the previous messages.',
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
name: '/tests',
|
|
48
|
-
shouldSubmit: false,
|
|
49
|
-
description: 'Write tests for the selected snippet.',
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: '/refactor',
|
|
53
|
-
shouldSubmit: false,
|
|
54
|
-
description: 'Refactor the selected snippet.',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: '/explain',
|
|
58
|
-
shouldSubmit: false,
|
|
59
|
-
description: 'Explain the selected snippet.',
|
|
60
|
-
},
|
|
61
|
-
];
|
|
62
|
-
|
|
63
40
|
const isMessage = (item) => Boolean(item) && item?.role;
|
|
41
|
+
const isSlashCommand = (command) => Boolean(command) && command?.name && command.description;
|
|
64
42
|
|
|
65
43
|
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
66
44
|
const itemsValidator = (items) => items.every(isMessage);
|
|
45
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
46
|
+
const slashCommandsValidator = (commands) => commands.every(isSlashCommand);
|
|
67
47
|
|
|
68
48
|
export default {
|
|
69
49
|
name: 'GlDuoChat',
|
|
@@ -161,12 +141,13 @@ export default {
|
|
|
161
141
|
default: i18n.CHAT_DEFAULT_TITLE,
|
|
162
142
|
},
|
|
163
143
|
/**
|
|
164
|
-
*
|
|
144
|
+
* Array of slash commands to display in the chat.
|
|
165
145
|
*/
|
|
166
|
-
|
|
167
|
-
type:
|
|
146
|
+
slashCommands: {
|
|
147
|
+
type: Array,
|
|
168
148
|
required: false,
|
|
169
|
-
default:
|
|
149
|
+
default: () => [],
|
|
150
|
+
validator: slashCommandsValidator,
|
|
170
151
|
},
|
|
171
152
|
},
|
|
172
153
|
data() {
|
|
@@ -178,6 +159,9 @@ export default {
|
|
|
178
159
|
};
|
|
179
160
|
},
|
|
180
161
|
computed: {
|
|
162
|
+
withSlashCommands() {
|
|
163
|
+
return this.slashCommands.length > 0;
|
|
164
|
+
},
|
|
181
165
|
hasMessages() {
|
|
182
166
|
return this.messages?.length > 0;
|
|
183
167
|
},
|
|
@@ -206,13 +190,15 @@ export default {
|
|
|
206
190
|
},
|
|
207
191
|
filteredSlashCommands() {
|
|
208
192
|
const caseInsensitivePrompt = this.prompt.toLowerCase();
|
|
209
|
-
return slashCommands.filter((c) =>
|
|
193
|
+
return this.slashCommands.filter((c) =>
|
|
194
|
+
c.name.toLowerCase().startsWith(caseInsensitivePrompt)
|
|
195
|
+
);
|
|
210
196
|
},
|
|
211
197
|
shouldShowSlashCommands() {
|
|
212
198
|
if (!this.withSlashCommands) return false;
|
|
213
199
|
const caseInsensitivePrompt = this.prompt.toLowerCase();
|
|
214
200
|
const startsWithSlash = caseInsensitivePrompt.startsWith('/');
|
|
215
|
-
const startsWithSlashCommand = slashCommands.some((c) =>
|
|
201
|
+
const startsWithSlashCommand = this.slashCommands.some((c) =>
|
|
216
202
|
caseInsensitivePrompt.startsWith(c.name)
|
|
217
203
|
);
|
|
218
204
|
return startsWithSlash && this.filteredSlashCommands.length && !startsWithSlashCommand;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import productivityAnalyticsEmptyStateSvg from '@gitlab/svgs/dist/illustrations/productivity-analytics-empty-state.svg';
|
|
2
2
|
import issuesSvg from '@gitlab/svgs/dist/illustrations/rocket-launch-md.svg';
|
|
3
3
|
import GlButton from '../../base/button/button.vue';
|
|
4
4
|
import GlEmptyState from './empty_state.vue';
|
|
@@ -28,7 +28,7 @@ const Template = (args) => ({
|
|
|
28
28
|
|
|
29
29
|
const generateProps = ({
|
|
30
30
|
title = 'This state is empty',
|
|
31
|
-
svgPath =
|
|
31
|
+
svgPath = productivityAnalyticsEmptyStateSvg,
|
|
32
32
|
svgHeight = 144,
|
|
33
33
|
description = 'The title and message should be clear, concise, and explain why the user is seeing this screen.',
|
|
34
34
|
primaryButtonText = 'Something actionable',
|
package/tailwind.defaults.js
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
|
+
const baseColorTokens = require('./src/tokens/color.tokens.json');
|
|
2
|
+
const themeColorTokens = require('./src/tokens/color.theme.tokens.json');
|
|
3
|
+
|
|
4
|
+
const baseColors = ['blue', 'gray', 'green', 'orange', 'purple', 'red'].reduce((acc, color) => {
|
|
5
|
+
acc[color] = {};
|
|
6
|
+
Object.entries(baseColorTokens[color]).forEach(([shade, { $value }]) => {
|
|
7
|
+
acc[color][shade] = $value;
|
|
8
|
+
});
|
|
9
|
+
return acc;
|
|
10
|
+
}, {});
|
|
11
|
+
|
|
12
|
+
const themeColors = Object.entries(themeColorTokens.theme).reduce((acc, [color, shades]) => {
|
|
13
|
+
const colorKey = `theme-${color}`;
|
|
14
|
+
acc[colorKey] = {};
|
|
15
|
+
Object.entries(shades).forEach(([shade, { $value }]) => {
|
|
16
|
+
acc[colorKey][shade] = $value;
|
|
17
|
+
});
|
|
18
|
+
return acc;
|
|
19
|
+
}, {});
|
|
20
|
+
|
|
21
|
+
const gridSize = 0.5; // rem
|
|
22
|
+
const spacing = Object.fromEntries(
|
|
23
|
+
Object.entries({
|
|
24
|
+
0: 0,
|
|
25
|
+
1: 0.25,
|
|
26
|
+
2: 0.5,
|
|
27
|
+
3: 1,
|
|
28
|
+
4: 1.5,
|
|
29
|
+
5: 2,
|
|
30
|
+
6: 3,
|
|
31
|
+
7: 4,
|
|
32
|
+
8: 5,
|
|
33
|
+
9: 6,
|
|
34
|
+
10: 7,
|
|
35
|
+
11: 8,
|
|
36
|
+
'11-5': 9,
|
|
37
|
+
12: 10,
|
|
38
|
+
13: 12,
|
|
39
|
+
15: 15,
|
|
40
|
+
20: 20,
|
|
41
|
+
26: 26,
|
|
42
|
+
28: 28,
|
|
43
|
+
30: 30,
|
|
44
|
+
31: 31,
|
|
45
|
+
34: 34,
|
|
46
|
+
48: 48,
|
|
47
|
+
62: 62,
|
|
48
|
+
75: 75,
|
|
49
|
+
80: 80,
|
|
50
|
+
88: 88,
|
|
51
|
+
}).map(([scale, factor]) => {
|
|
52
|
+
return [scale, `${factor * gridSize}rem`];
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
|
|
1
56
|
/** @type {import('tailwindcss').Config} */
|
|
2
57
|
module.exports = {
|
|
3
58
|
prefix: 'gl-',
|
|
4
|
-
|
|
59
|
+
theme: {
|
|
60
|
+
colors: {
|
|
61
|
+
white: baseColorTokens.white.$value,
|
|
62
|
+
black: baseColorTokens.black.$value,
|
|
63
|
+
...baseColors,
|
|
64
|
+
...themeColors,
|
|
65
|
+
},
|
|
66
|
+
spacing,
|
|
67
|
+
},
|
|
5
68
|
};
|