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
6 changes: 5 additions & 1 deletion packages/react/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { F0Provider } from "@/lib/providers/f0"
import { DocsContainer } from "./DocsContainer.tsx"
import { buildTranslations, defaultTranslations } from "@/lib/providers/i18n"
import { dataCollectionLocalStorageHandler } from "@/lib/providers/datacollection"
import { aiTranslations } from "@/ai/AiChat/providers/AiChatTranslationsProvider"

MotionGlobalConfig.skipAnimations = isChromatic()

Expand All @@ -38,7 +39,10 @@ export const F0 = (Story: StoryFn, { parameters }: StoryContext) => {
fullScreen: parameters.layout === "fullscreen",
}}
i18n={{
translations: buildTranslations(defaultTranslations),
translations: buildTranslations({
...defaultTranslations,
...aiTranslations,
}),
}}
l10n={{
l10n: {
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/ai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./ai/AiChat"
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useI18n } from "@/lib/providers/i18n"
import { useAiChatTranslations } from "@/ai/AiChat/providers/AiChatTranslationsProvider"
import { cn, focusRing } from "@/lib/utils"
import {
Tooltip,
Expand All @@ -17,7 +17,7 @@ export const OneSwitch = ({
disabled,
}: React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>) => {
const { enabled, setOpen, open } = useAiChat()
const translations = useI18n()
const translations = useAiChatTranslations()
const [isHover, setIsHover] = useState(false)

if (!enabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import {
ThumbsUp,
ThumbsUpFilled,
} from "@/icons/app"
import { useI18n } from "@/lib/providers/i18n"
import { cn } from "@/lib/utils"
import { Markdown, type AssistantMessageProps } from "@copilotkit/react-ui"
import { useCallback, useRef, useState } from "react"
import { markdownRenderers as f0MarkdownRenderers } from "../markdownRenderers"
import { useAiChatTranslations } from "../providers/AiChatTranslationsProvider"
import { ChatSpinner } from "./ChatSpinner"
import { useFeedbackModal, UserReaction } from "./FeedbackProvider"

Expand All @@ -37,7 +37,7 @@ export const AssistantMessage = ({
)
const isEmptyMessage = !content && !subComponent

const translations = useI18n()
const translations = useAiChatTranslations()
const { open: openFeedbackModal } = useFeedbackModal()
const [reactionValue, setReactionValue] = useState<UserReaction | null>(null)
const [isHovered, setIsHovered] = useState(false)
Expand Down Expand Up @@ -106,7 +106,7 @@ export const AssistantMessage = ({
<F0Button
variant="ghost"
size="sm"
label={translations.actions.thumbsUp}
label={translations.ai.thumbsUp}
icon={reactionValue === "like" ? ThumbsUpFilled : ThumbsUp}
hideLabel
disabled={isGenerating}
Expand All @@ -124,7 +124,7 @@ export const AssistantMessage = ({
<F0Button
variant="ghost"
size="sm"
label={translations.actions.thumbsDown}
label={translations.ai.thumbsDown}
icon={
reactionValue === "dislike" ? ThumbsDownFilled : ThumbsDown
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { ButtonInternal } from "@/components/F0Button/internal"
import Cross from "@/icons/app/Cross"
import { useI18n } from "@/lib/providers/i18n"
import { cn } from "@/lib/utils"
import { useChatContext, type HeaderProps } from "@copilotkit/react-ui"
import { motion } from "motion/react"
import { useAiChat } from "../providers/AiChatStateProvider"
import { useAiChatTranslations } from "../providers/AiChatTranslationsProvider"

export const ChatHeader = (props: HeaderProps) => {
const { labels } = useChatContext()
const { setOpen } = useAiChat()
const translations = useI18n()
const translations = useAiChatTranslations()
const hasDefaultTitle = labels.title === "CopilotKit"

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { ButtonInternal } from "@/components/F0Button/internal"
import { ArrowUp, SolidStop } from "@/icons/app"
import { useI18n } from "@/lib/providers/i18n"
import { cn } from "@/lib/utils"
import { type InputProps } from "@copilotkit/react-ui"
import { AnimatePresence, motion } from "motion/react"
import { useEffect, useRef, useState } from "react"
import { useAiChatTranslations } from "../providers/AiChatTranslationsProvider"

export const ChatTextarea = ({ inProgress, onSend, onStop }: InputProps) => {
const [inputValue, setInputValue] = useState("")
const [hasScrollbar, setHasScrollbar] = useState(false)
const formRef = useRef<HTMLFormElement>(null)
const textareaRef = useRef<HTMLTextAreaElement>(null)
const translation = useI18n()
const translation = useAiChatTranslations()

const hasDataToSend = inputValue.trim().length > 0

Expand Down
9 changes: 9 additions & 0 deletions packages/react/src/ai/AiChat/components/ErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ErrorMessageProps } from "@copilotkit/react-ui"

export const ErrorMessage = ({ error }: ErrorMessageProps) => {
return (
<div className="relative isolate flex w-full flex-col items-start justify-center last:mb-8">
{error.message}
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { OneModal } from "@/experimental/Modals/OneModal"
import { useI18n } from "@/lib/providers/i18n"
import { type AIMessage } from "@copilotkit/shared"
import { useCallback, useEffect, useState } from "react"
import { useAiChatTranslations } from "../providers/AiChatTranslationsProvider"
import { UserReaction } from "./FeedbackProvider"

interface ReactionModalProps {
Expand All @@ -20,11 +21,12 @@ export const FeedbackModal = ({
message,
}: ReactionModalProps) => {
const [text, setText] = useState("")
const translations = useI18n()
const translation = useAiChatTranslations()
const globalTranslations = useI18n()
const { title, label, placeholder } =
reactionType === "like"
? translations.ai.feedbackModal.positive
: translations.ai.feedbackModal.negative
? translation.ai.feedbackModal.positive
: translation.ai.feedbackModal.negative
const handleSubmit = useCallback(() => {
onSubmit(message, text)
}, [text, message, onSubmit])
Expand Down Expand Up @@ -74,11 +76,11 @@ export const FeedbackModal = ({
<div className="flex flex-1 flex-row-reverse gap-3">
<ButtonInternal
onClick={handleSubmit}
label={translations.actions.send}
label={globalTranslations.actions.send}
/>
<ButtonInternal
onClick={handleClose}
label={translations.actions.cancel}
label={globalTranslations.actions.cancel}
variant="outline"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ButtonInternal } from "@/components/F0Button/internal"
import { ArrowDown } from "@/icons/app"
import { useI18n } from "@/lib/providers/i18n"
import { cn } from "@/lib/utils"
import {
useCopilotChatInternal as useCopilotChat,
Expand All @@ -13,6 +12,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useEventListener, useResizeObserver } from "usehooks-ts"
import { isAgentStateMessage } from "../messageTypes"
import { useAiChat } from "../providers/AiChatStateProvider"
import { useAiChatTranslations } from "../providers/AiChatTranslationsProvider"
import { FeedbackModal } from "./FeedbackModal"
import { FeedbackModalProvider, useFeedbackModal } from "./FeedbackProvider"
import { Thinking } from "./Thinking"
Expand Down Expand Up @@ -49,7 +49,7 @@ const Messages = ({
isOpen,
} = useFeedbackModal()

const translations = useI18n()
const translations = useAiChatTranslations()
const {
greeting,
initialMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { F0Icon } from "@/components/F0Icon/F0Icon"
import ChevronRight from "@/icons/app/ChevronRight"
import Lightbulb from "@/icons/app/Lightbulb"
import { useReducedMotion } from "@/lib/a11y"
import { useI18n } from "@/lib/providers/i18n"
import { MessagesProps } from "@copilotkit/react-ui"
import { type Message } from "@copilotkit/shared"
import { motion } from "motion/react"
Expand All @@ -12,6 +11,7 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from "../../../ui/collapsible"
import { useAiChatTranslations } from "../providers/AiChatTranslationsProvider"

type ThinkingProps = {
messages: Message[]
Expand All @@ -24,7 +24,7 @@ type ThinkingProps = {
export const Thinking = ({ messages }: ThinkingProps) => {
const [isExpanded, setIsExpanded] = useState(false)
const shouldReduceMotion = useReducedMotion()
const translations = useI18n()
const translations = useAiChatTranslations()

return (
<div className="mb-1">
Expand Down
19 changes: 19 additions & 0 deletions packages/react/src/ai/AiChat/exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export {
HILActionConfirmation,
type HILActionConfirmationProps,
} from "./HILActionConfirmation"
export {
AiChat,
AiChatProvider,
AiFullscreenChat,
type AiChatProviderProps,
} from "./index"
export { useAiChat } from "./providers/AiChatStateProvider"
export {
AiChatTranslationsProvider,
useAiChatTranslations,
type AiChatTranslations,
type AiChatTranslationsProviderProps,
} from "./providers/AiChatTranslationsProvider"

export { ActionItem, type ActionItemProps } from "./ActionItem"
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
useCopilotAction,
useCopilotChatInternal,
} from "@copilotkit/react-core"
import { CopilotSidebar } from "@copilotkit/react-ui"
import { CopilotChat, CopilotSidebar } from "@copilotkit/react-ui"

import { experimentalComponent } from "@/lib/experimental"

Expand Down Expand Up @@ -156,14 +156,63 @@ const AiChatCmp = () => {
)
}

const AiFullscreenChatCmp = () => {
const { enabled } = useAiChat()

useCopilotAction({
name: "orchestratorThinking",
description: "Display orchestrator thinking process (non-blocking)",
parameters: [
{
name: "message",
description: "User-friendly progress message",
required: true,
},
],
// render only when backend wants to display the thinking
available: "disabled",
render: (props) => {
return (
<div className={props.status ? "-ml-1" : undefined}>
<ActionItem
title={props.args.message ?? "thinking"}
status={props.status === "complete" ? "completed" : props.status}
inGroup={props.result?.inGroup}
/>
</div>
)
},
})

if (!enabled) {
return null
}

return (
<CopilotChat
className="relative flex h-full w-full"
Messages={MessagesContainer}
Input={ChatTextarea}
UserMessage={UserMessage}
AssistantMessage={AssistantMessage}
RenderSuggestionsList={SuggestionsList}
/>
)
}

/**
* @experimental This is an experimental component use it at your own risk
*/
const AiChat = experimentalComponent("AiChat", AiChatCmp)

/**
* @experimental This is an experimental component use it at your own risk
*/
const AiFullscreenChat = experimentalComponent("AiChat", AiFullscreenChatCmp)

const AiChatProvider = experimentalComponent(
"AiChatProvider",
AiChatProviderCmp
)

export { AiChat, AiChatProvider }
export { AiChat, AiChatProvider, AiFullscreenChat }
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createContext, ReactNode, useContext } from "react"

export const aiTranslations = {
ai: {
openChat: "Open Chat with One AI",
closeChat: "Close Chat with One AI",
scrollToBottom: "Scroll to bottom",
welcome: "Ask or create with One",
defaultInitialMessage: "How can I help you today?",
inputPlaceholder: "Ask about time, people, or company info…",
stopAnswerGeneration: "Stop generating",
sendMessage: "Send message",
thoughtsGroupTitle: "Reflection",
thumbsUp: "Like",
thumbsDown: "Dislike",
feedbackModal: {
positive: {
title: "What did you like about this response?",
label: "Your feedback helps us make Factorial AI better",
placeholder: "Share what worked well",
},
negative: {
title: "What could have been better?",
label: "Your feedback helps us improve future answers",
placeholder: "Share what didn’t work",
},
},
},
}

type TranslationShape<T> = {
[K in keyof T]: T[K] extends string
? string
: T[K] extends Record<string, string | Record<string, unknown>>
? TranslationShape<T[K]>
: never
}

export type AiChatTranslations = TranslationShape<typeof aiTranslations>

const AiChatTranslationsContext = createContext<AiChatTranslations | null>(null)

export interface AiChatTranslationsProviderProps {
children: ReactNode
translations: AiChatTranslations
}

export function AiChatTranslationsProvider({
children,
translations,
}: AiChatTranslationsProviderProps): JSX.Element {
return (
<AiChatTranslationsContext.Provider value={translations}>
{children}
</AiChatTranslationsContext.Provider>
)
}

export function useAiChatTranslations(): AiChatTranslations {
const context = useContext(AiChatTranslationsContext)

if (context === null) {
throw new Error(
"useAiChatTranslations must be used within an AiChatTranslationsProvider"
)
}

return context
}
8 changes: 0 additions & 8 deletions packages/react/src/experimental/AiChat/exports.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { useAiPromotionChat } from "@/experimental/AiPromotionChat/providers/AiP
import { breakpoints } from "@factorialco/f0-core"
import { Fragment, useEffect, useRef } from "react"
import { useMediaQuery } from "usehooks-ts"
import { AiChat, AiChatProvider, AiChatProviderProps } from "../../AiChat"
import { useAiChat } from "../../AiChat/providers/AiChatStateProvider"
import { AiChat, AiChatProvider, AiChatProviderProps } from "../../../ai/AiChat"
import { useAiChat } from "../../../ai/AiChat/providers/AiChatStateProvider"
import { FrameProvider, SidebarState, useSidebar } from "./FrameProvider"

export interface ApplicationFrameProps {
Expand Down
Loading
Loading