Chain of Thought
Step-by-step reasoning timeline with search results and images per step.
NVDAx 30-day price
NVDAx 30-day price
NVDAx 30-day price
import { ChainOfThought, ChainOfThoughtContent, ChainOfThoughtHeader, ChainOfThoughtImage, ChainOfThoughtSearchResult, ChainOfThoughtSearchResults, ChainOfThoughtStep,} from "@/components/ai/chain-of-thought";import { Shimmer } from "@/components/ai/shimmer";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const globe = ( <IconPlaceholder lucide="GlobeIcon" tabler="IconWorld" hugeicons="Globe02Icon" phosphor="GlobeIcon" remixicon="RiGlobalLine" className="size-3" />);
export default function Demo() { return ( <ChainOfThought defaultOpen className="max-w-lg"> <ChainOfThoughtHeader /> <ChainOfThoughtContent> <ChainOfThoughtStep icon={ <IconPlaceholder lucide="SearchIcon" tabler="IconSearch" hugeicons="SearchIcon" phosphor="MagnifyingGlassIcon" remixicon="RiSearchLine" /> } label="Searched for NVDAx news" > <ChainOfThoughtSearchResults> {["reuters.com", "sec.gov", "nasdaq.com"].map((site) => ( <ChainOfThoughtSearchResult key={site}> {globe} {site} </ChainOfThoughtSearchResult> ))} </ChainOfThoughtSearchResults> </ChainOfThoughtStep> <ChainOfThoughtStep icon={ <IconPlaceholder lucide="ChartLineIcon" tabler="IconChartLine" hugeicons="ChartLineData01Icon" phosphor="ChartLineIcon" remixicon="RiLineChartLine" /> } label="Read the 30-day chart" > <ChainOfThoughtImage caption="NVDAx 30-day price"> <div className="h-28 w-64 bg-linear-to-br from-chart-2 to-chart-1" /> </ChainOfThoughtImage> </ChainOfThoughtStep> <ChainOfThoughtStep status="active" icon={ <IconPlaceholder lucide="BrainIcon" tabler="IconBrain" hugeicons="AiBrainIcon" phosphor="BrainIcon" remixicon="RiBrainLine" /> } label={ <Shimmer as="span">Comparing weights with the mandate</Shimmer> } description="MAG4 · drift limit 5%" /> <ChainOfThoughtStep status="pending" label="Write the answer" /> </ChainOfThoughtContent> </ChainOfThought> );}<script setup lang="ts">import { ChainOfThought, ChainOfThoughtContent, ChainOfThoughtHeader, ChainOfThoughtImage, ChainOfThoughtSearchResult, ChainOfThoughtSearchResults, ChainOfThoughtStep,} from "@/components/ai/chain-of-thought";import { Shimmer } from "@/components/ai/shimmer";import { ChartLineIcon, GlobeIcon, SearchIcon, SparklesIcon } from "@lucide/vue";</script>
<template> <ChainOfThought default-open class="max-w-lg"> <ChainOfThoughtHeader /> <ChainOfThoughtContent> <ChainOfThoughtStep :icon="SearchIcon" label="Searched for NVDAx news"> <ChainOfThoughtSearchResults> <ChainOfThoughtSearchResult v-for="site in ['reuters.com', 'sec.gov', 'nasdaq.com']" :key="site"> <GlobeIcon class="size-3" /> {{ site }} </ChainOfThoughtSearchResult> </ChainOfThoughtSearchResults> </ChainOfThoughtStep> <ChainOfThoughtStep :icon="ChartLineIcon" label="Read the 30-day chart"> <ChainOfThoughtImage caption="NVDAx 30-day price"> <div class="h-28 w-64 bg-linear-to-br from-chart-2 to-chart-1" /> </ChainOfThoughtImage> </ChainOfThoughtStep> <ChainOfThoughtStep status="active" :icon="SparklesIcon" description="MAG4 · drift limit 5%"> <template #label> <Shimmer as="span">Comparing weights with the mandate</Shimmer> </template> </ChainOfThoughtStep> <ChainOfThoughtStep status="pending" label="Write the answer" /> </ChainOfThoughtContent> </ChainOfThought></template><script lang="ts"> import { ChainOfThought, ChainOfThoughtContent, ChainOfThoughtHeader, ChainOfThoughtImage, ChainOfThoughtSearchResult, ChainOfThoughtSearchResults, ChainOfThoughtStep, } from "$lib/components/ai/chain-of-thought"; import { Shimmer } from "$lib/components/ai/shimmer"; import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte";
const sites = ["reuters.com", "sec.gov", "nasdaq.com"];</script>
{#snippet searchIcon()} <IconPlaceholder lucide="SearchIcon" tabler="IconSearch" hugeicons="SearchIcon" phosphor="MagnifyingGlassIcon" remixicon="RiSearchLine" />{/snippet}{#snippet chartIcon()} <IconPlaceholder lucide="ChartLineIcon" tabler="IconChartLine" hugeicons="ChartLineData01Icon" phosphor="ChartLineIcon" remixicon="RiLineChartLine" />{/snippet}{#snippet brainIcon()} <IconPlaceholder lucide="BrainIcon" tabler="IconBrain" hugeicons="AiBrainIcon" phosphor="BrainIcon" remixicon="RiBrainLine" />{/snippet}{#snippet comparing()} <Shimmer as="span">Comparing weights with the mandate</Shimmer>{/snippet}
<ChainOfThought defaultOpen class="max-w-lg"> <ChainOfThoughtHeader /> <ChainOfThoughtContent> <ChainOfThoughtStep icon={searchIcon} label="Searched for NVDAx news"> <ChainOfThoughtSearchResults> {#each sites as site (site)} <ChainOfThoughtSearchResult>{site}</ChainOfThoughtSearchResult> {/each} </ChainOfThoughtSearchResults> </ChainOfThoughtStep> <ChainOfThoughtStep icon={chartIcon} label="Read the 30-day chart"> <ChainOfThoughtImage caption="NVDAx 30-day price"> <div class="h-28 w-64 bg-linear-to-br from-chart-2 to-chart-1"></div> </ChainOfThoughtImage> </ChainOfThoughtStep> <ChainOfThoughtStep status="active" icon={brainIcon} label={comparing} description="MAG4 · drift limit 5%" /> <ChainOfThoughtStep status="pending" label="Write the answer" /> </ChainOfThoughtContent></ChainOfThought>Built on badge and collapsible.
Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/ai-chain-of-thoughtpnpm dlx shadcn@latest add @edmi-ui/ai-chain-of-thoughtyarn dlx shadcn@latest add @edmi-ui/ai-chain-of-thoughtbunx shadcn@latest add @edmi-ui/ai-chain-of-thoughtPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
badge,collapsible.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/ai-chain-of-thoughtpnpm dlx shadcn-vue@latest add @edmi-ui/ai-chain-of-thoughtyarn dlx shadcn-vue@latest add @edmi-ui/ai-chain-of-thoughtbunx shadcn-vue@latest add @edmi-ui/ai-chain-of-thoughtPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @lucide/vue @vueuse/coreTerminal window pnpm add @lucide/vue @vueuse/coreTerminal window yarn add @lucide/vue @vueuse/coreTerminal window bun add @lucide/vue @vueuse/coreAdd the Edmi components this one builds on:
badge,collapsible.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-chain-of-thought.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-chain-of-thought.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-chain-of-thought.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-chain-of-thought.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
badge,collapsible.Copy the code from Component source into your project and update the import paths to match your aliases.
import { ChainOfThought, ChainOfThoughtHeader,} from "@/components/ai/chain-of-thought";
<ChainOfThought defaultOpen className="max-w-lg"> <ChainOfThoughtHeader /> {/* … */}</ChainOfThought><script setup lang="ts">import { ChainOfThought, ChainOfThoughtContent, ChainOfThoughtHeader, ChainOfThoughtImage, ChainOfThoughtSearchResult, ChainOfThoughtSearchResults, ChainOfThoughtStep,} from "@/components/ai/chain-of-thought";import { Shimmer } from "@/components/ai/shimmer";</script>
<template> <ChainOfThought default-open class="max-w-lg"> <ChainOfThoughtHeader /> <ChainOfThoughtContent> <ChainOfThoughtStep :icon="SearchIcon" label="Searched for NVDAx news"> <ChainOfThoughtSearchResults> <ChainOfThoughtSearchResult v-for="site in ['reuters.com', 'sec.gov', 'nasdaq.com']" :key="site"> <GlobeIcon class="size-3" /> {{ site }} </ChainOfThoughtSearchResult> </ChainOfThoughtSearchResults> </ChainOfThoughtStep> <ChainOfThoughtStep :icon="ChartLineIcon" label="Read the 30-day chart"> <ChainOfThoughtImage caption="NVDAx 30-day price"> <div class="h-28 w-64 bg-linear-to-br from-chart-2 to-chart-1" /> </ChainOfThoughtImage> </ChainOfThoughtStep> <ChainOfThoughtStep status="active" :icon="SparklesIcon" description="MAG4 · drift limit 5%"> <template #label> <Shimmer as="span">Comparing weights with the mandate</Shimmer> </template> </ChainOfThoughtStep> <ChainOfThoughtStep status="pending" label="Write the answer" /> </ChainOfThoughtContent> </ChainOfThought></template><script lang="ts"> import { ChainOfThought, ChainOfThoughtContent, ChainOfThoughtHeader, ChainOfThoughtImage, ChainOfThoughtSearchResult, ChainOfThoughtSearchResults, ChainOfThoughtStep, } from "$lib/components/ai/chain-of-thought";</script>
<ChainOfThought defaultOpen class="max-w-lg"> <ChainOfThoughtHeader /> <ChainOfThoughtContent> <ChainOfThoughtStep label="Searched for NVDAx news"> <ChainOfThoughtSearchResults> {#each sites as site (site)} <ChainOfThoughtSearchResult>{site}</ChainOfThoughtSearchResult> {/each} </ChainOfThoughtSearchResults> </ChainOfThoughtStep> <ChainOfThoughtStep label="Read the 30-day chart"> <ChainOfThoughtImage caption="NVDAx 30-day price"> <div class="h-28 w-64 bg-linear-to-br from-chart-2 to-chart-1"></div> </ChainOfThoughtImage> </ChainOfThoughtStep> <ChainOfThoughtStep status="active" description="MAG4 · drift limit 5%" /> <ChainOfThoughtStep status="pending" label="Write the answer" /> </ChainOfThoughtContent></ChainOfThought>Component source
Section titled “Component source”"use client";
import { cn } from "cn";import type { ComponentProps, ReactNode } from "react";import { createContext, memo, useContext, useMemo } from "react";import { BrainIcon, CaretDownIcon } from "@phosphor-icons/react";import { useControllableState } from "@/registry/edmi/hooks/ai/use-controllable-state";import { Badge } from "@/registry/edmi/ui/badge";import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/registry/edmi/ui/collapsible";
interface ChainOfThoughtContextValue { isOpen: boolean; setIsOpen: (open: boolean) => void;}
const ChainOfThoughtContext = createContext<ChainOfThoughtContextValue | null>( null,);
const useChainOfThought = () => { const context = useContext(ChainOfThoughtContext); if (!context) { throw new Error( "ChainOfThought components must be used within ChainOfThought", ); } return context;};
export type ChainOfThoughtProps = ComponentProps<"div"> & { open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void;};
export const ChainOfThought = memo( ({ className, open, defaultOpen = false, onOpenChange, children, ...props }: ChainOfThoughtProps) => { const [isOpen, setIsOpen] = useControllableState({ defaultProp: defaultOpen, onChange: onOpenChange, prop: open, });
const chainOfThoughtContext = useMemo( () => ({ isOpen, setIsOpen }), [isOpen, setIsOpen], );
return ( <ChainOfThoughtContext.Provider value={chainOfThoughtContext}> <div data-slot="ai-chain-of-thought" className={cn("not-prose w-full space-y-3", className)} {...props} > {children} </div> </ChainOfThoughtContext.Provider> ); },);
export type ChainOfThoughtHeaderProps = ComponentProps< typeof CollapsibleTrigger>;
export const ChainOfThoughtHeader = memo( ({ className, children, ...props }: ChainOfThoughtHeaderProps) => { const { isOpen, setIsOpen } = useChainOfThought();
return ( <Collapsible onOpenChange={setIsOpen} open={isOpen}> <CollapsibleTrigger data-slot="ai-chain-of-thought-header" className={cn( "flex w-full items-center gap-2 text-[13.5px] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", className, )} {...props} > <BrainIcon className="size-4" /> <span className="flex-1 text-left"> {children ?? "Chain of thought"} </span> <CaretDownIcon className={cn( "size-4 transition-transform", isOpen ? "rotate-180" : "rotate-0", )} /> </CollapsibleTrigger> </Collapsible> ); },);
export type ChainOfThoughtStepProps = Omit<ComponentProps<"div">, "title"> & { /** Any element (an icon, an svg). Defaults to a small dot. */ icon?: ReactNode; label: ReactNode; description?: ReactNode; status?: "complete" | "active" | "pending";};
// Icon colour and label colour per status (board AI 02): complete = muted icon, soft label; active = brand icon,// foreground label; pending = faint.const stepIconStyles = { active: "text-brand", complete: "text-muted-foreground", pending: "text-muted-foreground-2",};
const stepLabelStyles = { active: "text-foreground", complete: "text-foreground-2", pending: "text-muted-foreground-2",};
export const ChainOfThoughtStep = memo( ({ className, icon, label, description, status = "complete", children, ...props }: ChainOfThoughtStepProps) => ( <div data-slot="ai-chain-of-thought-step" data-status={status} className={cn( "group/step relative grid grid-cols-[22px_1fr] gap-2.5 pb-3.5 text-[13.5px] last:pb-0", className, )} {...props} > <span className={cn( "relative z-10 inline-flex size-[22px] items-center justify-center rounded-full bg-card [&_svg]:size-3.5", stepIconStyles[status], )} > {icon ?? <span className="size-1.5 rounded-full bg-current" />} </span> <span className="absolute top-6 -bottom-2 left-[10.5px] w-px bg-border group-last/step:hidden" /> <div className="min-w-0 space-y-2"> <div className={stepLabelStyles[status]}>{label}</div> {description && ( <div className="-mt-1.5 text-xs text-muted-foreground"> {description} </div> )} {children} </div> </div> ),);
export type ChainOfThoughtSearchResultsProps = ComponentProps<"div">;
export const ChainOfThoughtSearchResults = memo( ({ className, ...props }: ChainOfThoughtSearchResultsProps) => ( <div data-slot="ai-chain-of-thought-search-results" className={cn("flex flex-wrap items-center gap-1.5", className)} {...props} /> ),);
export type ChainOfThoughtSearchResultProps = ComponentProps<typeof Badge>;
export const ChainOfThoughtSearchResult = memo( ({ className, children, ...props }: ChainOfThoughtSearchResultProps) => ( <Badge data-slot="ai-chain-of-thought-search-result" className={cn( "h-5 gap-1 px-[7px] text-[11.5px] font-normal text-foreground-2", className, )} shape="pill" variant="secondary" {...props} > {children} </Badge> ),);
export type ChainOfThoughtContentProps = ComponentProps< typeof CollapsibleContent>;
export const ChainOfThoughtContent = memo( ({ className, children, ...props }: ChainOfThoughtContentProps) => { const { isOpen } = useChainOfThought();
return ( <Collapsible open={isOpen}> <CollapsibleContent data-slot="ai-chain-of-thought-content" className={cn( "h-(--collapsible-panel-height) overflow-hidden outline-none transition-[height] duration-200 ease-out data-[ending-style]:h-0 data-[starting-style]:h-0", className, )} {...props} > <div className="pl-0.5">{children}</div> </CollapsibleContent> </Collapsible> ); },);
export type ChainOfThoughtImageProps = ComponentProps<"div"> & { caption?: string;};
export const ChainOfThoughtImage = memo( ({ className, children, caption, ...props }: ChainOfThoughtImageProps) => ( <div data-slot="ai-chain-of-thought-image" className={cn("w-fit max-w-full space-y-1", className)} {...props} > <div className="relative flex max-h-[22rem] items-center justify-center overflow-hidden rounded-[10px] border border-border bg-muted"> {children} </div> {caption && <p className="text-xs text-muted-foreground">{caption}</p>} </div> ),);
ChainOfThought.displayName = "ChainOfThought";ChainOfThoughtHeader.displayName = "ChainOfThoughtHeader";ChainOfThoughtStep.displayName = "ChainOfThoughtStep";ChainOfThoughtSearchResults.displayName = "ChainOfThoughtSearchResults";ChainOfThoughtSearchResult.displayName = "ChainOfThoughtSearchResult";ChainOfThoughtContent.displayName = "ChainOfThoughtContent";ChainOfThoughtImage.displayName = "ChainOfThoughtImage";<script setup lang="ts">import type { HTMLAttributes, Ref } from "vue"import { useVModel } from "@vueuse/core"import { provide } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { ChainOfThoughtKey } from "./context"
const props = withDefaults(defineProps<{ class?: HTMLAttributes["class"] open?: boolean defaultOpen?: boolean}>(), { open: undefined, defaultOpen: false,})
const emit = defineEmits<{ (e: "update:open", value: boolean): void}>()
const isOpen = useVModel(props, "open", emit, { defaultValue: props.defaultOpen, passive: true,}) as Ref<boolean>
provide(ChainOfThoughtKey, { isOpen, setIsOpen: (value: boolean) => { isOpen.value = value },})</script>
<template> <div data-slot="ai-chain-of-thought" :class="cn('not-prose w-full space-y-3', props.class)"> <slot /> </div></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { Collapsible, CollapsibleContent } from "@/registry/edmi/ui/collapsible"import { useChainOfThoughtContext } from "./context"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()
const { isOpen } = useChainOfThoughtContext()</script>
<template> <Collapsible :open="isOpen"> <CollapsibleContent data-slot="ai-chain-of-thought-content" :class="cn( 'overflow-hidden outline-none data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down', props.class, )" > <div class="pl-0.5"> <slot /> </div> </CollapsibleContent> </Collapsible></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { ChevronDownIcon, SparklesIcon } from "@lucide/vue"import { cn } from "@/registry/edmi/lib/utils"import { Collapsible, CollapsibleTrigger } from "@/registry/edmi/ui/collapsible"import { useChainOfThoughtContext } from "./context"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()
const { isOpen, setIsOpen } = useChainOfThoughtContext()</script>
<template> <Collapsible :open="isOpen" @update:open="setIsOpen"> <CollapsibleTrigger data-slot="ai-chain-of-thought-header" :class="cn( 'flex w-full items-center gap-2 text-[13.5px] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring', props.class, )" > <SparklesIcon class="size-4" /> <span class="flex-1 text-left"><slot>Chain of thought</slot></span> <ChevronDownIcon :class="cn('size-4 transition-transform', isOpen ? 'rotate-180' : 'rotate-0')" /> </CollapsibleTrigger> </Collapsible></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] caption?: string}>()</script>
<template> <div data-slot="ai-chain-of-thought-image" :class="cn('w-fit max-w-full space-y-1', props.class)"> <div class="relative flex max-h-[22rem] items-center justify-center overflow-hidden rounded-[10px] border border-border bg-muted"> <slot /> </div> <p v-if="caption" class="text-xs text-muted-foreground"> {{ caption }} </p> </div></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { Badge } from "@/registry/edmi/ui/badge"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <Badge data-slot="ai-chain-of-thought-search-result" shape="pill" variant="secondary" :class="cn('h-5 gap-1 px-[7px] text-[11.5px] font-normal text-foreground-2', props.class)" > <slot /> </Badge></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <div data-slot="ai-chain-of-thought-search-results" :class="cn('flex flex-wrap items-center gap-1.5', props.class)"> <slot /> </div></template><script setup lang="ts">import type { Component, HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = withDefaults(defineProps<{ class?: HTMLAttributes["class"] /** Icon component; the `icon` slot works too. Defaults to a small dot. */ icon?: Component label?: string description?: string status?: "complete" | "active" | "pending"}>(), { status: "complete",})
// Icon colour and label colour per status (board AI 02): complete = muted icon, soft label; active = brand icon,// foreground label; pending = faint.const iconStyles = { active: "text-brand", complete: "text-muted-foreground", pending: "text-muted-foreground-2",}const labelStyles = { active: "text-foreground", complete: "text-foreground-2", pending: "text-muted-foreground-2",}</script>
<template> <div data-slot="ai-chain-of-thought-step" :data-status="status" :class="cn( 'group/step relative grid grid-cols-[22px_1fr] gap-2.5 pb-3.5 text-[13.5px] last:pb-0', props.class, )" > <span :class="cn( 'relative z-10 inline-flex size-[22px] items-center justify-center rounded-full bg-card [&_svg]:size-3.5', iconStyles[status], )" > <slot name="icon"> <component :is="icon" v-if="icon" /> <span v-else class="size-1.5 rounded-full bg-current" /> </slot> </span> <span class="absolute top-6 -bottom-2 left-[10.5px] w-px bg-border group-last/step:hidden" /> <div class="min-w-0 space-y-2"> <div :class="labelStyles[status]"> <slot name="label"> {{ label }} </slot> </div> <div v-if="description || $slots.description" class="-mt-1.5 text-xs text-muted-foreground"> <slot name="description"> {{ description }} </slot> </div> <slot /> </div> </div></template>import type { InjectionKey, Ref } from "vue";import { inject } from "vue";
export interface ChainOfThoughtContextValue { isOpen: Ref<boolean>; setIsOpen: (open: boolean) => void;}
export const ChainOfThoughtKey: InjectionKey<ChainOfThoughtContextValue> = Symbol("ChainOfThoughtContext");
export function useChainOfThoughtContext() { const ctx = inject(ChainOfThoughtKey); if (!ctx) throw new Error( "ChainOfThought components must be used within <ChainOfThought>", ); return ctx;}export { default as ChainOfThought } from "./ChainOfThought.vue";export { default as ChainOfThoughtContent } from "./ChainOfThoughtContent.vue";export { default as ChainOfThoughtHeader } from "./ChainOfThoughtHeader.vue";export { default as ChainOfThoughtImage } from "./ChainOfThoughtImage.vue";export { default as ChainOfThoughtSearchResult } from "./ChainOfThoughtSearchResult.vue";export { default as ChainOfThoughtSearchResults } from "./ChainOfThoughtSearchResults.vue";export { default as ChainOfThoughtStep } from "./ChainOfThoughtStep.vue";export * from "./context";<script lang="ts"> import { cn } from "$lib/utils.js"; import { Collapsible, CollapsibleContent } from "$lib/registry/ui/collapsible/index.js"; import type { ComponentProps } from "svelte"; import { useChainOfThought } from "./use-chain-of-thought.svelte.js";
let { class: className, children, ...restProps }: ComponentProps<typeof CollapsibleContent> = $props();
const cot = useChainOfThought();</script>
<Collapsible open={cot.isOpen}> <CollapsibleContent data-slot="ai-chain-of-thought-content" class={cn( "overflow-hidden outline-none data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down", className )} {...restProps} > <div class="pl-0.5"> {@render children?.()} </div> </CollapsibleContent></Collapsible><script lang="ts"> import BrainIcon from "phosphor-svelte/lib/Brain"; import CaretDownIcon from "phosphor-svelte/lib/CaretDown"; import { cn } from "$lib/utils.js"; import { Collapsible, CollapsibleTrigger } from "$lib/registry/ui/collapsible/index.js"; import type { ComponentProps } from "svelte"; import { useChainOfThought } from "./use-chain-of-thought.svelte.js";
let { class: className, children, ...restProps }: ComponentProps<typeof CollapsibleTrigger> = $props();
const cot = useChainOfThought();</script>
<Collapsible open={cot.isOpen} onOpenChange={cot.setIsOpen}> <CollapsibleTrigger data-slot="ai-chain-of-thought-header" class={cn( "flex w-full items-center gap-2 text-[13.5px] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", className )} {...restProps} > <BrainIcon class="size-4" /> <span class="flex-1 text-left"> {#if children}{@render children()}{:else}Chain of thought{/if} </span> <CaretDownIcon class={cn("size-4 transition-transform", cot.isOpen ? "rotate-180" : "rotate-0")} /> </CollapsibleTrigger></Collapsible><script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, caption, children, ...restProps }: HTMLAttributes<HTMLDivElement> & { caption?: string } = $props();</script>
<div data-slot="ai-chain-of-thought-image" class={cn("w-fit max-w-full space-y-1", className)} {...restProps}> <div class="relative flex max-h-[22rem] items-center justify-center overflow-hidden rounded-[10px] border border-border bg-muted" > {@render children?.()} </div> {#if caption} <p class="text-xs text-muted-foreground">{caption}</p> {/if}</div><script lang="ts"> import { cn } from "$lib/utils.js"; import { Badge } from "$lib/registry/ui/badge/index.js"; import type { ComponentProps } from "svelte";
let { class: className, children, ...restProps }: ComponentProps<typeof Badge> = $props();</script>
<Badge data-slot="ai-chain-of-thought-search-result" shape="pill" variant="secondary" class={cn("h-5 gap-1 px-[7px] text-[11.5px] font-normal text-foreground-2", className)} {...restProps}> {@render children?.()}</Badge><script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();</script>
<div data-slot="ai-chain-of-thought-search-results" class={cn("flex flex-wrap items-center gap-1.5", className)} {...restProps}> {@render children?.()}</div><script lang="ts"> import { cn } from "$lib/utils.js"; import type { Snippet } from "svelte"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, icon, label, description, status = "complete", children, ...restProps }: Omit<HTMLAttributes<HTMLDivElement>, "children"> & { /** Snippet that renders the icon (an `IconPlaceholder`, an svg). Defaults to a small dot. */ icon?: Snippet; label?: string | Snippet; description?: string | Snippet; status?: "complete" | "active" | "pending"; children?: Snippet; } = $props();
// Icon colour and label colour per status (board AI 02): complete = muted icon, soft label; active = brand icon, // foreground label; pending = faint. const iconStyles = { active: "text-brand", complete: "text-muted-foreground", pending: "text-muted-foreground-2", }; const labelStyles = { active: "text-foreground", complete: "text-foreground-2", pending: "text-muted-foreground-2", };</script>
<div data-slot="ai-chain-of-thought-step" data-status={status} class={cn( "group/step relative grid grid-cols-[22px_1fr] gap-2.5 pb-3.5 text-[13.5px] last:pb-0", className )} {...restProps}> <span class={cn( "relative z-10 inline-flex size-[22px] items-center justify-center rounded-full bg-card [&_svg]:size-3.5", iconStyles[status] )} > {#if icon} {@render icon()} {:else} <span class="size-1.5 rounded-full bg-current"></span> {/if} </span> <span class="absolute top-6 -bottom-2 left-[10.5px] w-px bg-border group-last/step:hidden"></span> <div class="min-w-0 space-y-2"> <div class={labelStyles[status]}> {#if typeof label === "string"}{label}{:else if label}{@render label()}{/if} </div> {#if description} <div class="-mt-1.5 text-xs text-muted-foreground"> {#if typeof description === "string"}{description}{:else}{@render description()}{/if} </div> {/if} {@render children?.()} </div></div><script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements"; import { setChainOfThoughtContext } from "./use-chain-of-thought.svelte.js";
let { class: className, open = $bindable(), defaultOpen = false, children, ...restProps }: HTMLAttributes<HTMLDivElement> & { open?: boolean; defaultOpen?: boolean } = $props();
// svelte-ignore state_referenced_locally if (open === undefined) open = defaultOpen;
setChainOfThoughtContext({ get isOpen() { return !!open; }, setIsOpen: (value: boolean) => { open = value; }, });</script>
<div data-slot="ai-chain-of-thought" class={cn("not-prose w-full space-y-3", className)} {...restProps}> {@render children?.()}</div>import Root from "./chain-of-thought.svelte";import Content from "./chain-of-thought-content.svelte";import Header from "./chain-of-thought-header.svelte";import Image from "./chain-of-thought-image.svelte";import SearchResult from "./chain-of-thought-search-result.svelte";import SearchResults from "./chain-of-thought-search-results.svelte";import Step from "./chain-of-thought-step.svelte";
export * from "./use-chain-of-thought.svelte.js";export { Content, Content as ChainOfThoughtContent, Header, Header as ChainOfThoughtHeader, Image, Image as ChainOfThoughtImage, Root, // Root as ChainOfThought, SearchResult, SearchResult as ChainOfThoughtSearchResult, SearchResults, SearchResults as ChainOfThoughtSearchResults, Step, Step as ChainOfThoughtStep,};import { getContext, setContext } from "svelte";
/** Getter object so consumers stay reactive. */export interface ChainOfThoughtContextValue { readonly isOpen: boolean; setIsOpen: (open: boolean) => void;}
const KEY = Symbol("ai-chain-of-thought");
export function setChainOfThoughtContext(value: ChainOfThoughtContextValue) { return setContext(KEY, value);}
export function useChainOfThought(): ChainOfThoughtContextValue { const ctx = getContext<ChainOfThoughtContextValue | undefined>(KEY); if (!ctx) throw new Error( "ChainOfThought components must be used within <ChainOfThought>", ); return ctx;}| Part | Prop | Type | Default |
|---|---|---|---|
| ChainOfThought | open, defaultOpen, onOpenChange |
controlled / uncontrolled open state | defaultOpen = false |
| ChainOfThoughtHeader | children |
label | Chain of thought |
| ChainOfThoughtStep | icon |
ReactNode (an IconPlaceholder or svg) |
small dot |
| ChainOfThoughtStep | label, description |
ReactNode |
|
| ChainOfThoughtStep | status |
complete | active | pending |
complete |
| ChainOfThoughtSearchResult | Badge props | secondary pill | |
| ChainOfThoughtImage | caption |
string |
✦ Edmi additions
Section titled “✦ Edmi additions”icontakes an element instead of an icon component, so it follows the consumer’siconLibrary.- Statuses:
activeshows a--brandicon and a foreground label (pair it withShimmer),pendingis faint. - A 1px
--borderline joins the step icons and stops after the last step.