Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/atomic-react/src/components/search/components.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AtomicAiConversationToggle as LitAtomicAiConversationToggle,
AtomicAriaLive as LitAtomicAriaLive,
AtomicComponentError as LitAtomicComponentError,
AtomicExternal as LitAtomicExternal,
Expand Down Expand Up @@ -41,6 +42,12 @@ import {
import {createComponent} from '@lit/react';
import React from 'react';

export const AtomicAiConversationToggle = createComponent({
tagName: 'atomic-ai-conversation-toggle',
react: React,
elementClass: LitAtomicAiConversationToggle,
});

export const AtomicAriaLive = createComponent({
tagName: 'atomic-aria-live',
react: React,
Expand Down
58 changes: 58 additions & 0 deletions packages/atomic/dev/examples/multiturn-conversation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!doctype html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0" />
<title>Coveo Multi-turn conversation</title>

<script type="module">
if (import.meta.env) {
const {defineCustomElements} = await import('@coveo/atomic/loader');
import('@coveo/atomic/themes/coveo.css');
defineCustomElements();
} else {
import('http://localhost:3000/atomic/v0.0.0/atomic.esm.js');
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'http://localhost:3000/atomic/v0.0.0/themes/coveo.css';
document.head.appendChild(link);
}
</script>

<!-- <script type="module">
await customElements.whenDefined('atomic-search-interface');
const searchInterface = document.querySelector('atomic-search-interface');

// Initialization
await searchInterface.initialize({
accessToken: 'xx564559b1-0045-48e1-953c-3addd1ee4457',
organizationId: 'searchuisamples',
search: {
pipeline: 'genqatest',
},
});

// Trigger a first search
searchInterface.executeFirstSearch();
</script> -->
<style>
body {
margin: 0;
}

.header-bg {
background-color: var(--atomic-neutral-light);
grid-area: 1 / -1 / 1 / 1;
}

atomic-search-layout {
row-gap: var(--atomic-layout-spacing-y);
}
</style>
</head>

<body>
<script src="../header.js" type="text/javascript"></script>
Multi-turn conversation example coming soon!
</body>
</html>
4 changes: 4 additions & 0 deletions packages/atomic/dev/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const links = [
{href: '/examples/genqa.html', label: 'Gen Q&A'},
{href: '/examples/tabs.html', label: 'Tabs'},
{href: '/examples/commerce-website/homepage.html', label: 'Commerce Website'},
{
href: '/examples/multiturn-conversation.html',
label: 'Multi-turn conversation',
},
];

const header = document.createElement('header');
Expand Down
11 changes: 6 additions & 5 deletions packages/atomic/scripts/generate-component.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {execSync} from 'node:child_process';
import {existsSync} from 'node:fs';
import {mkdir, readFile, writeFile} from 'node:fs/promises';
import path from 'node:path';
import fs from 'fs-extra';
import handlebars from 'handlebars';

const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
Expand Down Expand Up @@ -48,12 +49,12 @@ async function generateFiles(name, outputDir) {
outputPaths.push(outputPath);

// Does not overwrite existing files
if (await fs.pathExists(outputPath)) {
if (existsSync(outputPath)) {
console.log(`Skipped (already exists): ${outputPath}`);
continue;
}

const templateContent = await fs.readFile(templatePath, 'utf8');
const templateContent = await readFile(templatePath, 'utf8');
const compiled = handlebars.compile(templateContent);
const content = compiled({
name,
Expand All @@ -63,8 +64,8 @@ async function generateFiles(name, outputDir) {
githubPath,
});

await fs.ensureDir(path.dirname(outputPath));
await fs.writeFile(outputPath, content, 'utf8');
await mkdir(path.dirname(outputPath), {recursive: true});
await writeFile(outputPath, content, 'utf8');
console.log(`Created: ${outputPath}`);
}
execSync(`npx @biomejs/biome check --write ${outputPaths.join(' ')}`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type {i18n} from 'i18next';
import {html} from 'lit';
import {localizedString} from '@/src/directives/localized-string';
import type {FunctionalComponentWithChildren} from '@/src/utils/functional-component-utils';
import CloseIcon from '../../../images/close.svg';
import {ATOMIC_MODAL_EXPORT_PARTS} from '../atomic-modal/export-parts';
import '../atomic-modal/atomic-modal';
import {renderButton} from '../button';
import '../atomic-icon/atomic-icon';

interface AIConversationModalProps {
host: HTMLElement;
i18n: i18n;
onClose(): void;
title: string;
isOpen: boolean;
openButton?: HTMLElement;
boundary?: 'page' | 'element';
scope?: HTMLElement;
}

export const renderAIConversationModal: FunctionalComponentWithChildren<
AIConversationModalProps
> =
({props}) =>
(children) => {

const renderHeader = () => {
return html`
<div slot="header" class="contents">
<h1 part="title" class="truncate">${props.title}</h1>
${renderButton({
props: {
style: 'text-transparent',
class: 'grid place-items-center',
part: 'close-button',
onClick: props.onClose,
ariaLabel: props.i18n.t('close'),
},
})(
html`<atomic-icon
part="close-icon"
class="h-5 w-5"
icon=${CloseIcon}
></atomic-icon>`
)}
</div>
`;
};

const renderFooter = () => {
return html`
<div part="footer-content" slot="footer">
${renderButton({
props: {
style: 'primary',
part: 'footer-button',
class: 'flex w-full justify-center p-3 text-lg',
onClick: props.onClose,
},
})(html`
<span part="footer-button-text" class="mr-1 truncate">
${'Close'}
</span>
`)}
</div>
`;
};

return html`
<atomic-modal
.fullscreen=${true}
.isOpen=${props.isOpen}
.source=${props.openButton}
.container=${props.host}
.close=${props.onClose}
.onAnimationEnded=${() => {}}
exportparts=${ATOMIC_MODAL_EXPORT_PARTS}
.boundary=${props.boundary ?? 'page'}
.scope=${props.scope}
>
${renderHeader()} ${children} ${renderFooter()}
</atomic-modal>
`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {Meta} from '@storybook/addon-docs/blocks';
import * as AtomicCitationListStories from './atomic-citation-container.new.stories';
import {AtomicDocTemplate} from '../../../../../storybook-utils/documentation/atomic-doc-template';

<Meta of={AtomicCitationListStories} />

<AtomicDocTemplate
stories={AtomicCitationListStories}
githubPath="common/generated-answer/atomic-citation-list/atomic-citation-list.ts"
tagName="atomic-citation-list"
className="AtomicCitationList"
>

The `atomic-citation-list` component displays a list of citations for a generated answer.

## Usage

```html
<atomic-citation-list></atomic-citation-list>
```

</AtomicDocTemplate>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type {Meta, StoryObj as Story} from '@storybook/web-components-vite';
import {html} from 'lit';
import {parameters} from '@/storybook-utils/common/common-meta-parameters';
import './atomic-citation-container.js';
import type {GeneratedAnswerCitation} from '@coveo/headless';
import type {AtomicCitationContainer} from './atomic-citation-container.js';

const sampleCitations: GeneratedAnswerCitation[] = [
{
id: '1',
title: "What's a query pipeline? | Coveo Platform",
uri: 'https://docs.coveo.com/en/query-pipeline',
permanentid: 'doc-query-pipeline-123',
source: 'Coveo Documentation',
clickUri: 'https://docs.coveo.com/en/query-pipeline',
},
{
id: '2',
title:
'Manage the basic configuration of a query pipeline | Coveo Platform',
uri: 'https://docs.coveo.com/en/manage-query-pipeline',
permanentid: 'doc-manage-pipeline-456',
source: 'Coveo Training',
clickUri: 'https://docs.coveo.com/en/manage-query-pipeline',
},
];

const meta: Meta = {
component: 'atomic-citation-container',
title: 'Common/Citation Container',
id: 'atomic-citation-container',

render: (args) => {
const element = document.createElement(
'atomic-citation-container'
) as AtomicCitationContainer;
element.citations = args.citations;
return element;
},
parameters: {
...parameters,
},
args: {
citations: sampleCitations,
},
};

export default meta;

export const Default: Story = {
name: 'atomic-citation-container',
decorators: [(story) => html`${story()}`],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type {GeneratedAnswerCitation} from '@coveo/headless';
import {html, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {withTailwindStyles} from '@/src/decorators/with-tailwind-styles.js';
import '../atomic-citation-list/atomic-citation-list';

/**
* The `atomic-citation-container` component displays a list of citations for a generated answer in the context of a multiturn conversation.
*
* @part citation-list - The container for the citation list
* @part citation-item - Each individual citation item
* @part citation-title - The title of the citation
* @part citation-source - The source label of the citation
* @part citation-link - The clickable link element
*/
@customElement('atomic-citation-container')
@withTailwindStyles
export class AtomicCitationContainer extends LitElement {
/**
* The array of citations to display
*/
@property({type: Array, attribute: false})
citations: GeneratedAnswerCitation[] = [];

render() {
// Guard against null/undefined citations

return html`
<div part="citation-list" class="citation-list">
<h3 class="citation-list-title">Citations list:</h3>
<atomic-citation-list .citations=${this.citations}></atomic-citation-list>
</div>
`;
}
}

declare global {
interface HTMLElementTagNameMap {
'atomic-citation-container': AtomicCitationContainer;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {Meta} from '@storybook/addon-docs/blocks';
import * as AtomicCitationListStories from './atomic-citation-list.new.stories';
import {AtomicDocTemplate} from '../../../../../storybook-utils/documentation/atomic-doc-template';

<Meta of={AtomicCitationListStories} />

<AtomicDocTemplate
stories={AtomicCitationListStories}
githubPath="common/generated-answer/atomic-citation-list/atomic-citation-list.ts"
tagName="atomic-citation-list"
className="AtomicCitationList"
>

The `atomic-citation-list` component displays a list of citations for a generated answer.

## Usage

```html
<atomic-citation-list></atomic-citation-list>
```

</AtomicDocTemplate>
Loading
Loading