diff --git a/package.json b/package.json
index b3a561345be078bcf7f023769e419360a8aec06f..889785cb501778ed9c27ed5c6321d4a83ec6c023 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@gitlab/ui",
- "version": "88.0.0",
+ "version": "89.5.0",
"description": "GitLab UI Components",
"license": "MIT",
"main": "dist/index.js",
@@ -108,13 +108,14 @@
"@babel/core": "^7.25.2",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
- "@babel/preset-env": "^7.25.2",
+ "@babel/preset-env": "^7.25.3",
"@babel/preset-react": "^7.24.7",
"@cypress/grep": "^4.0.1",
- "@gitlab/eslint-plugin": "19.6.0",
+ "@faker-js/faker": "^8.4.1",
+ "@gitlab/eslint-plugin": "20.0.0",
"@gitlab/fonts": "^1.3.0",
"@gitlab/stylelint-config": "6.2.1",
- "@gitlab/svgs": "3.112.0",
+ "@gitlab/svgs": "3.114.0",
"@jest/test-sequencer": "^29.7.0",
"@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-node-resolve": "^7.1.3",
@@ -148,7 +149,7 @@
"babel-loader": "^8.0.5",
"bootstrap": "4.6.2",
"cobertura-merge": "^1.0.4",
- "cypress": "13.13.2",
+ "cypress": "13.13.3",
"cypress-axe": "^1.4.0",
"cypress-real-events": "^1.11.0",
"dompurify": "^3.1.2",
@@ -156,7 +157,7 @@
"esbuild": "^0.18.0",
"eslint": "8.57.0",
"eslint-import-resolver-jest": "3.0.2",
- "eslint-plugin-cypress": "3.4.0",
+ "eslint-plugin-cypress": "3.5.0",
"eslint-plugin-storybook": "0.8.0",
"gitlab-api-async-iterator": "^1.3.1",
"glob": "10.3.3",
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context.scss b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context.scss
new file mode 100644
index 0000000000000000000000000000000000000000..cb75998b94fd5740a6e9d8de2f1421858030bd55
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context.scss
@@ -0,0 +1,18 @@
+.gl-duo-chat-context-item-disabled {
+ &,
+ &:hover,
+ &:focus,
+ &:active {
+ background: var(--gl-control-background-color-disabled);
+ color: var(--gl-text-color-disabled);
+ cursor: not-allowed;
+ }
+
+ span {
+ color: var(--gl-text-color-disabled);
+ }
+
+ .gl-icon {
+ color: var(--gl-text-color-disabled);
+ }
+}
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_event_bus.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_event_bus.js
new file mode 100644
index 0000000000000000000000000000000000000000..e962714e1bc7be4d903982acc4c05a388ba4fd1e
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_event_bus.js
@@ -0,0 +1,8 @@
+export const EVENT_BUS_TYPES = {
+ TOGGLE_CONTEXT_MENU: 'toggle_context_menu',
+ CONTEXT_ITEM_SEARCH_QUERY: 'context_item_search_query',
+ CONTEXT_ITEM_SEARCH_RESULT: 'context_item_search_result',
+ CONTEXT_ITEM_SEARCH_ERROR: 'context_item_search_error',
+ CONTEXT_ITEM_ADDED: 'context_item_added',
+ CONTEXT_ITEM_REMOVED: 'context_item_removed',
+};
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.stories.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.stories.js
new file mode 100644
index 0000000000000000000000000000000000000000..122086b5058e19132c7b81a8a66068faa3c29f7a
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.stories.js
@@ -0,0 +1,118 @@
+import Vue from 'vue';
+import { categories, generateSampleContextItems } from '../duo_chat_context_items_sample_data';
+import { setStoryTimeout } from '../../../../../../../utils/test_utils';
+import { EVENT_BUS_TYPES } from '../duo_chat_context_event_bus';
+import GlDuoChatContextItemMenu from './duo_chat_context_item_menu.vue';
+
+const eventBus = new Vue();
+const sampleCategories = categories;
+const sampleContextItems = generateSampleContextItems();
+
+export default {
+ title: 'experimental/duo/chat/components/duo-chat-context/duo-chat-context-item-menu',
+ component: GlDuoChatContextItemMenu,
+ argTypes: {
+ cursorPosition: { control: { type: 'number', min: 0, max: 1000, step: 10 } },
+ },
+};
+
+const Template = (args, { argTypes }) => ({
+ props: Object.keys(argTypes),
+ components: { GlDuoChatContextItemMenu },
+ data() {
+ return {
+ localEventBus: eventBus,
+ contextCategories: sampleCategories,
+ contextItems: sampleContextItems,
+ addedItemJson: null,
+ };
+ },
+ mounted() {
+ this.localEventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_SEARCH_QUERY, this.handleSearch);
+ this.localEventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_ADDED, this.handleItemAdded);
+ },
+ beforeDestroy() {
+ this.localEventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_SEARCH_QUERY, this.handleSearch);
+ this.localEventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_ADDED, this.handleItemAdded);
+ },
+ methods: {
+ handleSearch({ category, query }) {
+ const filteredResults = this.contextItems
+ .filter((item) => item.type === category)
+ .filter((item) => item.name.toLowerCase().includes(query.toLowerCase()));
+ setStoryTimeout(() => {
+ this.localEventBus.$emit(EVENT_BUS_TYPES.CONTEXT_ITEM_SEARCH_RESULT, filteredResults);
+ }, 300);
+ },
+ handleItemAdded(item) {
+ this.addedItemJson = JSON.stringify(item, null, 2);
+ },
+ toggleMenu() {
+ this.localEventBus.$emit(EVENT_BUS_TYPES.TOGGLE_CONTEXT_MENU, true);
+ },
+ },
+ template: `
+
+
+
+
{{ addedItemJson }}
+
+ `,
+});
+
+export const Default = Template.bind({});
+Default.args = {
+ cursorPosition: 100,
+};
+
+const errorEventBus = new Vue();
+
+// create an error template
+const ErrorTemplate = (args, { argTypes }) => ({
+ props: Object.keys(argTypes),
+ components: { GlDuoChatContextItemMenu },
+ data() {
+ return {
+ localEventBus: errorEventBus,
+ contextCategories: sampleCategories,
+ };
+ },
+ mounted() {
+ this.localEventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_SEARCH_QUERY, this.handleSearch);
+ this.localEventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_ADDED, this.handleItemAdded);
+ },
+ beforeDestroy() {
+ this.localEventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_SEARCH_QUERY, this.handleSearch);
+ this.localEventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_ADDED, this.handleItemAdded);
+ },
+ methods: {
+ handleSearch() {
+ setStoryTimeout(() => {
+ this.localEventBus.$emit(EVENT_BUS_TYPES.CONTEXT_ITEM_SEARCH_ERROR, args.searchError);
+ }, 300);
+ },
+ toggleMenu() {
+ this.localEventBus.$emit(EVENT_BUS_TYPES.TOGGLE_CONTEXT_MENU, true);
+ },
+ },
+ template: `
+
+
+
+
+ `,
+});
+
+export const WithError = ErrorTemplate.bind({});
+WithError.args = {
+ cursorPosition: 100,
+ searchError: 'Something went wrong',
+};
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.vue b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2dfdb346973da66013b7c2e7c2b33747dd1bf839
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.vue
@@ -0,0 +1,319 @@
+
+
+
+
+
+
+ -
+
+
+
+ {{ category.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.stories.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.stories.js
new file mode 100644
index 0000000000000000000000000000000000000000..939eb71d80ab69cf3cdacbc88070c74c95d17ba9
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.stories.js
@@ -0,0 +1,46 @@
+import { generateSampleContextItems } from '../duo_chat_context_items_sample_data';
+import GlDuoChatContextItemPopover from './duo_chat_context_item_popover.vue';
+
+const sampleContextItems = generateSampleContextItems(10);
+
+export default {
+ title: 'experimental/duo/chat/components/duo-chat-context/duo-chat-context-item-popover',
+ component: GlDuoChatContextItemPopover,
+ argTypes: {
+ placement: {
+ control: { type: 'select', options: ['top', 'right', 'bottom', 'left'] },
+ },
+ },
+};
+
+const Template = (args, { argTypes }) => ({
+ props: Object.keys(argTypes),
+ components: { GlDuoChatContextItemPopover },
+ data() {
+ return {
+ items: sampleContextItems,
+ };
+ },
+ template: `
+
+
+
+
+
+
+
+ `,
+});
+
+export const Default = Template.bind({});
+Default.args = {
+ placement: 'right',
+};
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.vue b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.vue
new file mode 100644
index 0000000000000000000000000000000000000000..78e034a8926e15de3dae4962fe3caaaf99ae3b30
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.vue
@@ -0,0 +1,86 @@
+
+
+
+
+ {{ popoverData.name }}
+
+
+
+ Project:
+ {{ popoverData.project }}
+
+
+ Path:
+ {{ popoverData.filePath }}
+
+
+ ID:
+ {{ popoverData.idPrefix }}{{ popoverData.id }}
+
+
+ Type:
+ {{ popoverData.type }}
+
+
+ Note:
+ {{ popoverData.disabledMessage }}
+
+
+
+
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.stories.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.stories.js
new file mode 100644
index 0000000000000000000000000000000000000000..836188c287d56fdc66ab8fe433134032c340712b
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.stories.js
@@ -0,0 +1,91 @@
+import Vue from 'vue';
+import { generateSampleContextItems } from '../duo_chat_context_items_sample_data';
+import { EVENT_BUS_TYPES } from '../duo_chat_context_event_bus';
+import GlDuoChatContextItemSelections from './duo_chat_context_item_selections.vue';
+
+const eventBus = new Vue();
+const sampleContextItems = generateSampleContextItems(5);
+
+export default {
+ title: 'experimental/duo/chat/components/duo-chat-context/duo-chat-context-item-selections',
+ component: GlDuoChatContextItemSelections,
+ argTypes: {
+ title: { control: 'text' },
+ collapsable: { control: 'boolean' },
+ showClose: { control: 'boolean' },
+ },
+};
+
+const Template = (args, { argTypes }) => ({
+ props: Object.keys(argTypes),
+ components: { GlDuoChatContextItemSelections },
+ data() {
+ return {
+ localEventBus: eventBus,
+ itemSelections: [...sampleContextItems],
+ };
+ },
+ mounted() {
+ this.localEventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_REMOVED, this.handleRemoveItem);
+ },
+ beforeDestroy() {
+ this.localEventBus.$off(EVENT_BUS_TYPES.CONTEXT_ITEM_REMOVED, this.handleRemoveItem);
+ },
+ methods: {
+ handleRemoveItem(item) {
+ const index = this.itemSelections.findIndex((i) => i.id === item.id);
+ if (index !== -1) {
+ this.itemSelections.splice(index, 1);
+ }
+ },
+ },
+ template: `
+
+
+
+ `,
+});
+
+export const Default = Template.bind({});
+Default.args = {
+ title: 'Added Context',
+ collapsable: false,
+ showClose: true,
+};
+
+export const Collapsable = Template.bind({});
+Collapsable.args = {
+ ...Default.args,
+ collapsable: true,
+};
+
+export const NoCloseButton = Template.bind({});
+NoCloseButton.args = {
+ ...Default.args,
+ showClose: false,
+};
+
+export const CustomTitle = Template.bind({});
+CustomTitle.args = {
+ ...Default.args,
+ title: 'Selected Items',
+};
+
+export const EmptySelection = Template.bind({});
+EmptySelection.args = {
+ ...Default.args,
+};
+EmptySelection.decorators = [
+ () => ({
+ template: '',
+ data() {
+ return {
+ contextItemSelections: [],
+ };
+ },
+ }),
+];
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue
new file mode 100644
index 0000000000000000000000000000000000000000..370d5cca042449d957cc822393830c2ae5db4176
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_items_sample_data.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_items_sample_data.js
new file mode 100644
index 0000000000000000000000000000000000000000..964a2b336976c18c4b843cd8090284fdb6913632
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_items_sample_data.js
@@ -0,0 +1,65 @@
+import { faker } from '@faker-js/faker';
+
+export const categories = [
+ { label: 'Files', value: 'file', icon: 'document' },
+ { label: 'Issues', value: 'issue', icon: 'issues' },
+ { label: 'Merge Requests', value: 'merge_request', icon: 'merge-request' },
+];
+
+const generateFile = () => ({
+ id: faker.string.uuid(),
+ name: faker.system.fileName({ extensionCount: { min: 1, max: 3 } }),
+ isEnabled: faker.datatype.boolean(),
+ info: {
+ project: `${faker.internet.domainWord()}/${faker.internet.domainWord()}`,
+ relFilePath: faker.system.filePath(),
+ },
+ type: 'file',
+ subType: faker.helpers.arrayElement(['open_tab', 'local_file_search']),
+});
+
+const generateIssue = () => ({
+ id: faker.string.uuid(),
+ name: faker.hacker.phrase(),
+ isEnabled: faker.datatype.boolean(),
+ info: {
+ project: `${faker.internet.domainWord()}/${faker.internet.domainWord()}`,
+ iid: faker.number.int({ min: 1000, max: 9999 }),
+ disabledReasons: faker.datatype.boolean()
+ ? [faker.lorem.sentence(), faker.lorem.sentence()]
+ : [],
+ },
+ type: 'issue',
+});
+
+const generateMergeRequest = () => ({
+ id: faker.string.uuid(),
+ name: faker.git.commitMessage(),
+ isEnabled: faker.datatype.boolean(),
+ info: {
+ project: `${faker.internet.domainWord()}/${faker.internet.domainWord()}`,
+ iid: faker.number.int({ min: 1000, max: 9999 }),
+ },
+ type: 'merge_request',
+});
+
+export const generateSampleContextItems = (count = 100) => {
+ const items = Array.from({ length: count }, () => {
+ const type = faker.helpers.arrayElement(['file', 'issue', 'merge_request']);
+ switch (type) {
+ case 'file':
+ return generateFile();
+ case 'issue':
+ return generateIssue();
+ case 'merge_request':
+ return generateMergeRequest();
+ default:
+ throw new Error(`Unknown type: ${type}`);
+ }
+ });
+
+ // put disabled items in the back
+ const disabledItems = items.filter((item) => !item.isEnabled);
+ const enabledItems = items.filter((item) => item.isEnabled);
+ return [...enabledItems, ...disabledItems];
+};
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_types.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_types.js
new file mode 100644
index 0000000000000000000000000000000000000000..32ce56b84f6a838910609fff88ef36b3cc4f3854
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_types.js
@@ -0,0 +1,32 @@
+/**
+ * @typedef {Object} AiContextItemInfo
+ * @property {string} [project] - The project associated with the context item
+ * @property {string[]} [disabledReasons] - Reasons why the context item might be disabled
+ * @property {number} [iid] - Internal ID of the context item
+ * @property {string} [relFilePath] - Relative file path of the context item
+ */
+
+/**
+ * @typedef {'issue' | 'merge_request' | 'file'} AiContextItemType
+ */
+
+/**
+ * @typedef {'open_tab' | 'local_file_search'} AiContextItemSubType
+ */
+
+/**
+ * @typedef {Object} AiContextItemBase
+ * @property {string} id - Unique identifier for the context item
+ * @property {string} name - Name of the context item
+ * @property {boolean} isEnabled - Whether the context item is enabled
+ * @property {AiContextItemInfo} info - Additional information about the context item
+ * @property {AiContextItemType} type - Type of the context item
+ */
+
+/**
+ * @typedef {AiContextItemBase & ({type: 'issue' | 'merge_request', subType?: never} | {type: 'file', subType: AiContextItemSubType})} AiContextItem
+ */
+
+/**
+ * @typedef {AiContextItem & {content: string}} AiContextItemWithContent
+ */
diff --git a/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.vue b/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.vue
index 01c45a9d464c5c31710a02907241e99d405764c7..cbc1273d27ead31efc3c751d0878268b65fa2ce0 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.vue
+++ b/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.vue
@@ -77,6 +77,7 @@ export default {
v-for="(msg, index) in messages"
:key="`${msg.role}-${index}`"
:message="msg"
+ :message-index="index"
:is-cancelled="canceledRequestIds.includes(msg.requestId)"
@track-feedback="onTrackFeedback"
@insert-code-snippet="onInsertCodeSnippet"
diff --git a/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.vue b/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.vue
index 3eaaccfd4ecc0c46d86c4fb8a3ead165b4becb63..983f1f23400da48386894c8c244edc6daa34eabe 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.vue
+++ b/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.vue
@@ -10,6 +10,7 @@ import { MESSAGE_MODEL_ROLES } from '../../constants';
import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_sources.vue';
// eslint-disable-next-line no-restricted-imports
import { renderDuoChatMarkdownPreview } from '../../markdown_renderer';
+import GlDuoChatContextItemSelections from '../duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue';
import { CopyCodeElement } from './copy_code_element';
import { InsertCodeSnippetElement } from './insert_code_snippet_element';
import { concatUntilEmpty } from './utils';
@@ -40,6 +41,7 @@ export default {
GlFormTextarea,
GlIcon,
GlLoadingIcon,
+ GlDuoChatContextItemSelections,
},
directives: {
SafeHtml,
@@ -79,6 +81,10 @@ export default {
type: Boolean,
required: true,
},
+ messageIndex: {
+ type: Number,
+ required: true,
+ },
},
data() {
return {
@@ -130,6 +136,25 @@ export default {
error() {
return Boolean(this.message?.errors?.length) && this.message.errors.join('; ');
},
+ selectedContextItems() {
+ return this.message.extras?.contextItems || [];
+ },
+ displaySelectedContextItems() {
+ return this.message.extras && this.message.extras.contextItems;
+ },
+ showSelectedContextItemCollapsible() {
+ return this.messageIndex === 1; // Make the second message (index 1) collapsible
+ },
+ selectedContextItemTitle() {
+ if (!this.displaySelectedContextItems) return '';
+ const count = this.selectedContextItems.length;
+
+ if (this.messageIndex === 0) {
+ return `added context`;
+ }
+
+ return `used ${count} reference${count !== 1 ? 's' : ''}`;
+ },
},
beforeCreate() {
if (!customElements.get('copy-code')) {
@@ -213,6 +238,14 @@ export default {
data-testid="error"
/>
+
typeof GlDuoChat.props[prop].default === 'function'
@@ -79,6 +86,10 @@ Default.args = generateProps({
});
Default.decorators = [makeContainer({ height: '800px' })];
+const contextItemMenuEventBus = new Vue();
+
+let previousSelectedContextItems = [];
+
export const Interactive = (args, { argTypes }) => ({
components: { GlDuoChat, GlButton },
props: Object.keys(argTypes),
@@ -95,15 +106,30 @@ export const Interactive = (args, { argTypes }) => ({
timeout: null,
requestId: 1,
canceledMessageRequestIds: [],
+ sampleContextItems: [],
+ eventBus: contextItemMenuEventBus,
+ sampleContextCategories: categories,
};
},
+ mounted() {
+ this.eventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_ADDED, this.onAddSelectedItem);
+ this.eventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_REMOVED, this.onRemoveSelectedItem);
+ this.eventBus.$on(EVENT_BUS_TYPES.CONTEXT_ITEM_SEARCH_QUERY, this.handleMockSearch);
+ },
+ beforeDestroy() {
+ this.eventBus.$off(EVENT_BUS_TYPES.CONTEXT_ITEM_ADDED, this.onAddSelectedItem);
+ this.eventBus.$off(EVENT_BUS_TYPES.CONTEXT_ITEM_REMOVED, this.onRemoveSelectedItem);
+ this.eventBus.$off(EVENT_BUS_TYPES.CONTEXT_ITEM_SEARCH_QUERY, this.handleMockSearch);
+ },
methods: {
onSendChatPrompt(prompt) {
+ previousSelectedContextItems = [...this.sampleContextItems];
const newPrompt = {
...MOCK_USER_PROMPT_MESSAGE,
contentHtml: '',
content: prompt,
requestId: this.requestId,
+ extras: { contextItems: previousSelectedContextItems },
};
this.loggerInfo += `New prompt: ${JSON.stringify(newPrompt)}\n\n`;
if ([CHAT_CLEAN_MESSAGE, CHAT_CLEAR_MESSAGE].includes(prompt)) {
@@ -112,6 +138,15 @@ export const Interactive = (args, { argTypes }) => ({
this.msgs.push(newPrompt);
this.promptInFlight = true;
}
+ this.sampleContextItems = [];
+ },
+ onAddSelectedItem(item) {
+ this.sampleContextItems = [...this.sampleContextItems, item];
+ this.loggerInfo += `Added selected item: ${JSON.stringify(item)}\n\n`;
+ },
+ onRemoveSelectedItem(item) {
+ this.sampleContextItems = this.sampleContextItems.filter((i) => i.id !== item.id);
+ this.loggerInfo += `Removed selected item: ${JSON.stringify(item)}\n\n`;
},
onChatHidden() {
this.isHidden = true;
@@ -133,8 +168,8 @@ export const Interactive = (args, { argTypes }) => ({
},
async mockResponseFromAi() {
const generator = generateMockResponseChunks(this.requestId);
-
for await (const newResponse of generator) {
+ newResponse.extras.contextItems = previousSelectedContextItems;
if (!this.canceledMessageRequestIds.includes(newResponse.requestId)) {
const existingMessageIndex = this.msgs.findIndex(
(msg) => msg.requestId === newResponse.requestId && msg.role === newResponse.role
@@ -159,39 +194,52 @@ export const Interactive = (args, { argTypes }) => ({
...newResponse,
});
},
+
+ async handleMockSearch({ category, query }) {
+ const mockData = generateSampleContextItems(30);
+ const filteredResults = mockData.filter(
+ (item) => item.type === category && item.name.toLowerCase().includes(query.toLowerCase())
+ );
+ this.loggerInfo += `Search results for ${category} - "${query}": ${JSON.stringify(filteredResults)}\n\n`;
+
+ // Simulate an async operation
+ await new Promise((resolve) => {
+ setStoryTimeout(resolve, 100);
+ });
+
+ this.eventBus.$emit(EVENT_BUS_TYPES.CONTEXT_ITEM_SEARCH_RESULT, filteredResults);
+ },
},
template: `
-
-
-
-{{ loggerInfo }}
-
-
Mock the response
+
+ Mock the response
+
-
Show chat
-
-
`,
+ `,
});
Interactive.args = generateProps({});
diff --git a/src/components/experimental/duo/chat/duo_chat.vue b/src/components/experimental/duo/chat/duo_chat.vue
index 1dc3cdba137418a8a0114abdbeea8ca347cd0336..e5efdfaa034c94ba128bbb9edc54d9ca2926bd9b 100644
--- a/src/components/experimental/duo/chat/duo_chat.vue
+++ b/src/components/experimental/duo/chat/duo_chat.vue
@@ -16,6 +16,9 @@ import { SafeHtmlDirective as SafeHtml } from '../../../../directives/safe_html/
import GlDuoChatLoader from './components/duo_chat_loader/duo_chat_loader.vue';
import GlDuoChatPredefinedPrompts from './components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue';
import GlDuoChatConversation from './components/duo_chat_conversation/duo_chat_conversation.vue';
+import GlDuoChatContextItemMenu from './components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.vue';
+import GlDuoChatContextItemSelections from './components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue';
+import { EVENT_BUS_TYPES } from './components/duo_chat_context/duo_chat_context_event_bus';
import { CHAT_CLEAN_MESSAGE, CHAT_RESET_MESSAGE, CHAT_CLEAR_MESSAGE } from './constants';
export const i18n = {
@@ -62,6 +65,8 @@ export default {
GlDuoChatConversation,
GlCard,
GlDropdownItem,
+ GlDuoChatContextItemMenu,
+ GlDuoChatContextItemSelections,
},
directives: {
SafeHtml,
@@ -198,6 +203,37 @@ export default {
required: false,
default: '',
},
+
+ /**
+ * Additional Context Menu Item Event Bus
+ */
+ contextItemMenuEventBus: {
+ type: Object,
+ required: false,
+ default: () => ({
+ $on: () => {},
+ $off: () => {},
+ $emit: () => {},
+ }),
+ },
+
+ /**
+ * Items selected by Additional Context Menu
+ */
+ contextItemSelections: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+
+ /**
+ * Categories to enable in the Additional Context Menu
+ */
+ contextCategories: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
return {
@@ -207,6 +243,8 @@ export default {
activeCommandIndex: 0,
displaySubmitButton: true,
compositionJustEnded: false,
+ contextMenuOpen: false,
+ cursorPosition: 0,
};
},
computed: {
@@ -290,10 +328,17 @@ export default {
},
created() {
this.handleScrollingTrottled = throttle(this.handleScrolling, 200); // Assume a 200ms throttle for example
+ this.contextItemMenuEventBus.$on(
+ EVENT_BUS_TYPES.CONTEXT_ITEM_ADDED,
+ this.handleContextItemAdded
+ );
},
mounted() {
this.scrollToBottom();
},
+ beforeDestroy() {
+ document.removeEventListener('keydown', this.handleContextMenuKeydown);
+ },
methods: {
compositionEnd() {
this.compositionJustEnded = true;
@@ -412,11 +457,42 @@ export default {
this.sendChatPrompt();
} else {
this.setPromptAndFocus(`${command.name} `);
+ if (command.name === '/include' && this.contextCategories.length > 0) {
+ this.showContextItemMenu(true);
+ }
}
},
onInsertCodeSnippet(e) {
this.$emit('insert-code-snippet', e);
},
+
+ async showContextItemMenu(show = true) {
+ this.contextMenuOpen = show;
+
+ if (show) {
+ await this.$nextTick();
+ this.$refs.prompt.$el.blur();
+ document.addEventListener('keydown', this.handleContextMenuKeydown);
+ await this.$nextTick();
+ this.$refs.contextItemMenu.focusSearchInput();
+ } else {
+ document.removeEventListener('keydown', this.handleContextMenuKeydown);
+ this.$refs.prompt.$el.focus();
+ }
+ },
+
+ async handleContextMenuKeydown(event) {
+ if (this.contextMenuOpen) {
+ this.$refs.contextItemMenu.handleKeydown(event);
+ }
+ },
+
+ async handleContextItemAdded() {
+ this.prompt = this.prompt.replace('/include', '').trim();
+ await this.$nextTick();
+ this.$refs.prompt.$el.focus();
+ this.contextMenuOpen = false;
+ },
},
i18n,
emptySvg,
@@ -532,6 +608,10 @@ export default {
:class="{ 'duo-chat-drawer-body-scrim-on-footer': !scrolledToBottom }"
>
+
-
+
= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@@ -13750,7 +13818,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0:
+semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.1:
version "7.6.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
@@ -13813,24 +13881,27 @@ set-blocking@^2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
-set-function-length@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed"
- integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==
+set-function-length@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
+ integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
dependencies:
- define-data-property "^1.1.1"
- get-intrinsic "^1.2.1"
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
gopd "^1.0.1"
- has-property-descriptors "^1.0.0"
+ has-property-descriptors "^1.0.2"
-set-function-name@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a"
- integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==
+set-function-name@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985"
+ integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==
dependencies:
- define-data-property "^1.0.1"
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
functions-have-names "^1.2.3"
- has-property-descriptors "^1.0.0"
+ has-property-descriptors "^1.0.2"
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
@@ -14304,32 +14375,33 @@ string.prototype.padend@^3.0.0:
es-abstract "^1.4.3"
function-bind "^1.0.2"
-string.prototype.trim@^1.2.8:
- version "1.2.8"
- resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd"
- integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==
+string.prototype.trim@^1.2.9:
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4"
+ integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==
dependencies:
- call-bind "^1.0.2"
- define-properties "^1.2.0"
- es-abstract "^1.22.1"
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.0"
+ es-object-atoms "^1.0.0"
-string.prototype.trimend@^1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e"
- integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==
+string.prototype.trimend@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229"
+ integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==
dependencies:
- call-bind "^1.0.2"
- define-properties "^1.2.0"
- es-abstract "^1.22.1"
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
-string.prototype.trimstart@^1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298"
- integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==
+string.prototype.trimstart@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde"
+ integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==
dependencies:
- call-bind "^1.0.2"
- define-properties "^1.2.0"
- es-abstract "^1.22.1"
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
string_decoder@^1.1.1, string_decoder@~1.1.1:
version "1.1.1"
@@ -14985,10 +15057,10 @@ ts-map@^1.0.3:
resolved "https://registry.yarnpkg.com/ts-map/-/ts-map-1.0.3.tgz#1c4d218dec813d2103b7e04e4bcf348e1471c1ff"
integrity sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==
-tsconfig-paths@^3.14.2:
- version "3.14.2"
- resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088"
- integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==
+tsconfig-paths@^3.15.0:
+ version "3.15.0"
+ resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
+ integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==
dependencies:
"@types/json5" "^0.0.29"
json5 "^1.0.2"
@@ -15106,44 +15178,49 @@ type-is@^1.6.16, type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
-typed-array-buffer@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60"
- integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==
+typed-array-buffer@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3"
+ integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==
dependencies:
- call-bind "^1.0.2"
- get-intrinsic "^1.2.1"
- is-typed-array "^1.1.10"
+ call-bind "^1.0.7"
+ es-errors "^1.3.0"
+ is-typed-array "^1.1.13"
-typed-array-byte-length@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0"
- integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==
+typed-array-byte-length@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67"
+ integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==
dependencies:
- call-bind "^1.0.2"
+ call-bind "^1.0.7"
for-each "^0.3.3"
- has-proto "^1.0.1"
- is-typed-array "^1.1.10"
+ gopd "^1.0.1"
+ has-proto "^1.0.3"
+ is-typed-array "^1.1.13"
-typed-array-byte-offset@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b"
- integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==
+typed-array-byte-offset@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063"
+ integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==
dependencies:
- available-typed-arrays "^1.0.5"
- call-bind "^1.0.2"
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.7"
for-each "^0.3.3"
- has-proto "^1.0.1"
- is-typed-array "^1.1.10"
+ gopd "^1.0.1"
+ has-proto "^1.0.3"
+ is-typed-array "^1.1.13"
-typed-array-length@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb"
- integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==
+typed-array-length@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3"
+ integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==
dependencies:
- call-bind "^1.0.2"
+ call-bind "^1.0.7"
for-each "^0.3.3"
- is-typed-array "^1.1.9"
+ gopd "^1.0.1"
+ has-proto "^1.0.3"
+ is-typed-array "^1.1.13"
+ possible-typed-array-names "^1.0.0"
typedarray-to-buffer@^3.1.5:
version "3.1.5"
@@ -15515,10 +15592,10 @@ vue-docgen-loader@^1.5.1:
loader-utils "^1.2.3"
querystring "^0.2.0"
-vue-eslint-parser@^9.3.1:
- version "9.3.2"
- resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.3.2.tgz#6f9638e55703f1c77875a19026347548d93fd499"
- integrity sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg==
+vue-eslint-parser@^9.4.3:
+ version "9.4.3"
+ resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz#9b04b22c71401f1e8bca9be7c3e3416a4bde76a8"
+ integrity sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==
dependencies:
debug "^4.3.4"
eslint-scope "^7.1.1"
@@ -15803,16 +15880,16 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
-which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.2:
- version "1.1.13"
- resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36"
- integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==
+which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.2:
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d"
+ integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==
dependencies:
- available-typed-arrays "^1.0.5"
- call-bind "^1.0.4"
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.7"
for-each "^0.3.3"
gopd "^1.0.1"
- has-tostringtag "^1.0.0"
+ has-tostringtag "^1.0.2"
which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.1:
version "1.3.1"