@gitlab/duo-ui 15.2.0 → 15.4.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/agentic_chat/agentic_duo_chat.js +1 -1
- package/dist/components/agentic_chat/web_agentic_duo_chat.js +6 -1
- package/dist/components/chat/components/duo_chat_message/duo_chat_message.js +21 -28
- package/dist/components/chat/components/duo_chat_message/markdown_renderer.js +102 -0
- package/dist/components/chat/components/duo_chat_message/message_types/message_agent.js +5 -6
- package/dist/components/chat/components/duo_chat_message/message_types/message_base.js +27 -14
- package/dist/components/chat/components/duo_chat_message/message_types/message_tool_kv_section.js +1 -1
- package/dist/components/chat/components/duo_chat_message_tool_approval/components/base_tool_params.js +1 -1
- package/dist/components/chat/components/duo_chat_message_tool_approval/components/create_commit_tool_params.js +9 -3
- package/dist/components/chat/components/duo_chat_message_tool_approval/components/pre_block.js +11 -6
- package/dist/components/chat/components/duo_chat_message_tool_approval/components/tool_params_json_view.js +1 -1
- package/dist/components/chat/components/duo_chat_threads/duo_chat_threads.js +9 -2
- package/dist/components/chat/components/duo_chat_threads/duo_chat_threads_skeleton.js +48 -0
- package/dist/components/chat/duo_chat.js +1 -1
- package/dist/components/chat/markdown_renderer.js +31 -5
- package/dist/components/chat/mock_data.js +28 -2
- package/dist/components/chat/web_duo_chat.js +1 -1
- package/dist/components/ui/duo_layout/duo_layout.js +1 -1
- package/dist/components.css +1 -1
- package/dist/components.css.map +1 -1
- package/dist/tailwind.css +1 -1
- package/dist/tailwind.css.map +1 -1
- package/dist/utils/highlight.js +483 -0
- package/package.json +3 -2
- package/src/components/agentic_chat/agentic_duo_chat.scss +2 -1
- package/src/components/agentic_chat/agentic_duo_chat.vue +1 -1
- package/src/components/agentic_chat/web_agentic_duo_chat.vue +8 -1
- package/src/components/chat/components/duo_chat_message/duo_chat_message.scss +15 -19
- package/src/components/chat/components/duo_chat_message/duo_chat_message.vue +30 -28
- package/src/components/chat/components/duo_chat_message/markdown_renderer.vue +71 -0
- package/src/components/chat/components/duo_chat_message/message_types/message_agent.vue +8 -8
- package/src/components/chat/components/duo_chat_message/message_types/message_base.vue +24 -14
- package/src/components/chat/components/duo_chat_message/message_types/message_tool_kv_section.vue +1 -1
- package/src/components/chat/components/duo_chat_message_tool_approval/components/base_tool_params.vue +1 -1
- package/src/components/chat/components/duo_chat_message_tool_approval/components/create_commit_tool_params.vue +7 -2
- package/src/components/chat/components/duo_chat_message_tool_approval/components/pre_block.vue +11 -6
- package/src/components/chat/components/duo_chat_message_tool_approval/components/tool_params_json_view.vue +1 -1
- package/src/components/chat/components/duo_chat_threads/duo_chat_threads.vue +9 -1
- package/src/components/chat/components/duo_chat_threads/duo_chat_threads_skeleton.vue +39 -0
- package/src/components/chat/duo_chat.vue +1 -1
- package/src/components/chat/markdown_renderer.js +37 -6
- package/src/components/chat/mock_data.js +30 -1
- package/src/components/chat/web_duo_chat.vue +1 -1
- package/src/components/ui/duo_layout/duo_layout.vue +1 -1
- package/src/utils/highlight.js +562 -0
- package/translations.js +1 -0
- package/dist/components/chat/components/duo_chat_message_tool_approval/services/highlight.js +0 -21
- package/src/components/chat/components/duo_chat_message_tool_approval/services/highlight.js +0 -18
|
@@ -13,8 +13,7 @@ $duo-code-scrim-top-gradient: linear-gradient(to top, rgba($gray-10, 0), $gray-1
|
|
|
13
13
|
|
|
14
14
|
pre {
|
|
15
15
|
box-shadow: none !important;
|
|
16
|
-
|
|
17
|
-
@apply gl-border gl-max-h-[60vh] gl-px-4 gl-py-3 gl-text-inherit;
|
|
16
|
+
@apply gl-border gl-max-h-[60vh] gl-scroll-smooth gl-px-4 gl-py-3 gl-text-inherit;
|
|
18
17
|
background-color: var(--gl-background-color-preformat, var(--gl-background-color-default));
|
|
19
18
|
|
|
20
19
|
&::before,
|
|
@@ -44,9 +43,10 @@ $duo-code-scrim-top-gradient: linear-gradient(to top, rgba($gray-10, 0), $gray-1
|
|
|
44
43
|
|
|
45
44
|
pre code {
|
|
46
45
|
@apply gl-text-sm;
|
|
47
|
-
@apply gl-leading-1;
|
|
48
46
|
@apply gl-bg-transparent;
|
|
49
|
-
|
|
47
|
+
@apply gl-block;
|
|
48
|
+
@apply gl-whitespace-pre-wrap;
|
|
49
|
+
@apply gl-break-all;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
p:not(:last-of-type) {
|
|
@@ -87,15 +87,6 @@ $duo-code-scrim-top-gradient: linear-gradient(to top, rgba($gray-10, 0), $gray-1
|
|
|
87
87
|
copy-code {
|
|
88
88
|
margin-right: $gl-spacing-scale-8;
|
|
89
89
|
}
|
|
90
|
-
|
|
91
|
-
.js-markdown-code.markdown-code-block:hover {
|
|
92
|
-
copy-code,
|
|
93
|
-
copy-code:focus-within,
|
|
94
|
-
insert-code-snippet,
|
|
95
|
-
insert-code-snippet:focus-within {
|
|
96
|
-
@apply gl-opacity-10;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
90
|
}
|
|
100
91
|
|
|
101
92
|
.insert-code-hidden {
|
|
@@ -108,13 +99,18 @@ $duo-code-scrim-top-gradient: linear-gradient(to top, rgba($gray-10, 0), $gray-1
|
|
|
108
99
|
}
|
|
109
100
|
}
|
|
110
101
|
|
|
111
|
-
.duo-message-
|
|
112
|
-
.duo-message-pre-block:
|
|
113
|
-
.
|
|
114
|
-
|
|
102
|
+
.duo-chat-message-complete {
|
|
103
|
+
.duo-message-pre-block:focus,
|
|
104
|
+
.duo-message-pre-block:hover,
|
|
105
|
+
.duo-message-pre-block:has(button:focus) {
|
|
106
|
+
copy-code,
|
|
107
|
+
insert-code-snippet,
|
|
108
|
+
.copy-to-clipboard-button-container {
|
|
109
|
+
opacity: 1;
|
|
110
|
+
}
|
|
115
111
|
}
|
|
116
112
|
}
|
|
117
113
|
|
|
118
|
-
.duo-message-
|
|
119
|
-
|
|
114
|
+
.duo-chat-message-error > p {
|
|
115
|
+
margin: 0;
|
|
120
116
|
}
|
|
@@ -14,9 +14,9 @@ import { MESSAGE_MODEL_ROLES, SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED } from '.
|
|
|
14
14
|
|
|
15
15
|
import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_sources.vue';
|
|
16
16
|
// eslint-disable-next-line no-restricted-imports
|
|
17
|
-
import { renderDuoChatMarkdownPreview } from '../../markdown_renderer';
|
|
18
17
|
import { copyToClipboard, concatUntilEmpty } from '../utils';
|
|
19
18
|
import MessageFeedback from './message_feedback.vue';
|
|
19
|
+
import MarkdownRenderer from './markdown_renderer.vue';
|
|
20
20
|
import { CopyCodeElement } from './copy_code_element';
|
|
21
21
|
import { InsertCodeSnippetElement } from './insert_code_snippet_element';
|
|
22
22
|
|
|
@@ -48,6 +48,7 @@ export default {
|
|
|
48
48
|
GlIcon,
|
|
49
49
|
GlAnimatedLoaderIcon,
|
|
50
50
|
GlButton,
|
|
51
|
+
MarkdownRenderer,
|
|
51
52
|
},
|
|
52
53
|
directives: {
|
|
53
54
|
SafeHtml,
|
|
@@ -64,10 +65,6 @@ export default {
|
|
|
64
65
|
element.classList.add('duo-chat-markdown', 'duo-chat-compact-markdown');
|
|
65
66
|
},
|
|
66
67
|
},
|
|
67
|
-
renderMarkdown: {
|
|
68
|
-
from: 'renderMarkdown',
|
|
69
|
-
default: () => renderDuoChatMarkdownPreview,
|
|
70
|
-
},
|
|
71
68
|
},
|
|
72
69
|
props: {
|
|
73
70
|
/**
|
|
@@ -147,31 +144,25 @@ export default {
|
|
|
147
144
|
hasFeedback() {
|
|
148
145
|
return Boolean(this.message.extras?.hasFeedback);
|
|
149
146
|
},
|
|
147
|
+
hasContentHtml() {
|
|
148
|
+
return this.message.contentHtml?.length > 0;
|
|
149
|
+
},
|
|
150
150
|
defaultContent() {
|
|
151
|
-
if (this.
|
|
151
|
+
if (this.hasContentHtml) {
|
|
152
152
|
return this.message.contentHtml;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
return this.
|
|
155
|
+
return this.message.content;
|
|
156
156
|
},
|
|
157
157
|
messageContent() {
|
|
158
158
|
if (this.isAssistantMessage && this.isChunk) {
|
|
159
|
-
return
|
|
160
|
-
trustedUrls: this.trustedUrls,
|
|
161
|
-
});
|
|
159
|
+
return concatUntilEmpty(this.messageChunks);
|
|
162
160
|
}
|
|
163
161
|
|
|
164
|
-
return (
|
|
165
|
-
this.renderMarkdown(this.defaultContent, { trustedUrls: this.trustedUrls }) ||
|
|
166
|
-
this.renderMarkdown(concatUntilEmpty(this.message.chunks), {
|
|
167
|
-
trustedUrls: this.trustedUrls,
|
|
168
|
-
})
|
|
169
|
-
);
|
|
162
|
+
return this.defaultContent || concatUntilEmpty(this.message.chunks);
|
|
170
163
|
},
|
|
171
164
|
renderedError() {
|
|
172
|
-
return this.
|
|
173
|
-
trustedUrls: this.trustedUrls,
|
|
174
|
-
});
|
|
165
|
+
return this.message.errors?.join('; ');
|
|
175
166
|
},
|
|
176
167
|
error() {
|
|
177
168
|
return Boolean(this.message?.errors?.length) && this.message.errors.join('; ');
|
|
@@ -255,12 +246,14 @@ export default {
|
|
|
255
246
|
this.messageWatcher = null; // Ensure the watcher can't be stopped multiple times
|
|
256
247
|
}
|
|
257
248
|
},
|
|
258
|
-
hydrateContentWithGFM() {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
249
|
+
async hydrateContentWithGFM() {
|
|
250
|
+
await this.$nextTick();
|
|
251
|
+
if (this.isDoneStreaming) {
|
|
252
|
+
if (this.$refs.content) {
|
|
253
|
+
this.renderGFM(this.$refs.content.$el);
|
|
254
|
+
}
|
|
263
255
|
}
|
|
256
|
+
|
|
264
257
|
this.detectScrollableCodeBlocks();
|
|
265
258
|
},
|
|
266
259
|
logEvent(e) {
|
|
@@ -339,6 +332,7 @@ export default {
|
|
|
339
332
|
:class="{
|
|
340
333
|
'gl-pr-7': isAssistantMessage,
|
|
341
334
|
'gl-justify-end gl-pl-7': isUserMessage,
|
|
335
|
+
'duo-chat-message-complete': isDoneStreaming,
|
|
342
336
|
}"
|
|
343
337
|
@insert-code-snippet="onInsertCodeSnippet"
|
|
344
338
|
@copy-code-snippet="onCopyCodeSnippet"
|
|
@@ -356,7 +350,13 @@ export default {
|
|
|
356
350
|
class="error-icon !gl-mt-1 gl-mr-3 gl-shrink-0 gl-text-danger"
|
|
357
351
|
data-testid="error"
|
|
358
352
|
/>
|
|
359
|
-
<
|
|
353
|
+
<markdown-renderer
|
|
354
|
+
v-if="error"
|
|
355
|
+
ref="error-message"
|
|
356
|
+
class="duo-chat-message-error"
|
|
357
|
+
:markdown="renderedError"
|
|
358
|
+
:trusted-urls="trustedUrls"
|
|
359
|
+
/>
|
|
360
360
|
</div>
|
|
361
361
|
<template v-else-if="isAssistantMessage || isUserMessage">
|
|
362
362
|
<div class="gl-sr-only">
|
|
@@ -372,14 +372,16 @@ export default {
|
|
|
372
372
|
@get-content="onGetContextItemContent"
|
|
373
373
|
/>
|
|
374
374
|
|
|
375
|
-
<
|
|
375
|
+
<markdown-renderer
|
|
376
376
|
ref="content"
|
|
377
|
-
v-safe-html:[$options.safeHtmlConfigExtension]="messageContent"
|
|
378
377
|
class="duo-chat-message"
|
|
379
378
|
:class="{
|
|
380
379
|
'gl-bg-feedback-info gl-p-4 gl-text-feedback-info': isUserMessage,
|
|
381
380
|
}"
|
|
382
|
-
|
|
381
|
+
:is-html="hasContentHtml"
|
|
382
|
+
:markdown="messageContent"
|
|
383
|
+
:trusted-urls="trustedUrls"
|
|
384
|
+
/>
|
|
383
385
|
|
|
384
386
|
<documentation-sources v-if="sources" :sources="sources" />
|
|
385
387
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
|
|
3
|
+
// eslint-disable-next-line no-restricted-imports
|
|
4
|
+
import { renderDuoChatMarkdownPreview } from '../../markdown_renderer';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
name: 'MarkdownRenderer',
|
|
8
|
+
directives: {
|
|
9
|
+
SafeHtml,
|
|
10
|
+
},
|
|
11
|
+
inject: {
|
|
12
|
+
// Note, we likely might move away from Provide/Inject for this
|
|
13
|
+
// and only ship the versions that are currently in the default
|
|
14
|
+
// See https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/3953#note_1762834219
|
|
15
|
+
// for more context.
|
|
16
|
+
renderGFM: {
|
|
17
|
+
from: 'renderGFM',
|
|
18
|
+
default: () => (element) => {
|
|
19
|
+
element.classList.add('gl-markdown', 'gl-compact-markdown', 'gl-text-sm');
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
renderMarkdown: {
|
|
23
|
+
from: 'renderMarkdown',
|
|
24
|
+
default: () => renderDuoChatMarkdownPreview,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
props: {
|
|
28
|
+
markdown: {
|
|
29
|
+
type: String,
|
|
30
|
+
required: false,
|
|
31
|
+
default: '',
|
|
32
|
+
},
|
|
33
|
+
isHtml: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
required: false,
|
|
36
|
+
default: false,
|
|
37
|
+
},
|
|
38
|
+
trustedUrls: {
|
|
39
|
+
type: Array,
|
|
40
|
+
required: false,
|
|
41
|
+
default: () => [],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
data() {
|
|
45
|
+
return {
|
|
46
|
+
renderedMarkdown: '',
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
watch: {
|
|
50
|
+
async markdown() {
|
|
51
|
+
this.render();
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
created() {
|
|
55
|
+
this.render();
|
|
56
|
+
},
|
|
57
|
+
methods: {
|
|
58
|
+
async render() {
|
|
59
|
+
this.renderedMarkdown = this.isHtml
|
|
60
|
+
? this.markdown
|
|
61
|
+
: await this.renderMarkdown(this.markdown, this.trustedUrls);
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
safeHtmlConfigExtension: {
|
|
65
|
+
ADD_TAGS: ['copy-code', 'insert-code-snippet'],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
</script>
|
|
69
|
+
<template>
|
|
70
|
+
<div ref="content" v-safe-html:[$options.safeHtmlConfigExtension]="renderedMarkdown"></div>
|
|
71
|
+
</template>
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { GlIcon
|
|
2
|
+
import { GlIcon } from '@gitlab/ui';
|
|
3
3
|
import MessageFeedback from '../message_feedback.vue';
|
|
4
|
+
import MarkdownRenderer from '../markdown_renderer.vue';
|
|
4
5
|
import BaseMessage from './message_base.vue';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
name: 'DuoAgentMessage',
|
|
8
9
|
components: {
|
|
9
10
|
BaseMessage,
|
|
10
|
-
MessageFeedback,
|
|
11
11
|
GlIcon,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
SafeHtml,
|
|
12
|
+
MessageFeedback,
|
|
13
|
+
MarkdownRenderer,
|
|
15
14
|
},
|
|
16
15
|
safeHtmlConfigExtension: {
|
|
17
16
|
ADD_TAGS: ['copy-code', 'insert-code-snippet'],
|
|
@@ -57,12 +56,13 @@ export default {
|
|
|
57
56
|
</div>
|
|
58
57
|
</div>
|
|
59
58
|
<slot name="message" v-bind="slotProps">
|
|
60
|
-
<
|
|
59
|
+
<markdown-renderer
|
|
61
60
|
ref="content"
|
|
62
|
-
|
|
61
|
+
:is-html="slotProps.isHtml"
|
|
62
|
+
:markdown="slotProps.content"
|
|
63
63
|
@insert-code-snippet="onInsertCodeSnippet"
|
|
64
64
|
@copy-code-snippet="onCopyCodeSnippet"
|
|
65
|
-
|
|
65
|
+
/>
|
|
66
66
|
</slot>
|
|
67
67
|
|
|
68
68
|
<message-feedback v-if="withFeedback" @feedback="$emit('feedback', $event)" />
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { GlIcon, GlTooltipDirective
|
|
3
|
-
|
|
4
|
-
import { renderDuoChatMarkdownPreview } from '../../../markdown_renderer';
|
|
2
|
+
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
|
3
|
+
import MarkdownRenderer from '../markdown_renderer.vue';
|
|
5
4
|
|
|
6
5
|
export default {
|
|
7
6
|
name: 'DuoBaseMessage',
|
|
8
7
|
components: {
|
|
9
8
|
GlIcon,
|
|
9
|
+
MarkdownRenderer,
|
|
10
10
|
},
|
|
11
11
|
directives: {
|
|
12
|
-
SafeHtml,
|
|
13
12
|
GlTooltip: GlTooltipDirective,
|
|
14
13
|
},
|
|
15
14
|
inject: {
|
|
@@ -23,10 +22,6 @@ export default {
|
|
|
23
22
|
element.classList.add('gl-markdown', 'gl-compact-markdown', 'gl-text-sm');
|
|
24
23
|
},
|
|
25
24
|
},
|
|
26
|
-
renderMarkdown: {
|
|
27
|
-
from: 'renderMarkdown',
|
|
28
|
-
default: () => renderDuoChatMarkdownPreview,
|
|
29
|
-
},
|
|
30
25
|
},
|
|
31
26
|
props: {
|
|
32
27
|
/**
|
|
@@ -41,11 +36,25 @@ export default {
|
|
|
41
36
|
},
|
|
42
37
|
},
|
|
43
38
|
computed: {
|
|
39
|
+
isDoneStreaming() {
|
|
40
|
+
// Agentic chat format
|
|
41
|
+
if (Object.hasOwn(this.message, 'status')) {
|
|
42
|
+
return this.message.status === 'success';
|
|
43
|
+
}
|
|
44
|
+
// Classic chat format
|
|
45
|
+
return !this.isChunk;
|
|
46
|
+
},
|
|
47
|
+
isChunk() {
|
|
48
|
+
return typeof this.message.chunkId === 'number';
|
|
49
|
+
},
|
|
50
|
+
hasContentHtml() {
|
|
51
|
+
return this.message?.contentHtml?.length > 0;
|
|
52
|
+
},
|
|
44
53
|
messageContent() {
|
|
45
|
-
return this.
|
|
54
|
+
return this.hasContentHtml ? this.message.contentHtml : this.message?.content;
|
|
46
55
|
},
|
|
47
56
|
renderedError() {
|
|
48
|
-
return this.
|
|
57
|
+
return this.message.errors?.join('; ') || '';
|
|
49
58
|
},
|
|
50
59
|
hasError() {
|
|
51
60
|
return Boolean(this.message?.errors?.length);
|
|
@@ -61,7 +70,7 @@ export default {
|
|
|
61
70
|
hydrateContentWithGFM() {
|
|
62
71
|
if (this.$refs.content) {
|
|
63
72
|
this.$nextTick(() => {
|
|
64
|
-
this.renderGFM(this.$refs.content, this.message.role);
|
|
73
|
+
this.renderGFM(this.$refs.content.$el, this.message.role);
|
|
65
74
|
});
|
|
66
75
|
}
|
|
67
76
|
},
|
|
@@ -73,6 +82,7 @@ export default {
|
|
|
73
82
|
class="duo-chat-message gl-w-full gl-flex-grow gl-leading-20 gl-break-anywhere"
|
|
74
83
|
:class="{
|
|
75
84
|
'gl-items-top gl-flex': hasError,
|
|
85
|
+
'duo-chat-message-complete': isDoneStreaming,
|
|
76
86
|
}"
|
|
77
87
|
>
|
|
78
88
|
<gl-icon
|
|
@@ -83,10 +93,10 @@ export default {
|
|
|
83
93
|
data-testid="error"
|
|
84
94
|
/>
|
|
85
95
|
<div ref="content-wrapper" :class="{ 'has-error': hasError }">
|
|
86
|
-
<
|
|
96
|
+
<markdown-renderer v-if="hasError" ref="error-message" :markdown="renderedError" />
|
|
87
97
|
<div v-else>
|
|
88
|
-
<slot name="message" v-bind="{ content:
|
|
89
|
-
<
|
|
98
|
+
<slot name="message" v-bind="{ content: messageContent, isHtml: hasContentHtml }">
|
|
99
|
+
<markdown-renderer ref="content" :is-html="hasContentHtml" :markdown="messageContent" />
|
|
90
100
|
</slot>
|
|
91
101
|
</div>
|
|
92
102
|
</div>
|
|
@@ -169,7 +169,7 @@ export default {
|
|
|
169
169
|
|
|
170
170
|
<gl-accordion v-if="message || description" class="-gl-ml-2" :header-level="3">
|
|
171
171
|
<gl-accordion-item v-if="description" class="gl-mb-3" :title="accordionTitle">
|
|
172
|
-
<pre-block
|
|
172
|
+
<pre-block :languages="['markdown']">{{ description }}</pre-block>
|
|
173
173
|
</gl-accordion-item>
|
|
174
174
|
<gl-accordion-item v-if="withJsonView" :title="$options.i18n.EXPAND_JSON_VIEW_TITLE">
|
|
175
175
|
<tool-params-json-view :tool-params="toolParams" />
|
|
@@ -70,6 +70,9 @@ export default {
|
|
|
70
70
|
return sprintf(this.$options.i18n.UNKNOWN_FILE_ACTION_LABEL, { filePath });
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
|
+
getActionFileExtension({ file_path: filePath }) {
|
|
74
|
+
return filePath.split('.').pop() || 'plaintext';
|
|
75
|
+
},
|
|
73
76
|
getActionContent({
|
|
74
77
|
action,
|
|
75
78
|
content,
|
|
@@ -168,7 +171,7 @@ export default {
|
|
|
168
171
|
class="gl-mb-3"
|
|
169
172
|
:title="$options.i18n.READ_COMMIT_MESSAGE"
|
|
170
173
|
>
|
|
171
|
-
<pre-block
|
|
174
|
+
<pre-block :languages="['markdown']">{{ commitMessage }}</pre-block>
|
|
172
175
|
</gl-accordion-item>
|
|
173
176
|
<gl-accordion-item v-if="actionsCount" :title="$options.i18n.EXPAND_CHANGES">
|
|
174
177
|
<gl-accordion :header-level="4">
|
|
@@ -178,7 +181,9 @@ export default {
|
|
|
178
181
|
class="gl-mb-3"
|
|
179
182
|
:title="getActionTitle(action)"
|
|
180
183
|
>
|
|
181
|
-
<pre-block
|
|
184
|
+
<pre-block :languages="[getActionFileExtension(action), 'diff']">{{
|
|
185
|
+
getActionContent(action)
|
|
186
|
+
}}</pre-block>
|
|
182
187
|
</gl-accordion-item>
|
|
183
188
|
</gl-accordion>
|
|
184
189
|
</gl-accordion-item>
|
package/src/components/chat/components/duo_chat_message_tool_approval/components/pre_block.vue
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { GlButton, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
|
|
3
3
|
import { translate } from '../../../../../utils/i18n';
|
|
4
|
+
import { highlightElement } from '../../../../../utils/highlight';
|
|
4
5
|
import { copyToClipboard } from '../../utils';
|
|
5
|
-
import { highlightElement } from '../services/highlight';
|
|
6
6
|
|
|
7
7
|
export default {
|
|
8
8
|
name: 'PreBlock',
|
|
@@ -13,10 +13,15 @@ export default {
|
|
|
13
13
|
GlButton,
|
|
14
14
|
},
|
|
15
15
|
props: {
|
|
16
|
-
|
|
17
|
-
type:
|
|
16
|
+
languages: {
|
|
17
|
+
type: Array,
|
|
18
18
|
required: false,
|
|
19
|
-
default:
|
|
19
|
+
default: () => [],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
computed: {
|
|
23
|
+
languageClasses() {
|
|
24
|
+
return this.languages.map((lang) => `language-${lang}`);
|
|
20
25
|
},
|
|
21
26
|
},
|
|
22
27
|
async mounted() {
|
|
@@ -30,7 +35,7 @@ export default {
|
|
|
30
35
|
const codeElement = this.$el.querySelector('code');
|
|
31
36
|
|
|
32
37
|
if (codeElement) {
|
|
33
|
-
highlightElement(codeElement);
|
|
38
|
+
highlightElement(codeElement, this.languages);
|
|
34
39
|
}
|
|
35
40
|
} catch (error) {
|
|
36
41
|
// Silently fail if highlight.js is not available
|
|
@@ -77,5 +82,5 @@ export default {
|
|
|
77
82
|
/>
|
|
78
83
|
</span>
|
|
79
84
|
|
|
80
|
-
<code ref="codeElement" :class="
|
|
85
|
+
<code ref="codeElement" :class="languageClasses" class="!gl-leading-20 gl-break-all"><slot></slot></code></pre>
|
|
81
86
|
</template>
|
|
@@ -32,7 +32,7 @@ export default {
|
|
|
32
32
|
<figcaption class="gl-text-subtle">
|
|
33
33
|
{{ $options.i18n.REQUEST_TEXT }}
|
|
34
34
|
</figcaption>
|
|
35
|
-
<pre-block v-if="hasToolParams"
|
|
35
|
+
<pre-block v-if="hasToolParams" :languages="['json']" data-testid="tool-parameters">{{
|
|
36
36
|
JSON.stringify(toolParams, null, 2)
|
|
37
37
|
}}</pre-block>
|
|
38
38
|
<span v-else class="gl-text-sm gl-text-gray-500" data-testid="no-parameters-message">
|
|
@@ -3,6 +3,7 @@ import { GlButton, GlAvatar } from '@gitlab/ui';
|
|
|
3
3
|
import { sprintf, translate } from '../../../../utils/i18n';
|
|
4
4
|
import { formatLocalizedDate } from '../../../../utils/date';
|
|
5
5
|
import DuoChatThreadsEmpty from './duo_chat_threads_empty.vue';
|
|
6
|
+
import DuoChatThreadsSkeleton from './duo_chat_threads_skeleton.vue';
|
|
6
7
|
|
|
7
8
|
const i18n = {
|
|
8
9
|
CHAT_HISTORY_INFO: translate(
|
|
@@ -21,6 +22,7 @@ export default {
|
|
|
21
22
|
GlButton,
|
|
22
23
|
GlAvatar,
|
|
23
24
|
DuoChatThreadsEmpty,
|
|
25
|
+
DuoChatThreadsSkeleton,
|
|
24
26
|
},
|
|
25
27
|
|
|
26
28
|
props: {
|
|
@@ -32,6 +34,11 @@ export default {
|
|
|
32
34
|
type: Array,
|
|
33
35
|
required: true,
|
|
34
36
|
},
|
|
37
|
+
loading: {
|
|
38
|
+
type: Boolean,
|
|
39
|
+
required: false,
|
|
40
|
+
default: false,
|
|
41
|
+
},
|
|
35
42
|
},
|
|
36
43
|
|
|
37
44
|
computed: {
|
|
@@ -93,7 +100,8 @@ export default {
|
|
|
93
100
|
|
|
94
101
|
<template>
|
|
95
102
|
<div class="gl-flex gl-flex-col gl-overflow-hidden">
|
|
96
|
-
<
|
|
103
|
+
<duo-chat-threads-skeleton v-if="loading" />
|
|
104
|
+
<template v-else-if="hasThreads">
|
|
97
105
|
<div class="gl-grow gl-overflow-y-scroll gl-px-4 gl-py-5">
|
|
98
106
|
<div v-for="(threadsForDate, date) in groupedThreads" :key="date" class="gl-mb-3">
|
|
99
107
|
<div
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { translate } from '../../../../utils/i18n';
|
|
3
|
+
|
|
4
|
+
const i18n = {
|
|
5
|
+
LOADING_THREADS: translate('DuoChat.loadingThreads', 'Loading chat history...'),
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'DuoChatThreadsSkeleton',
|
|
10
|
+
i18n,
|
|
11
|
+
};
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div class="gl-p-6">
|
|
16
|
+
<span class="gl-sr-only" role="status" aria-live="polite" aria-atomic="true">{{
|
|
17
|
+
$options.i18n.LOADING_THREADS
|
|
18
|
+
}}</span>
|
|
19
|
+
<div v-for="groupIndex in 3" :key="groupIndex" class="gl-mb-3">
|
|
20
|
+
<div
|
|
21
|
+
class="gl-animate-skeleton-loader gl-mb-3 gl-h-4 !gl-max-w-26 gl-rounded-default"
|
|
22
|
+
style="animation-delay: 0ms"
|
|
23
|
+
data-testid="chat-threads-skeleton-date-header"
|
|
24
|
+
></div>
|
|
25
|
+
<div
|
|
26
|
+
v-for="itemIndex in 2"
|
|
27
|
+
:key="`group${groupIndex}-item${itemIndex}`"
|
|
28
|
+
class="gl-mb-5 gl-flex gl-items-center gl-gap-3"
|
|
29
|
+
style="animation-delay: 0ms"
|
|
30
|
+
data-testid="chat-threads-skeleton-item"
|
|
31
|
+
>
|
|
32
|
+
<div class="gl-animate-skeleton-loader gl-h-8 gl-w-8 gl-shrink-0 gl-rounded-full"></div>
|
|
33
|
+
<div
|
|
34
|
+
class="gl-animate-skeleton-loader gl-h-5 !gl-max-w-48 gl-grow gl-rounded-default"
|
|
35
|
+
></div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
@@ -732,7 +732,7 @@ export default {
|
|
|
732
732
|
'resizable-content': shouldRenderResizable,
|
|
733
733
|
'duo-chat-drawer': !shouldRenderResizable,
|
|
734
734
|
}"
|
|
735
|
-
class="
|
|
735
|
+
class="duo-chat gl-bottom-0 gl-flex gl-max-h-full gl-flex-col"
|
|
736
736
|
role="complementary"
|
|
737
737
|
data-testid="chat-component"
|
|
738
738
|
>
|
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
// eslint-disable-next-line no-restricted-imports
|
|
2
|
-
import { Marked } from 'marked';
|
|
2
|
+
import { Marked, Renderer } from 'marked';
|
|
3
3
|
import markedBidi from 'marked-bidi';
|
|
4
|
+
import { markedHighlight } from 'marked-highlight';
|
|
4
5
|
import DOMPurify from 'dompurify';
|
|
6
|
+
import { highlightCode } from '../../utils/highlight';
|
|
5
7
|
|
|
8
|
+
const baseOptions = {
|
|
9
|
+
async: true,
|
|
10
|
+
breaks: false,
|
|
11
|
+
gfm: false,
|
|
12
|
+
};
|
|
13
|
+
const baseRenderer = new Renderer({ ...baseOptions });
|
|
14
|
+
const customCodeRenderer = {
|
|
15
|
+
code(...args) {
|
|
16
|
+
const baseCode = baseRenderer.code(...args);
|
|
17
|
+
|
|
18
|
+
// Insert copy-code elements inside the <pre> tag
|
|
19
|
+
return baseCode
|
|
20
|
+
.replace(
|
|
21
|
+
'<pre>',
|
|
22
|
+
'<div class="gl-relative gl-flex js-markdown-code duo-message-pre-block !focus:gl-focus" tabindex="0">\n<copy-code></copy-code>\n<insert-code-snippet></insert-code-snippet>\n<pre class="code code-syntax-highlight-theme duo-message-pre-block gl-grow gl-overflow-auto gl-grid">'
|
|
23
|
+
)
|
|
24
|
+
.replace('</pre>', '</pre></div>');
|
|
25
|
+
},
|
|
26
|
+
};
|
|
6
27
|
const duoMarked = new Marked([
|
|
7
28
|
{
|
|
8
|
-
|
|
9
|
-
breaks: false,
|
|
10
|
-
gfm: false,
|
|
29
|
+
...baseOptions,
|
|
11
30
|
},
|
|
12
31
|
markedBidi(),
|
|
13
32
|
]);
|
|
@@ -134,13 +153,13 @@ function isHtml(markup) {
|
|
|
134
153
|
return Array.from(doc.body.childNodes).some((n) => n.nodeType === Node.ELEMENT_NODE);
|
|
135
154
|
}
|
|
136
155
|
|
|
137
|
-
export function renderDuoChatMarkdownPreview(md, { trustedUrls = [] } = {}) {
|
|
156
|
+
export async function renderDuoChatMarkdownPreview(md, { trustedUrls = [] } = {}) {
|
|
138
157
|
if (!md) return '';
|
|
139
158
|
|
|
140
159
|
DOMPurify.addHook('beforeSanitizeElements', handleImageElements);
|
|
141
160
|
DOMPurify.addHook('afterSanitizeAttributes', sanitizeLinksHook(trustedUrls));
|
|
142
161
|
|
|
143
|
-
const parsedMarkdown = isHtml(md) ? md : duoMarked.parse(md.toString());
|
|
162
|
+
const parsedMarkdown = isHtml(md) ? md : await duoMarked.parse(md.toString());
|
|
144
163
|
|
|
145
164
|
const sanitized = DOMPurify.sanitize(parsedMarkdown, config);
|
|
146
165
|
|
|
@@ -149,6 +168,18 @@ export function renderDuoChatMarkdownPreview(md, { trustedUrls = [] } = {}) {
|
|
|
149
168
|
return sanitized;
|
|
150
169
|
}
|
|
151
170
|
|
|
171
|
+
duoMarked.use(
|
|
172
|
+
markedHighlight({
|
|
173
|
+
async: true,
|
|
174
|
+
langPrefix: 'hljs language-',
|
|
175
|
+
highlight(code, lang) {
|
|
176
|
+
return highlightCode(code, [lang]);
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
duoMarked.use({ renderer: customCodeRenderer });
|
|
182
|
+
|
|
152
183
|
export function addDuoMarkdownPlugin(plugin) {
|
|
153
184
|
duoMarked.use(plugin);
|
|
154
185
|
}
|