Tweaks to AI Chat and AI Actions Dropdown (#3495)

This commit is contained in:
Zeno Kapitein
2025-07-22 19:18:35 +02:00
committed by GitHub
parent 250c194a02
commit b0c534fc2c
21 changed files with 72 additions and 24 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Tweaks to AI Chat and AI Actions Dropdown
@@ -23,7 +23,7 @@ export function AIMessageView(
<div
key={index}
className={tcls(
'flex animate-[fadeIn_500ms_both] flex-col gap-2',
'flex animate-fadeIn-slow flex-col gap-2',
step.content.nodes.length > 0 ? 'has-content' : ''
)}
>
@@ -38,7 +38,7 @@ function ToolCallSummary(props: { toolCall: AIToolCall; context: GitBookSiteCont
const { toolCall, context } = props;
return (
<div className="flex origin-left animate-[scaleIn_500ms_500ms_both] items-start gap-2 text-sm text-tint-subtle">
<div className="flex origin-left animate-scaleIn-slow items-start gap-2 text-sm text-tint-subtle">
<Icon
icon={getIconForToolCall(toolCall)}
className="mt-1 size-3 shrink-0 text-tint-subtle/8"
@@ -151,13 +151,13 @@ async function DescriptionForSearchToolCall(props: {
<details className={tcls('-ml-5 group flex w-full flex-col', hasResults ? 'gap-2' : '')}>
<summary
className={tcls(
'-mx-2 flex list-none items-center gap-2 rounded-corners:rounded-md pr-4 pl-7 transition-colors marker:hidden',
'-mx-2 flex list-none items-center gap-2 circular-corners:rounded-2xl rounded-corners:rounded-md pr-4 pl-7 transition-colors marker:hidden',
hasResults ? '-my-2 cursor-pointer py-2 hover:bg-primary-hover' : ''
)}
>
<div className="flex flex-col">
<div className="flex flex-col leading-snug">
<p>{t(language, 'searched_for', <strong>{toolCall.query}</strong>)}</p>
<p className="text-tint-subtle text-xs">
<p className="mt-0.5 text-tint-subtle text-xs">
{hasResults
? t(language, 'search_results_count', toolCall.results.length)
: t(language, 'search_no_results')}
@@ -175,12 +175,12 @@ async function DescriptionForSearchToolCall(props: {
) : null}
</summary>
{hasResults ? (
<div className="max-h-0 overflow-y-auto circular-corners:rounded-2xl rounded-corners:rounded-lg border border-tint-subtle p-2 opacity-0 transition-all duration-500 [transition-behavior:allow-discrete] group-open:max-h-96 group-open:opacity-11">
<div className="mt-1 max-h-0 overflow-y-auto circular-corners:rounded-2xl rounded-corners:rounded-lg border border-tint-subtle p-2 opacity-0 transition-all duration-500 [transition-behavior:allow-discrete] group-open:max-h-96 group-open:opacity-11">
<ol className="space-y-1">
{searchResultsWithHrefs.map((result, index) => (
<li
key={`${result.pageId}-${index}`}
className="animate-fadeIn"
className="animate-fadeIn-slow"
style={{
animationDelay: `${index * 25}ms`,
}}
@@ -112,7 +112,7 @@ export function AIChatWindow(props: {
ref={containerRef}
>
<div className="relative flex h-full grow flex-col overflow-hidden circular-corners:rounded-3xl rounded-corners:rounded-md bg-tint-base text-sm text-tint depth-subtle:shadow-lg shadow-tint ring-1 ring-tint-subtle">
<div className="flex items-center gap-2 border-tint-subtle border-b bg-tint-subtle px-4 py-2 text-tint-strong">
<div className="flex select-none items-center gap-2 border-tint-subtle border-b bg-tint-subtle px-4 py-2 text-tint-strong">
<AIChatIcon
className="size-5 text-tint"
trademark={trademark}
@@ -149,7 +149,7 @@ export function AIChatWindow(props: {
onClick={() => {}}
iconOnly
icon="ellipsis"
label={tString(language, 'more')}
label={tString(language, 'actions')}
className="!px-2"
variant="blank"
size="default"
@@ -162,7 +162,10 @@ export function AIChatWindow(props: {
}}
disabled={isEmpty}
>
<Icon icon="broom-wide" className="size-3 text-tint-subtle" />
<Icon
icon="broom-wide"
className="size-3 shrink-0 text-tint-subtle"
/>
{t(language, 'ai_chat_clear_conversation')}
</DropdownMenuItem>
</DropdownMenu>
@@ -186,7 +189,7 @@ export function AIChatWindow(props: {
>
{isEmpty ? (
<div className="flex min-h-full w-full shrink-0 flex-col items-center justify-center gap-6 py-4">
<div className="flex size-32 animate-[fadeIn_500ms_both] items-center justify-center rounded-full bg-tint-subtle">
<div className="flex size-32 animate-fadeIn-slow items-center justify-center rounded-full bg-tint-subtle">
<AIChatIcon
state="intro"
trademark={trademark}
@@ -215,7 +218,7 @@ export function AIChatWindow(props: {
</div>
<div
ref={inputRef}
className="absolute inset-x-0 bottom-0 mr-2 flex flex-col gap-4 bg-gradient-to-b from-transparent to-50% to-tint-base/9 p-4 pr-2"
className="absolute inset-x-0 bottom-0 mr-2 flex select-none flex-col gap-4 bg-gradient-to-b from-transparent to-50% to-tint-base/9 p-4 pr-2"
>
{/* Display an error banner when something went wrong. */}
{chat.error ? <AIChatError chatController={chatController} /> : null}
@@ -24,7 +24,7 @@ export function AIChatMessages(props: {
<div
ref={isLastUserMessage ? lastUserMessageRef : undefined}
className={tcls(
message.content ? 'animate-[fadeIn_500ms_both]' : '',
message.content ? 'animate-fadeIn-slow' : '',
'shrink-0',
'last:min-h-[calc(100%-5rem)]',
'scroll-mt-36',
@@ -43,11 +43,11 @@ export function AIChatMessages(props: {
{message.content ? message.content : null}
{isLastMessage && chat.loading ? (
<div className="flex w-full animate-[fadeIn_500ms_both] flex-wrap gap-2 group-has-[.has-content]/message:hidden">
<div className="flex w-full animate-fadeIn-slow flex-wrap gap-2 group-has-[.has-content]/message:hidden">
{Array.from({ length: 7 }).map((_, index) => (
<div
key={index}
className="h-4 animate-[fadeIn_0.5s_ease_both,pulse_1.5s_infinite] rounded-md bg-tint-4"
className="h-4 animate-[fadeIn_500ms_ease_both,pulse_1.5s_infinite] circular-corners:rounded-2xl rounded-corners:rounded-md bg-tint-4"
style={{
width: `calc(${(index % 4) * 20 + 10}% - 4px)`,
animationDelay: `${index * 0.1}s`,
@@ -59,8 +59,8 @@ export function AIResponseFeedback(props: {
/>
{rating !== null ? (
<span
className="ml-2 animate-fadeIn text-tint-subtle"
style={{ animationDelay: '.3s', animationDuration: '.5s' }}
className="ml-2 animate-fadeIn-slow text-tint-subtle"
style={{ animationDelay: '.3s' }}
>
{t(language, 'was_this_helpful_thank_you')}
</span>
@@ -23,7 +23,7 @@ export function AIChatFollowupSuggestions(props: {
chatController.postMessage({ message: suggestion });
}}
label={suggestion}
className="max-w-[80%] animate-[present_500ms_both] whitespace-normal text-left ring-1 ring-tint-subtle"
className="animate-[present_500ms_both] whitespace-normal text-left ring-1 ring-tint-subtle"
size="medium"
variant="blank"
style={{
@@ -95,8 +95,9 @@ export function PageAside(props: {
'page-api-block:xl:max-2xl:dark:hover:shadow-tint-1/1',
'page-api-block:xl:max-2xl:rounded-md',
'page-api-block:xl:max-2xl:h-auto',
'page-api-block:xl:max-2xl:my-4',
'page-api-block:p-2'
'page-api-block:xl:max-2xl:my-9',
'page-api-block:px-2',
'page-api-block:py-1.5'
)}
>
{page.layout.outline ? (
@@ -45,7 +45,7 @@ export async function PageHeader(props: {
(withAIChat || pageActions.markdown || pageActions.externalAI) ? (
<div
className={tcls(
'float-right mb-2 ml-4',
'float-right mb-2 ml-4 page-api-block:xl:max-2xl:mr-[14.5rem]',
ancestors.length > 0 ? '-mt-2' : 'xs:mt-2'
)}
>
@@ -1,5 +1,9 @@
'use client';
import { tcls } from '@/lib/tailwind';
import * as RadixTooltip from '@radix-ui/react-tooltip';
import { useState } from 'react';
import { useIsMobile } from '../hooks/useIsMobile';
export function Tooltip(props: {
children: React.ReactNode;
@@ -22,10 +26,24 @@ export function Tooltip(props: {
className,
} = props;
const isMobile = useIsMobile();
const [open, setOpen] = useState(false);
return (
<RadixTooltip.Provider delayDuration={300} {...providerProps}>
<RadixTooltip.Root {...rootProps}>
<RadixTooltip.Trigger asChild {...triggerProps}>
<RadixTooltip.Root open={open} onOpenChange={(e) => setOpen(e)} {...rootProps}>
<RadixTooltip.Trigger
asChild
onClick={(event) => {
event.preventDefault();
if (isMobile) {
setOpen(true);
}
setOpen(true);
}}
onPointerDown={(event) => event.preventDefault()}
{...triggerProps}
>
{children}
</RadixTooltip.Trigger>
<RadixTooltip.Portal>
@@ -35,6 +53,12 @@ export function Tooltip(props: {
'z-50 max-w-xs animate-scaleIn circular-corners:rounded-2xl rounded-corners:rounded-md bg-tint-12 px-2 py-1 text-contrast-tint-12 text-sm',
className
)}
onPointerDownOutside={(event) => {
event.preventDefault();
if (isMobile) {
setOpen(false);
}
}}
{...contentProps}
>
{label}
@@ -8,7 +8,7 @@ export const ButtonStyles = [
'gap-2',
'rounded-md',
'straight-corners:rounded-none',
'circular-corners:rounded-full',
'circular-corners:rounded-3xl',
'border',
'border-tint',
@@ -88,6 +88,7 @@ export const de = {
ai_chat_context_disclaimer: 'KI-Antworten können Fehler enthalten.',
ai_chat_input_placeholder: 'Fragen, suchen oder Aktion ausführen...',
send: 'Senden',
actions: 'Aktionen',
ai_chat_suggested_questions_title: 'Vorgeschlagene Fragen',
ai_chat_suggested_questions_about_this_page: 'Worum geht es auf dieser Seite?',
ai_chat_suggested_questions_read_next: 'Was sollte ich als nächstes lesen?',
@@ -86,6 +86,7 @@ export const en = {
ai_chat_context_disclaimer: 'AI responses may contain mistakes.',
ai_chat_input_placeholder: 'Ask, search, or take action...',
send: 'Send',
actions: 'Actions',
ai_chat_suggested_questions_title: 'Suggested questions',
ai_chat_suggested_questions_about_this_page: 'What is this page about?',
ai_chat_suggested_questions_read_next: 'What should I read next?',
@@ -90,6 +90,7 @@ export const es: TranslationLanguage = {
ai_chat_context_disclaimer: 'Las respuestas de IA pueden contener errores.',
ai_chat_input_placeholder: 'Pregunta, busca o realiza una acción...',
send: 'Enviar',
actions: 'Acciones',
ai_chat_suggested_questions_title: 'Preguntas sugeridas',
ai_chat_suggested_questions_about_this_page: '¿De qué trata esta página?',
ai_chat_suggested_questions_read_next: '¿Qué debería leer después?',
@@ -88,6 +88,7 @@ export const fr: TranslationLanguage = {
ai_chat_context_disclaimer: "Les réponses de l'IA peuvent contenir des erreurs.",
ai_chat_input_placeholder: 'Demander, rechercher ou effectuer une action...',
send: 'Envoyer',
actions: 'Actions',
ai_chat_suggested_questions_title: 'Questions suggérées',
ai_chat_suggested_questions_about_this_page: 'De quoi parle cette page ?',
ai_chat_suggested_questions_read_next: 'Que devrais-je lire ensuite ?',
@@ -88,6 +88,7 @@ export const ja: TranslationLanguage = {
ai_chat_context_disclaimer: 'AIの回答には誤りが含まれる場合があります。',
ai_chat_input_placeholder: '質問、検索、またはアクションを実行...',
send: '送信',
actions: 'アクション',
ai_chat_suggested_questions_title: 'おすすめの質問',
ai_chat_suggested_questions_about_this_page: 'このページは何について説明していますか?',
ai_chat_suggested_questions_read_next: '次に何を読むべきですか?',
@@ -88,6 +88,7 @@ export const nl: TranslationLanguage = {
ai_chat_context_disclaimer: 'AI-antwoorden kunnen fouten bevatten.',
ai_chat_input_placeholder: 'Vraag, zoek of voer een actie uit...',
send: 'Versturen',
actions: 'Acties',
ai_chat_suggested_questions_title: 'Voorgestelde vragen',
ai_chat_suggested_questions_about_this_page: 'Waar gaat deze pagina over?',
ai_chat_suggested_questions_read_next: 'Wat moet ik hierna lezen?',
@@ -88,6 +88,7 @@ export const no: TranslationLanguage = {
ai_chat_context_disclaimer: 'AI-svar kan inneholde feil.',
ai_chat_input_placeholder: 'Spør, søk eller utfør en handling...',
send: 'Send',
actions: 'Handlinger',
ai_chat_suggested_questions_title: 'Foreslåtte spørsmål',
ai_chat_suggested_questions_about_this_page: 'Hva handler denne siden om?',
ai_chat_suggested_questions_read_next: 'Hva bør jeg lese neste?',
@@ -88,6 +88,7 @@ export const pt_br = {
ai_chat_context_disclaimer: 'Respostas de IA podem conter erros.',
ai_chat_input_placeholder: 'Pergunte, pesquise ou execute uma ação...',
send: 'Enviar',
actions: 'Ações',
ai_chat_suggested_questions_title: 'Perguntas sugeridas',
ai_chat_suggested_questions_about_this_page: 'Sobre o que é esta página?',
ai_chat_suggested_questions_read_next: 'O que devo ler em seguida?',
@@ -85,6 +85,7 @@ export const zh: TranslationLanguage = {
ai_chat_context_disclaimer: '人工智能的回答可能包含错误。',
ai_chat_input_placeholder: '询问、搜索或执行操作...',
send: '发送',
actions: '操作',
ai_chat_suggested_questions_title: '建议的问题',
ai_chat_suggested_questions_about_this_page: '这个页面讲的是什么?',
ai_chat_suggested_questions_read_next: '我接下来应该读什么?',
+6
View File
@@ -298,10 +298,16 @@ const config: Config = {
},
animation: {
present: 'present 200ms cubic-bezier(0.25, 1, 0.5, 1) both',
'present-slow': 'present 500ms cubic-bezier(0.25, 1, 0.5, 1) both',
scaleIn: 'scaleIn 200ms ease both',
'scaleIn-slow': 'scaleIn 500ms ease both',
scaleOut: 'scaleOut 200ms ease both',
'scaleOut-slow': 'scaleOut 500ms ease both',
fadeIn: 'fadeIn 200ms ease both',
'fadeIn-slow': 'fadeIn 500ms ease both',
fadeOut: 'fadeOut 200ms ease both',
'fadeOut-slow': 'fadeOut 500ms ease both',
enterFromLeft: 'enterFromLeft 250ms cubic-bezier(0.83, 0, 0.17, 1) both',
enterFromRight: 'enterFromRight 250ms cubic-bezier(0.83, 0, 0.17, 1) both',
exitToLeft: 'exitToLeft 250ms cubic-bezier(0.83, 0, 0.17, 1) both',