Skip to content

167/show ai tools #427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 11, 2025
Merged
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
2 changes: 1 addition & 1 deletion src/app/app/virtual-lab/(free)/explore/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { sectionAtom } from '@/state/application';

import styles from './layout.module.css';

const LiteratureSuggestions = dynamic(() => import('@/components/literature-suggestions'));
const LiteratureSuggestions = dynamic(() => import('@/components/ai-assistant'));

type GenericLayoutProps = {
children: ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { sectionAtom } from '@/state/application';

import styles from './layout.module.css';

const LiteratureSuggestions = dynamic(() => import('@/components/literature-suggestions'));
const LiteratureSuggestions = dynamic(() => import('@/components/ai-assistant'));

type GenericLayoutProps = {
children: ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

width: max(320px, 25vw);
height: 100%;
background-color: var(--color-neutral-9);
background-color: var(--color-neutral-7);
color: var(--color-primary-1);
padding: 1em;
display: grid;
Expand Down Expand Up @@ -77,18 +77,28 @@

.literatureSuggestions button.actionButton {
font: inherit;
border: 2px solid var(--color-primary-2);
border-radius: 4px;
border: 2px solid #f0f0f0;
background: #fff;
border-radius: 999vmax;
box-shadow:
0 0.5em 1em #0005,
0 -0.5em 1em #fff;
line-height: 2;
padding: 0 1em;
cursor: pointer;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: 1em;
}

.footerButtons {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
justify-content: flex-end;
justify-content: flex-start;
align-items: center;
gap: 1em;
margin: 1em 0;
Expand Down Expand Up @@ -132,8 +142,10 @@ button.cancelButton:active {
margin-top: 2em;
font-size: 120%;
padding: 1em;
box-shadow: rgba(0, 0, 0, 0.12) 0px 6px 16px;
box-shadow: rgba(0, 0, 0, 0.12) 0 6px 16px;
border-radius: 1em;
border: 2px solid var(--color-neutral-9);
background-color: var(--color-neutral-8);
}

.welcome > div > p {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,25 @@ import Prompt from './prompt';
import { Spinner } from './spinner';
import SuggestedQuestions from './suggested-questions';
import { useCollapsedPanel } from './hooks';
import { IconClear } from './icons/clear';
import { classNames } from '@/util/utils';
import { useServiceAiAgentChat, useServiceAiAgentThread } from '@/services/ai-agent';

import styles from './literature-suggestions.module.css';
import { useAITools } from '@/services/ai-agent/tools/tools';
import styles from './ai-assistant.module.css';

export interface LiteratureSuggestionsProps {
className?: string;
}

export default function LiteratureSuggestions({ className }: LiteratureSuggestionsProps) {
export default function ArtificialIntelligenceAssistant({ className }: LiteratureSuggestionsProps) {
const tools = useAITools();
const [collapsedPanel, setCollapsedPanel] = useCollapsedPanel();
const refChatBottom = React.useRef<HTMLDivElement | null>(null);
const [threadId, recreateThreadId] = useServiceAiAgentThread();
const [prompt, setPrompt] = React.useState('');
const { messages, clear, status, append, error, stop } = useServiceAiAgentChat(threadId ?? '');

// TODO: for future improvement, to disable the spinner for user has not virtual lab
// const userStats = useAtomValue(userStatsAtom);
// const userHasVirtualLab = Boolean(userStats?.data?.owned_labs_count);

const handleQuery = React.useCallback(
(content: string) => {
append({
Expand Down Expand Up @@ -77,7 +76,7 @@ export default function LiteratureSuggestions({ className }: LiteratureSuggestio
</div>
</div>
)}
{threadId ? (
{threadId && tools ? (
<>
<div className={styles.articles}>
{messages.map((item, messageIndex) => (
Expand All @@ -90,7 +89,8 @@ export default function LiteratureSuggestions({ className }: LiteratureSuggestio
{status === 'ready' && messages.length > 0 && (
<div className={styles.footerButtons}>
<button type="button" className={styles.actionButton} onClick={handleClearChat}>
Clear the Chat
<IconClear />
<div>Clear chat</div>
</button>
</div>
)}
Expand All @@ -110,7 +110,7 @@ export default function LiteratureSuggestions({ className }: LiteratureSuggestio
/>
)}
{(status === 'ready' || status === 'error') && (
<Prompt value={prompt} onChange={setPrompt} onClick={handleQuery} />
<Prompt value={prompt} tools={tools} onChange={setPrompt} onClick={handleQuery} />
)}
{status !== 'ready' && status !== 'error' && (
<div className={styles.spinnerContainer}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
}

.error pre {
white-space: prewrap;
white-space: pre-wrap;
}
16 changes: 16 additions & 0 deletions src/components/ai-assistant/icons/clear.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function IconClear() {
return (
<svg
width="1em"
height="1em"
viewBox="0 0 13 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.9999 6.6258L12.9948 5.30653C12.9898 4.42915 12.2957 3.72064 11.4397 3.71827H11.4341L9.80397 3.72405L9.83444 0.598611V0.599189C9.83726 0.275303 9.5839 0.0104052 9.26792 0.00694055L8.50449 0H8.49828C8.18511 0 7.93008 0.258531 7.9267 0.580107L7.89623 3.7322L6.2108 3.73856C5.35314 3.74377 4.66087 4.45863 4.66188 5.33778L4.6664 6.65705C4.6664 6.72182 4.68953 6.78486 4.73072 6.8346C4.54 8.13476 3.86911 9.31064 2.85578 10.118L2.8507 10.1221C2.74011 10.2111 2.71584 10.3731 2.7954 10.4922C2.86763 10.6015 2.94267 10.7068 3.01716 10.8051C3.92505 12.0018 5.26232 12.7767 6.73108 12.9566C6.95959 12.9855 7.18981 13 7.42002 13C8.52765 12.9971 9.60986 12.6559 10.528 12.0208C11.446 11.3852 12.1587 10.4835 12.5757 9.43151C12.9075 8.58767 13.0389 7.67564 12.9599 6.7698C12.9865 6.72642 12.9999 6.67669 12.9999 6.6258ZM8.46279 0.584713C8.46336 0.56447 8.47916 0.548276 8.49891 0.547698L9.26291 0.555216V0.555795C9.28266 0.556373 9.29846 0.572567 9.29902 0.59281L9.26855 3.7258L8.43232 3.72927L8.46279 0.584713ZM5.49423 4.59749V4.59691C5.68382 4.3997 5.94226 4.28807 6.21253 4.28749L11.4364 4.26783L11.4398 4.26725C12.0006 4.26956 12.4548 4.73399 12.4594 5.30831L12.4633 6.35284L5.20087 6.38118L5.19692 5.33665C5.19523 5.05961 5.30244 4.79298 5.49372 4.59749L5.49423 4.59749ZM10.5115 11.3546C9.95008 11.8052 9.30231 12.1279 8.60999 12.3008C9.18101 11.7016 9.61604 10.9798 9.88294 10.1875C9.92357 10.047 9.84908 9.89892 9.71366 9.85091C9.57881 9.80291 9.43041 9.87231 9.37737 10.0082C9.06026 10.9475 8.48416 11.7722 7.71954 12.3836C7.69866 12.3998 7.6806 12.4194 7.66537 12.4414C7.04638 12.4732 6.42684 12.3882 5.83781 12.191C6.31177 11.7433 6.70506 11.2135 7.00073 10.6266C7.06393 10.4924 7.01145 10.331 6.88224 10.2628C6.75359 10.1945 6.59447 10.2443 6.52506 10.375C6.21754 10.9852 5.79323 11.5259 5.2775 11.9649C4.55751 11.6219 3.92724 11.1083 3.43867 10.4663C3.41949 10.4403 3.39974 10.4143 3.37999 10.3871V10.3877C4.39169 9.50393 5.05863 8.27602 5.2584 6.9291L12.4334 6.90191C12.5655 8.62373 11.8426 10.2968 10.5115 11.3546ZM7.33133 9.82423L7.32343 9.84795C7.30086 9.91793 7.25233 9.97519 7.18857 10.0076C7.12425 10.0405 7.05033 10.0457 6.98262 10.022C6.91491 9.99832 6.85961 9.948 6.82802 9.88207C6.79698 9.81613 6.79303 9.74037 6.81617 9.67154L6.82294 9.65129V9.65072C6.86977 9.5067 7.02156 9.4292 7.16206 9.4772C7.30199 9.52521 7.37816 9.68079 7.33133 9.82423ZM3.76692 6.54599C3.91475 6.54599 4.03494 6.42338 4.03494 6.27184C4.03494 6.12031 3.91476 5.99711 3.76692 5.99711C2.94875 5.99653 2.28575 5.31694 2.28517 4.47829V4.47713C2.28404 4.32618 2.16442 4.20413 2.01714 4.20413C1.86931 4.20413 1.74968 4.32675 1.74968 4.47829C1.74855 5.31518 1.08838 5.99355 0.271975 5.99711H0.267461C0.119632 5.99711 0 6.1203 0 6.27184C0 6.42336 0.119621 6.54599 0.267461 6.54599C1.0828 6.54714 1.74517 7.22268 1.74979 8.05844V8.06538C1.7481 8.13883 1.77518 8.21055 1.82596 8.26318C1.87618 8.31639 1.94502 8.34588 2.01724 8.34588C2.08946 8.34588 2.15831 8.31639 2.20908 8.26318C2.2593 8.21055 2.28695 8.13883 2.28526 8.06538C2.28582 7.22849 2.94599 6.54952 3.76253 6.54597L3.76692 6.54599ZM2.01722 7.03645C1.83835 6.71835 1.58105 6.45461 1.27071 6.27184C1.58048 6.08849 1.83778 5.82476 2.01665 5.50665C2.19552 5.82475 2.45281 6.08849 2.76316 6.27184C2.45283 6.45518 2.19609 6.71891 2.01722 7.03645Z"
fill="currentColor"
/>
</svg>
);
}
17 changes: 17 additions & 0 deletions src/components/ai-assistant/icons/gear.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function IconGear({ className }: { className?: string }) {
return (
<svg
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1.5em"
height="1.5em"
>
<title>Tools</title>
<path
fill="currentColor"
d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"
/>
</svg>
);
}
1 change: 1 addition & 0 deletions src/components/ai-assistant/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ai-assistant';
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
.user {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: 0;
color: var(--color-on-primary);
margin: 8px 0;
margin-bottom: 32px;
}

.timestamp {
color: #000b;
}

.userAvatar {
Expand Down Expand Up @@ -38,6 +43,7 @@
}

.userContent {
position: relative;
flex: 1 1 auto;
padding: 8px;
border-radius: 6px;
Expand All @@ -50,10 +56,34 @@
min-height: 64px;
}

.userContent::after {
content: '';
position: absolute;
right: 16px;
bottom: -24px;
width: 32px;
height: 24px;
background-color: var(--color-primary-5);
border-radius: 0;
z-index: 1;
clip-path: path('M0,0h24L32,24,0,0z');
}

.userContent > div {
padding: 0 1em;
}

.markdown {
border-radius: 1em;
border: 2px solid var(--color-neutral-9);
background-color: #f8f8f8;
padding: 1em;
margin: 1em 0;
box-shadow: 0 0.5em 1em #0005;
clip-path: none;
z-index: 1;
}

.markdown ul {
list-style: disc;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import Link from 'next/link';
import { ToolInvocation, UIMessage } from '@ai-sdk/ui-utils';
import ReactMarkdown from 'react-markdown';

import ToolArticles from './tools/articles/tool-articles';
import ToolMorphologies from './tools/morphologies/tool-morphologies';
import ToolArticles from '../../../services/ai-agent/tools/articles/tool-articles';
import ToolMorphologies from '../../../services/ai-agent/tools/morphologies/tool-morphologies';
import ToolsProgress from './tools-progress';
import { classNames } from '@/util/utils';

import styles from './message-item.module.css';
Expand All @@ -31,23 +32,26 @@ function renderMessage(value: UIMessage, hideTools: boolean, debug: boolean): Re
case 'user':
return (
<div className={styles.user}>
{/* <div className={styles.userAvatar}><ChevronRight fill="currentColor" /></div> */}
<div className={styles.userContent}>
<div>{value.content}</div>
</div>
<div className={styles.timestamp}>{value.createdAt && formatDate(value.createdAt)}</div>
</div>
);
case 'assistant': {
return (
<>
<ReactMarkdown
className={styles.markdown}
components={{
a: LinkWithExternalTarget,
}}
>
{value.content}
</ReactMarkdown>
<ToolsProgress message={value} />
{value.content.trim().length > 0 && (
<ReactMarkdown
className={styles.markdown}
components={{
a: LinkWithExternalTarget,
}}
>
{value.content}
</ReactMarkdown>
)}
{!hideTools && (
<>
<ToolArticles message={value} />
Expand Down Expand Up @@ -108,3 +112,11 @@ function LinkWithExternalTarget({ href, children }: AnchorHTMLAttributes<HTMLAnc
</Link>
);
}

function formatDate(d: Date): string {
const formatter = new Intl.DateTimeFormat(undefined, {
dateStyle: 'short',
timeStyle: 'short',
});
return formatter.format(d);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './tools-progress';
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.toolState {
color: #000e;
border: 1px solid #0003;
display: inline-flex;
flex-wrap: nowrap;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 0.5em;
padding: 0.25em 0.5em;
margin: 0.5em;
}

.toolState > * {
flex: 0 0 auto;
}

.toolState > .name {
flex: 1 1 auto;
}

.spin {
animation: 1s infinite linear animation-spin;
}

@keyframes animation-spin {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
Loading