-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0c59fc9
commit c364d7b
Showing
22 changed files
with
715 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
packages/x-components/src/__stubs__/related-prompts-stubs.factory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { RelatedPrompt } from '@empathyco/x-types'; | ||
|
||
/** | ||
* Creates a {@link @empathyco/x-types#RelatedPrompt | related prompts} stub. | ||
* | ||
* @param amount - Number of stubbed related prompts to create. | ||
* | ||
* @returns Array of related prompts stub. | ||
* | ||
* @internal | ||
*/ | ||
export function getRelatedPromptsStub(amount = 12): RelatedPrompt[] { | ||
return Array.from({ length: amount }, (_, index) => | ||
createRelatedPromptStub(`Related Prompt ${index + 1}`) | ||
); | ||
} | ||
|
||
/** | ||
* Creates a related prompt stub with the provided options. | ||
* | ||
* @param suggestionText - The suggested text of the related prompt. | ||
* | ||
* @returns A related prompt. | ||
*/ | ||
export function createRelatedPromptStub(suggestionText: string): RelatedPrompt { | ||
return { | ||
suggestionText, | ||
nextQueries: createNextQueriesArrayStub(10), | ||
modelName: 'RelatedPrompt', | ||
type: Math.random() < 0.5 ? 'CURATED' : 'SYNTHETIC' | ||
}; | ||
} | ||
|
||
/** | ||
* Creates an array of next queries. | ||
* | ||
* @param amount - Number of next queries to create. | ||
* | ||
* @returns Array of next queries. | ||
*/ | ||
function createNextQueriesArrayStub(amount: number): string[] { | ||
return Array.from({ length: amount }, (_, index) => `Next query ${index + 1}`); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
packages/x-components/src/x-modules/related-prompts/components/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default as RelatedPrompt } from './related-prompt.vue'; | ||
export { default as RelatedPromptsList } from './related-prompts-list.vue'; |
80 changes: 80 additions & 0 deletions
80
packages/x-components/src/x-modules/related-prompts/components/related-prompt.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<template> | ||
<div | ||
class="x-related-prompt x-bg-neutral-10 x-p-24 x-flex x-flex-col x-gap-16" | ||
data-test="related-prompt" | ||
> | ||
<div class="x-related-prompt__info x-flex x-flex-col x-gap-16"> | ||
<slot name="header" :suggestionText="relatedPrompt.suggestionText"> | ||
{{ relatedPrompt.suggestionText }} | ||
</slot> | ||
<slot name="next-queries" :nextQueries="relatedPrompt.nextQueries"> | ||
<SlidingPanel :resetOnContentChange="false"> | ||
<div class="x-flex x-gap-8 x-pr-8"> | ||
<button | ||
v-for="(nextQuery, index) in relatedPrompt.nextQueries" | ||
:key="index" | ||
@click="onClick(nextQuery)" | ||
class="x-button x-button-lead x-button-sm x-button-outlined x-rounded-sm x-border-lead-50 x-text-neutral-75 hover:x-text-neutral-0 selected:x-text-neutral-0 selected:hover:x-bg-lead-50" | ||
:class="{ 'x-selected': selectedNextQuery === nextQuery }" | ||
> | ||
<span | ||
class="x-whitespace-nowrap" | ||
:class=" | ||
selectedNextQuery === nextQuery ? 'x-title3 x-title3-md' : 'x-text1 x-text1-lg' | ||
" | ||
> | ||
{{ nextQuery }} | ||
</span> | ||
<CrossTinyIcon v-if="selectedNextQuery === nextQuery" class="x-icon" /> | ||
<PlusIcon v-else class="x-icon" /> | ||
</button> | ||
</div> | ||
</SlidingPanel> | ||
</slot> | ||
</div> | ||
|
||
<div class="x-related-prompt__query-preview"> | ||
<slot name="selected-query" :selectedQuery="selectedNextQuery"> | ||
{{ selectedNextQuery }} | ||
</slot> | ||
</div> | ||
</div> | ||
</template> | ||
<script lang="ts"> | ||
import { defineComponent, PropType, ref } from 'vue'; | ||
import { RelatedPrompt } from '@empathyco/x-types'; | ||
import { relatedPromptsXModule } from '../x-module'; | ||
import CrossTinyIcon from '../../../components/icons/cross-tiny.vue'; | ||
import PlusIcon from '../../../components/icons/plus.vue'; | ||
import SlidingPanel from '../../../components/sliding-panel.vue'; | ||
export default defineComponent({ | ||
name: 'RelatedPrompt', | ||
components: { | ||
SlidingPanel, | ||
CrossTinyIcon, | ||
PlusIcon | ||
}, | ||
xModule: relatedPromptsXModule.name, | ||
props: { | ||
relatedPrompt: { type: Object as PropType<RelatedPrompt>, required: true } | ||
}, | ||
setup(props) { | ||
const selectedNextQuery = ref(props.relatedPrompt.nextQueries[0]); | ||
/** | ||
* Handles the click event on a next query button. | ||
* | ||
* @param nextQuery - The clicked next query. | ||
*/ | ||
function onClick(nextQuery: string): void { | ||
if (selectedNextQuery.value === nextQuery) { | ||
selectedNextQuery.value = ''; | ||
} else { | ||
selectedNextQuery.value = nextQuery; | ||
} | ||
} | ||
return { selectedNextQuery, onClick }; | ||
} | ||
}); | ||
</script> |
185 changes: 185 additions & 0 deletions
185
packages/x-components/src/x-modules/related-prompts/components/related-prompts-list.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
<script lang="ts"> | ||
import { computed, ComputedRef, defineComponent, h, inject, provide, Ref } from 'vue'; | ||
import { RelatedPrompt } from '@empathyco/x-types'; | ||
import { AnimationProp } from '../../../types/animation-prop'; | ||
import { groupItemsBy } from '../../../utils/array'; | ||
import ItemsList from '../../../components/items-list.vue'; | ||
import { ListItem } from '../../../utils/types'; | ||
import { | ||
HAS_MORE_ITEMS_KEY, | ||
LIST_ITEMS_KEY, | ||
QUERY_KEY | ||
} from '../../../components/decorators/injection.consts'; | ||
import { relatedPromptsXModule } from '../x-module'; | ||
import { useState } from '../../../composables/use-state'; | ||
import { RelatedPromptsGroup } from '../types'; | ||
/** | ||
* Component that inserts groups of related prompts in different positions of the injected search | ||
* items list, based on the provided configuration. | ||
* | ||
* @public | ||
*/ | ||
export default defineComponent({ | ||
name: 'RelatedPromptsList', | ||
xModule: relatedPromptsXModule.name, | ||
props: { | ||
/** | ||
* Animation component that will be used to animate the related prompts groups. | ||
*/ | ||
animation: { | ||
type: AnimationProp, | ||
default: 'ul' | ||
}, | ||
/** | ||
* The first index to insert a group of related prompts at. | ||
*/ | ||
offset: { | ||
type: Number, | ||
default: 24 | ||
}, | ||
/** | ||
* The items cycle size to keep inserting related prompts groups at. | ||
*/ | ||
frequency: { | ||
type: Number, | ||
default: 24 | ||
}, | ||
/** | ||
* The maximum amount of related prompts to add in a single group. | ||
*/ | ||
maxNextQueriesPerPrompt: { | ||
type: Number, | ||
default: 4 | ||
}, | ||
/** | ||
* The maximum number of groups to insert into the injected list items list. | ||
*/ | ||
maxGroups: { | ||
type: Number, | ||
default: undefined | ||
}, | ||
/** | ||
* Determines if a group is added to the injected items list in case the number | ||
* of items is smaller than the offset. | ||
*/ | ||
showOnlyAfterOffset: { | ||
type: Boolean, | ||
default: false | ||
} | ||
}, | ||
setup(props, { slots }) { | ||
const { query, status } = useState('relatedPrompts', ['query', 'status']); | ||
/** | ||
* The state related prompts. | ||
*/ | ||
const relatedPrompts: ComputedRef<RelatedPrompt[]> = useState('relatedPrompts', [ | ||
'relatedPrompts' | ||
]).relatedPrompts; | ||
/** | ||
* Injected query, updated when the related request(s) have succeeded. | ||
*/ | ||
const injectedQuery = inject<Ref<string | undefined>>(QUERY_KEY as string); | ||
/** | ||
* Indicates if there are more available results than the injected. | ||
*/ | ||
const hasMoreItems = inject<Ref<boolean | undefined>>(HAS_MORE_ITEMS_KEY as string); | ||
/** | ||
* The grouped related prompts based on the given config. | ||
* | ||
* @returns A list of related prompts groups. | ||
*/ | ||
const relatedPromptsGroups = computed<RelatedPromptsGroup[]>(() => | ||
Object.values( | ||
groupItemsBy(relatedPrompts.value, (_, index) => | ||
Math.floor(index / props.maxNextQueriesPerPrompt) | ||
) | ||
) | ||
.slice(0, props.maxGroups) | ||
.map((relatedPrompts, index) => ({ | ||
modelName: 'RelatedPromptsGroup' as const, | ||
id: `related-prompts-group-${index}`, | ||
relatedPrompts | ||
})) | ||
); | ||
/** | ||
* It injects {@link ListItem} provided by an ancestor as injectedListItems. | ||
*/ | ||
const injectedListItems = inject<Ref<ListItem[]>>(LIST_ITEMS_KEY as string); | ||
/** | ||
* Checks if the related prompts are outdated taking into account the injected query. | ||
* | ||
* @returns True if the related prompts are outdated, false if not. | ||
*/ | ||
const relatedPromptsAreOutdated = computed( | ||
() => | ||
!!injectedQuery?.value && | ||
(query.value !== injectedQuery.value || status.value !== 'success') | ||
); | ||
/** | ||
* Checks if the number of items is smaller than the offset so a group | ||
* should be added to the injected items list. | ||
* | ||
* @returns True if a group should be added, false if not. | ||
*/ | ||
const hasNotEnoughListItems = computed( | ||
() => | ||
!props.showOnlyAfterOffset && | ||
!hasMoreItems?.value && | ||
injectedListItems !== undefined && | ||
injectedListItems.value.length > 0 && | ||
props.offset > injectedListItems.value.length | ||
); | ||
/** | ||
* New list of {@link ListItem}s to render. | ||
* | ||
* @returns The new list of {@link ListItem}s with the related prompts groups inserted. | ||
*/ | ||
const items = computed((): ListItem[] => { | ||
if (!injectedListItems?.value) { | ||
return relatedPromptsGroups.value; | ||
} | ||
if (relatedPromptsAreOutdated.value) { | ||
return injectedListItems.value; | ||
} | ||
if (hasNotEnoughListItems.value) { | ||
return injectedListItems.value.concat(relatedPromptsGroups.value[0] ?? []); | ||
} | ||
return relatedPromptsGroups?.value.reduce( | ||
(items, relatedPromptsGroup, index) => { | ||
const targetIndex = props.offset + props.frequency * index; | ||
if (targetIndex <= items.length) { | ||
items.splice(targetIndex, 0, relatedPromptsGroup); | ||
} | ||
return items; | ||
}, | ||
[...injectedListItems.value] | ||
); | ||
}); | ||
/** | ||
* The computed list items of the entity that uses the mixin. | ||
* | ||
* @remarks It should be overridden in the component that uses the mixin and | ||
* it's intended to be filled with items from the state. Vue doesn't allow | ||
* mixins as abstract classes. | ||
* @returns An empty array as fallback in case it is not overridden. | ||
*/ | ||
provide(LIST_ITEMS_KEY as string, items); | ||
return () => { | ||
const innerProps = { items: items.value, animation: props.animation }; | ||
// https://vue-land.github.io/faq/forwarding-slots#passing-all-slots | ||
return slots.default?.(innerProps)[0] ?? h(ItemsList, innerProps, slots); | ||
}; | ||
} | ||
}); | ||
</script> |
16 changes: 16 additions & 0 deletions
16
packages/x-components/src/x-modules/related-prompts/events.types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { RelatedPromptsRequest } from '@empathyco/x-types'; | ||
|
||
/** | ||
* Dictionary of the events of RelatedPrompts XModule, where each key is the event name, | ||
* and the value is the event payload type or `void` if it has no payload. | ||
* | ||
* @public | ||
*/ | ||
export interface RelatedPromptsXEvents { | ||
/** | ||
* Any property of the related-prompts request has changed | ||
* Payload: The new related-prompts request or `null` if there is not enough data in the state | ||
* to conform a valid request. | ||
*/ | ||
RelatedPromptsRequestUpdated: RelatedPromptsRequest | null; | ||
} |
Oops, something went wrong.