Composer

Data Input

메시지를 입력하고 전송하는 채팅 입력창.

Usage

사용자가 새 메시지를 작성해 스레드로 전송할 때 사용한다.

import

import

import { Composer } from '@mildang/design-system/unofficial/Chat';

Composer 하나만 가져오면 Composer.Input · Composer.RichInput · Composer.Header · Composer.Footer · Composer.LeadingActions · Composer.TrailingActions · Composer.Status · Composer.Send · Composer.Cancel · Composer.AddAttachment · Composer.Dictate · Composer.StopDictation · Composer.DictationTranscript · Composer.Attachments · Composer.If · Composer.Queue · Composer.AttachmentDropzone · Composer.Drawer · Composer.TriggerPopoverRoot · Composer.TriggerPopover 를 그 아래에서 쓸 수 있다.

예제를 복사해 쓸 때 필요한 준비

@mildang/icons 를 따로 설치한다. DS 패키지에 아이콘 컴포넌트가 포함되지 않는다.

@mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.

외부 패키지를 따로 설치한다. DS 의 전이 의존성에 기대지 않는다.

Anatomy

tsx

import { Composer } from '@mildang/design-system/unofficial/Chat';

export default function Example() {
  return (
    <Composer.Header>
      <Composer.Attachments />
    </Composer.Header>
    <Composer.Input />
    <Composer.RichInput />
    <Composer.DictationTranscript />
    <Composer.Footer>
      <Composer.Status />
      <Composer.LeadingActions />
      <Composer.TrailingActions />
      <Composer.AddAttachment />
      <Composer.Dictate />
      <Composer.StopDictation />
      <Composer.Send />
      <Composer.Cancel />
    </Composer.Footer>
    <Composer.AttachmentDropzone />
    <Composer.Queue />
    <Composer.Drawer />
    <Composer.TriggerPopoverRoot />
    <Composer.TriggerPopover />
    <Composer.If />
  );
}

부품

필수 여부

반복

위치

Composer.Header

선택

Composer.Attachments

선택

Composer.Header 안에 둔다.

Composer.Input

조건부

RichInput을 쓰지 않을 때 텍스트 입력에 사용한다.

Composer.RichInput

조건부

Input 대신 멘션/슬래시 커맨드가 필요할 때 사용한다.

Composer.DictationTranscript

조건부

런타임에 DictationAdapter가 있어야 동작한다.

Composer.Footer

선택

Composer.Status

조건부

표시할 상태가 있을 때만 렌더한다.

Composer.Footer 안에 둔다.

Composer.LeadingActions

선택

Composer.Footer 안에 둔다.

Composer.TrailingActions

선택

Composer.Footer 안에 둔다.

Composer.AddAttachment

선택

Composer.Footer 안에 둔다.

Composer.Dictate

조건부

런타임에 DictationAdapter가 있어야 동작한다.

Composer.Footer 안에 둔다.

Composer.StopDictation

조건부

런타임에 DictationAdapter가 있어야 동작한다.

Composer.Footer 안에 둔다.

Composer.Send

필수

Composer.Footer 안에 둔다.

Composer.Cancel

선택

Composer.Footer 안에 둔다.

Composer.AttachmentDropzone

선택

Composer.Queue

선택

Composer.Drawer

선택

Composer.TriggerPopoverRoot

조건부

멘션/슬래시 커맨드 팝오버를 쓸 때만 RichInput을 감싼다.

Composer.TriggerPopover

조건부

Composer.TriggerPopoverRoot 범위 안에서만 동작한다.

Composer.TriggerPopoverRoot 안에 둔다.

Composer.If

선택

  • Input과 RichInput은 함께 쓰지 않고 텍스트 입력 방식 하나만 선택한다.
  • 멘션·슬래시 커맨드가 필요하면 TriggerPopoverRoot로 RichInput을 감싼 뒤 TriggerPopover를 배치한다.
  • 받아쓰기 관련 파트(Dictate/StopDictation/DictationTranscript)는 런타임에 DictationAdapter가 있어야 동작한다.

API Reference

Composer Props

Prop

Type

Default

asChild

boolean

지정 안 함

compact

boolean

false

drawer

ReactNode

지정 안 함

elevation

"raised" | "flat"

raised

render

ReactElement<unknown, string | JSXElementConstructor<any>>

지정 안 함

severity

"error" | "warning"

지정 안 함

variant

"default" | "edit"

default

Composer.Header

입력창 위쪽에 첨부 목록 등 보조 콘텐츠를 두는 영역이다.

공개 Props 없음

Composer.Attachments

현재 첨부된 파일 목록을 렌더한다.

Prop

Type

Default

children

(value: { attachment: Attachment; }) => ReactNode

지정 안 함

components

ComposerAttachmentsComponentConfig

지정 안 함

Composer.Input

일반 텍스트 입력 영역이다.

공개 Props 없음

Composer.RichInput

멘션·슬래시 커맨드를 지원하는 서식 있는 입력 영역이다.

Prop

Type

Default

autoFocus

boolean

지정 안 함

cancelOnEscape

boolean

지정 안 함

children

ReactNode

지정 안 함

directiveChip

FC<ComposerDirectiveChipProps>

지정 안 함

placeholder

string

지정 안 함

submitMode

"none" | "enter" | "ctrlEnter"

지정 안 함

Composer.DictationTranscript

받아쓰기 중 실시간 인식 텍스트를 보여준다.

Prop

Type

Default

asChild

boolean

지정 안 함

render

import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>

지정 안 함

Composer.Footer

입력창 아래쪽에 상태·액션 버튼을 두는 영역이다.

공개 Props 없음

Composer.Status

전송 오류·경고 같은 상태 메시지를 보여준다.

Prop

Type

Default

severity

"error" | "warning"

지정 안 함

Composer.LeadingActions

입력창 앞쪽에 커스텀 액션 버튼을 두는 영역이다.

공개 Props 없음

Composer.TrailingActions

입력창 뒤쪽에 커스텀 액션 버튼을 두는 영역이다.

공개 Props 없음

Composer.AddAttachment

파일 첨부를 추가하는 버튼이다.

Prop

Type

Default

asChild

boolean

지정 안 함

children

ReactNode

지정 안 함

className

string

지정 안 함

disabled

boolean

지정 안 함

icon

ReactNode

지정 안 함

loading

boolean

지정 안 함

size

IconButtonSize

지정 안 함

variant

IconButtonVariant

지정 안 함

Composer.Dictate

음성 받아쓰기를 시작하는 버튼이다.

Prop

Type

Default

asChild

boolean

지정 안 함

children

ReactNode

지정 안 함

className

string

지정 안 함

disabled

boolean

지정 안 함

icon

ReactNode

지정 안 함

loading

boolean

지정 안 함

size

IconButtonSize

지정 안 함

variant

IconButtonVariant

지정 안 함

Composer.StopDictation

진행 중인 받아쓰기를 멈추는 버튼이다.

Prop

Type

Default

asChild

boolean

지정 안 함

children

ReactNode

지정 안 함

className

string

지정 안 함

disabled

boolean

지정 안 함

icon

ReactNode

지정 안 함

loading

boolean

지정 안 함

size

IconButtonSize

지정 안 함

variant

IconButtonVariant

지정 안 함

Composer.Send

메시지를 전송하는 버튼이다.

Prop

Type

Default

asChild

boolean

지정 안 함

children

ReactNode

지정 안 함

className

string

지정 안 함

disabled

boolean

지정 안 함

icon

ReactNode

지정 안 함

loading

boolean

지정 안 함

size

IconButtonSize

지정 안 함

variant

IconButtonVariant

지정 안 함

Composer.Cancel

생성 중인 응답을 취소하는 버튼이다.

Prop

Type

Default

asChild

boolean

지정 안 함

children

ReactNode

지정 안 함

className

string

지정 안 함

disabled

boolean

지정 안 함

icon

ReactNode

지정 안 함

loading

boolean

지정 안 함

size

IconButtonSize

지정 안 함

variant

IconButtonVariant

지정 안 함

Composer.AttachmentDropzone

파일을 드래그 앤 드롭으로 첨부할 수 있는 영역이다.

Prop

Type

Default

children
필수

ReactNode

지정 안 함

disabled

boolean

지정 안 함

hint

ReactNode

지정 안 함

Composer.Queue

생성 중에 이어서 보낼 메시지를 쌓아 두는 대기열이다.

Prop

Type

Default

children

(value: { queueItem: import("@assistant-ui/react").QueueItemState; }) => import("react").ReactNode

지정 안 함

Composer.Drawer

입력창 위에 추가 옵션을 펼치는 서랍 오버레이다.

Prop

Type

Default

children
필수

ReactNode

지정 안 함

collapsed

boolean

지정 안 함

count

number

지정 안 함

defaultCollapsed

boolean

지정 안 함

label

ReactNode

지정 안 함

onCollapsedChange

(collapsed: boolean) => void

지정 안 함

Composer.TriggerPopoverRoot

멘션·슬래시 커맨드 팝오버 상태를 제공하는 context 루트다.

Prop

Type

Default

children
필수

ReactNode

지정 안 함

Composer.TriggerPopover

@ 멘션 또는 / 슬래시 커맨드 후보를 보여주는 팝오버다.

Prop

Type

Default

char
필수

string

지정 안 함

adapter

import(".pnpm/@assistant-ui+core@0.3.16_@assistant-ui+store@0.3.11_@assistant-ui+tap@0.9.15_@types+react@19_fndwirrx3btc3x4f2dr6cyav5q/node_modules/@assistant-ui/core").Unstable_TriggerAdapter

지정 안 함

children

ReactNode

지정 안 함

isLoading

boolean

지정 안 함

Composer.If

전송 가능 여부 등 컴포저 상태에 따라 조건부로 렌더하는 헬퍼다.

Prop

Type

Default

dictation

boolean

지정 안 함

editing

boolean

지정 안 함

같은 패밀리

@mildang/design-system/unofficial/Chat 에서 같이 내보내는 컴포넌트다.

예제

기본 사용

기본 입력·전송 버튼으로 구성한 표준 컴포저다.

코드

// ComposerStandard.example.tsx
import { css } from "@mildang/styled-system/css";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerStandard.example.tsx
var wrapper = css({ padding: "16" });
var ComposerStandardExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <Composer>
        <Composer.Input placeholder="메시지를 입력하세요… (Enter 전송, Shift+Enter 줄바꿈)" />
        <Composer.Footer>
          <Composer.TrailingActions>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockRuntimeProvider>;
var ComposerStandard_example_default = ComposerStandardExample;
export {
  ComposerStandard_example_default as default
};

평면 스타일

코드

// ComposerElevationFlat.example.tsx
import { css } from "@mildang/styled-system/css";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerElevationFlat.example.tsx
var wrapper = css({ padding: "16" });
var ComposerElevationFlatExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <Composer elevation="flat">
        <Composer.Input placeholder="flat — 인풋처럼 납작한 입력창 (포커스 시 ring)" />
        <Composer.Footer>
          <Composer.TrailingActions>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockRuntimeProvider>;
var ComposerElevationFlat_example_default = ComposerElevationFlatExample;
export {
  ComposerElevationFlat_example_default as default
};

입체 스타일

코드

// ComposerElevationRaised.example.tsx
import { css } from "@mildang/styled-system/css";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerElevationRaised.example.tsx
var wrapper = css({ padding: "16" });
var ComposerElevationRaisedExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <Composer elevation="raised">
        <Composer.Input placeholder="raised — 그림자가 있는 떠 있는 입력창" />
        <Composer.Footer>
          <Composer.TrailingActions>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockRuntimeProvider>;
var ComposerElevationRaised_example_default = ComposerElevationRaisedExample;
export {
  ComposerElevationRaised_example_default as default
};

앞쪽 액션 추가

입력창 앞쪽에 커스텀 액션 버튼을 둔 예시다.

코드

// ComposerWithLeadingAction.example.tsx
import { css } from "@mildang/styled-system/css";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerWithLeadingAction.example.tsx
var wrapper = css({ padding: "16" });
var ComposerWithLeadingActionExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <Composer>
        <Composer.Input placeholder="파일을 첨부하거나 메시지를 입력하세요…" />
        <Composer.Footer>
          <Composer.LeadingActions>
            <Composer.AddAttachment />
          </Composer.LeadingActions>
          <Composer.TrailingActions>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockRuntimeProvider>;
var ComposerWithLeadingAction_example_default = ComposerWithLeadingActionExample;
export {
  ComposerWithLeadingAction_example_default as default
};

모델 선택 추가

전송 액션 옆에 모델 선택 드롭다운을 둔 예시다.

코드

// ComposerWithModelSelect.example.tsx
import { useState as useState2 } from "react";
import { css } from "@mildang/styled-system/css";
import ChevronDownIcon from "@mildang/icons/react/chevron-down";
import { Composer } from "@mildang/design-system/unofficial/Chat";
import { Menu } from "@mildang/design-system/Menu";
import { Button } from "@mildang/design-system/Button";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerWithModelSelect.example.tsx
var wrapper = css({ padding: "16" });
var ModelSelect = () => {
  const models = ["밀당 GPT", "밀당 Fast", "밀당 Pro"];
  const [model, setModel] = useState2(models[0]);
  return <Menu.Root>
      <Menu.Trigger asChild>
        <Button variant="tertiary" size="sm" endIcon={<ChevronDownIcon />}>
          {model}
        </Button>
      </Menu.Trigger>
      <Menu.Content>
        {models.map((m) => <Menu.Item key={m} selected={m === model} onSelect={() => setModel(m)}>
            {m}
          </Menu.Item>)}
      </Menu.Content>
    </Menu.Root>;
};
var ComposerWithModelSelectExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <Composer>
        <Composer.Input placeholder="모델을 고르고 메시지를 입력하세요…" />
        <Composer.Footer>
          <Composer.LeadingActions>
            <ModelSelect />
            <Composer.AddAttachment />
          </Composer.LeadingActions>
          <Composer.TrailingActions>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockRuntimeProvider>;
var ComposerWithModelSelect_example_default = ComposerWithModelSelectExample;
export {
  ComposerWithModelSelect_example_default as default
};

전송/취소 전환

생성 중 전송 버튼이 취소 버튼으로 바뀌는 예시다.

코드

// ComposerSendCancelToggle.example.tsx
import { css } from "@mildang/styled-system/css";
import { ThreadPrimitive } from "@assistant-ui/react";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerSendCancelToggle.example.tsx
var wrapper = css({ padding: "16" });
var ComposerSendCancelToggleExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <Composer>
        <Composer.Input placeholder="전송하면 잠깐 Cancel로 전환됩니다…" />
        <Composer.Footer>
          <Composer.LeadingActions>
            <Composer.AddAttachment />
          </Composer.LeadingActions>
          <Composer.TrailingActions>
            <ThreadPrimitive.If running={false}>
              <Composer.Send />
            </ThreadPrimitive.If>
            <ThreadPrimitive.If running>
              <Composer.Cancel />
            </ThreadPrimitive.If>
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockRuntimeProvider>;
var ComposerSendCancelToggle_example_default = ComposerSendCancelToggleExample;
export {
  ComposerSendCancelToggle_example_default as default
};

오류 상태

코드

// ComposerStatusError.example.tsx
import { css } from "@mildang/styled-system/css";
import ErrorOutline from "@mildang/icons/react/error-outline";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerStatusError.example.tsx
var wrapper = css({ padding: "16" });
var ComposerStatusErrorExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <Composer>
        <Composer.Status severity="error">
          <ErrorOutline />
          메시지는 2000자를 넘을 수 없습니다.
        </Composer.Status>
        <Composer.Input placeholder="메시지를 입력하세요…" />
        <Composer.Footer>
          <Composer.TrailingActions>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockRuntimeProvider>;
var ComposerStatusError_example_default = ComposerStatusErrorExample;
export {
  ComposerStatusError_example_default as default
};

경고 상태

코드

// ComposerStatusWarning.example.tsx
import { css } from "@mildang/styled-system/css";
import WarningFill from "@mildang/icons/react/warning-fill";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerStatusWarning.example.tsx
var wrapper = css({ padding: "16" });
var ComposerStatusWarningExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <Composer>
        <Composer.Status severity="warning">
          <WarningFill />
          네트워크가 불안정합니다. 전송이 지연될 수 있어요.
        </Composer.Status>
        <Composer.Input placeholder="메시지를 입력하세요…" />
        <Composer.Footer>
          <Composer.TrailingActions>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockRuntimeProvider>;
var ComposerStatusWarning_example_default = ComposerStatusWarningExample;
export {
  ComposerStatusWarning_example_default as default
};

멘션

TriggerPopover로 @ 멘션 후보를 보여주는 예시다.

코드

// ComposerMentions.example.tsx
import { css } from "@mildang/styled-system/css";
import { Composer as Composer2 } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ../../examples/triggerScenario.tsx
import {
  makeAssistantTool,
  unstable_defaultDirectiveFormatter,
  unstable_useMentionAdapter,
  unstable_useSlashCommandAdapter
} from "@assistant-ui/react";
import GraduationCapIcon from "@mildang/icons/react/graduation_cap";
import MagicWandIcon from "@mildang/icons/react/magic-wand";
import ReportPencilIcon from "@mildang/icons/react/report-pencil";
import BookIcon from "@mildang/icons/react/book";
import { Composer, createDirectiveChip, createDirectiveText } from "@mildang/design-system/unofficial/Chat";
var TRIGGER_ICON_MAP = {
  student: GraduationCapIcon,
  tool: MagicWandIcon,
  command: MagicWandIcon,
  report: ReportPencilIcon,
  context: BookIcon
};
var TRIGGER_FALLBACK_ICON = MagicWandIcon;
var resolveTriggerIcon = (item, iconMap = TRIGGER_ICON_MAP, fallbackIcon = TRIGGER_FALLBACK_ICON) => {
  const key = typeof item.metadata?.icon === "string" ? item.metadata.icon : void 0;
  const Icon = (key ? iconMap[key] : void 0) ?? fallbackIcon;
  return Icon ? <Icon /> : void 0;
};
var MILDANG_STUDENTS = [
  { id: "student-minjun", type: "student", label: "민준", description: "중2 · 수학", icon: "student" },
  { id: "student-seoyeon", type: "student", label: "서연", description: "중3 · 영어", icon: "student" },
  { id: "student-jiho", type: "student", label: "지호", description: "고1 · 국어", icon: "student" },
  { id: "student-hayoon", type: "student", label: "하윤", description: "고2 · 과학", icon: "student" }
];
var MILDANG_DIRECTIVE_CHIP_OPTIONS = {
  iconMap: {
    student: GraduationCapIcon,
    tool: MagicWandIcon,
    command: MagicWandIcon,
    context: BookIcon,
    report: ReportPencilIcon
  },
  colorMap: {
    student: "brand",
    tool: "orange",
    command: "blue",
    context: "green",
    report: "red"
  },
  fallbackColor: "neutral"
};
var MildangDirectiveChip = createDirectiveChip(MILDANG_DIRECTIVE_CHIP_OPTIONS);
var MildangDirectiveText = createDirectiveText(
  unstable_defaultDirectiveFormatter,
  MILDANG_DIRECTIVE_CHIP_OPTIONS
);
var emptyParams = { type: "object", properties: {} };
var StudentProgressTool = makeAssistantTool({
  toolName: "getStudentProgress",
  description: "학생의 최근 학습 현황을 조회합니다",
  parameters: emptyParams,
  execute: async () => ({ ok: true })
});
var WrongAnswerTool = makeAssistantTool({
  toolName: "analyzeWrongAnswers",
  description: "최근 오답을 분석해 취약 유형을 찾습니다",
  parameters: emptyParams,
  execute: async () => ({ ok: true })
});
var DemoModelContextTools = () => <>
    <StudentProgressTool />
    <WrongAnswerTool />
  </>;
var MentionPopover = ({ onInserted }) => {
  const mention = unstable_useMentionAdapter({
    categories: [{ id: "students", label: "학생", items: MILDANG_STUDENTS }],
    includeModelContextTools: { category: { id: "tools", label: "도구" }, icon: "tool" },
    iconMap: TRIGGER_ICON_MAP,
    fallbackIcon: TRIGGER_FALLBACK_ICON,
    ...onInserted ? { onInserted } : {}
  });
  return <Composer.TriggerPopover char="@" adapter={mention.adapter}>
      <Composer.TriggerPopover.Directive {...mention.directive} />
      <Composer.TriggerPopover.Categories>
        {(categories) => categories.map((category) => <Composer.TriggerPopover.CategoryItem
    key={category.id}
    categoryId={category.id}
    icon={category.id === "tools" ? <MagicWandIcon /> : <GraduationCapIcon />}
  >
              {category.label}
            </Composer.TriggerPopover.CategoryItem>)}
      </Composer.TriggerPopover.Categories>
      <Composer.TriggerPopover.Back>학생·도구 목록</Composer.TriggerPopover.Back>
      <Composer.TriggerPopover.Items>
        {(items) => items.length > 0 ? items.map((item, index) => <Composer.TriggerPopover.Item
    key={item.id}
    item={item}
    index={index}
    icon={resolveTriggerIcon(item)}
  />) : <Composer.TriggerPopover.Empty>일치하는 대상이 없어요</Composer.TriggerPopover.Empty>}
      </Composer.TriggerPopover.Items>
    </Composer.TriggerPopover>;
};

// ComposerMentions.example.tsx
var triggerWrapper = css({
  display: "flex",
  alignItems: "flex-end",
  minHeight: "[60vh]",
  padding: "16"
});
var ComposerMentionsExample = () => <MockRuntimeProvider>
    {
  /* includeModelContextTools 데모용 — 렌더되는 동안 도구 2종을 모델 컨텍스트에 등록 */
}
    <DemoModelContextTools />
    <div className={triggerWrapper}>
      <Composer2.TriggerPopoverRoot>
        <Composer2 sx={{ width: "100%" }}>
          <MentionPopover />
          <Composer2.RichInput
  directiveChip={MildangDirectiveChip}
  placeholder="@ 를 입력해 학생이나 도구를 멘션하세요…"
/>
          <Composer2.Footer>
            <Composer2.TrailingActions>
              <Composer2.Send />
            </Composer2.TrailingActions>
          </Composer2.Footer>
        </Composer2>
      </Composer2.TriggerPopoverRoot>
    </div>
  </MockRuntimeProvider>;
var ComposerMentions_example_default = ComposerMentionsExample;
export {
  ComposerMentions_example_default as default
};

멘션 왕복 편집

멘션을 선택하고 다시 편집하는 흐름을 보여주는 예시다.

코드

// ComposerMentionsRoundTrip.example.tsx
import { css } from "@mildang/styled-system/css";
import { ThreadPrimitive } from "@assistant-ui/react";
import { Composer as Composer2, Message } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ../../examples/triggerScenario.tsx
import {
  makeAssistantTool,
  unstable_defaultDirectiveFormatter,
  unstable_useMentionAdapter,
  unstable_useSlashCommandAdapter
} from "@assistant-ui/react";
import GraduationCapIcon from "@mildang/icons/react/graduation_cap";
import MagicWandIcon from "@mildang/icons/react/magic-wand";
import ReportPencilIcon from "@mildang/icons/react/report-pencil";
import BookIcon from "@mildang/icons/react/book";
import { Composer, createDirectiveChip, createDirectiveText } from "@mildang/design-system/unofficial/Chat";
var TRIGGER_ICON_MAP = {
  student: GraduationCapIcon,
  tool: MagicWandIcon,
  command: MagicWandIcon,
  report: ReportPencilIcon,
  context: BookIcon
};
var TRIGGER_FALLBACK_ICON = MagicWandIcon;
var resolveTriggerIcon = (item, iconMap = TRIGGER_ICON_MAP, fallbackIcon = TRIGGER_FALLBACK_ICON) => {
  const key = typeof item.metadata?.icon === "string" ? item.metadata.icon : void 0;
  const Icon = (key ? iconMap[key] : void 0) ?? fallbackIcon;
  return Icon ? <Icon /> : void 0;
};
var MILDANG_STUDENTS = [
  { id: "student-minjun", type: "student", label: "민준", description: "중2 · 수학", icon: "student" },
  { id: "student-seoyeon", type: "student", label: "서연", description: "중3 · 영어", icon: "student" },
  { id: "student-jiho", type: "student", label: "지호", description: "고1 · 국어", icon: "student" },
  { id: "student-hayoon", type: "student", label: "하윤", description: "고2 · 과학", icon: "student" }
];
var MILDANG_DIRECTIVE_CHIP_OPTIONS = {
  iconMap: {
    student: GraduationCapIcon,
    tool: MagicWandIcon,
    command: MagicWandIcon,
    context: BookIcon,
    report: ReportPencilIcon
  },
  colorMap: {
    student: "brand",
    tool: "orange",
    command: "blue",
    context: "green",
    report: "red"
  },
  fallbackColor: "neutral"
};
var MildangDirectiveChip = createDirectiveChip(MILDANG_DIRECTIVE_CHIP_OPTIONS);
var MildangDirectiveText = createDirectiveText(
  unstable_defaultDirectiveFormatter,
  MILDANG_DIRECTIVE_CHIP_OPTIONS
);
var emptyParams = { type: "object", properties: {} };
var StudentProgressTool = makeAssistantTool({
  toolName: "getStudentProgress",
  description: "학생의 최근 학습 현황을 조회합니다",
  parameters: emptyParams,
  execute: async () => ({ ok: true })
});
var WrongAnswerTool = makeAssistantTool({
  toolName: "analyzeWrongAnswers",
  description: "최근 오답을 분석해 취약 유형을 찾습니다",
  parameters: emptyParams,
  execute: async () => ({ ok: true })
});
var DemoModelContextTools = () => <>
    <StudentProgressTool />
    <WrongAnswerTool />
  </>;
var MentionPopover = ({ onInserted }) => {
  const mention = unstable_useMentionAdapter({
    categories: [{ id: "students", label: "학생", items: MILDANG_STUDENTS }],
    includeModelContextTools: { category: { id: "tools", label: "도구" }, icon: "tool" },
    iconMap: TRIGGER_ICON_MAP,
    fallbackIcon: TRIGGER_FALLBACK_ICON,
    ...onInserted ? { onInserted } : {}
  });
  return <Composer.TriggerPopover char="@" adapter={mention.adapter}>
      <Composer.TriggerPopover.Directive {...mention.directive} />
      <Composer.TriggerPopover.Categories>
        {(categories) => categories.map((category) => <Composer.TriggerPopover.CategoryItem
    key={category.id}
    categoryId={category.id}
    icon={category.id === "tools" ? <MagicWandIcon /> : <GraduationCapIcon />}
  >
              {category.label}
            </Composer.TriggerPopover.CategoryItem>)}
      </Composer.TriggerPopover.Categories>
      <Composer.TriggerPopover.Back>학생·도구 목록</Composer.TriggerPopover.Back>
      <Composer.TriggerPopover.Items>
        {(items) => items.length > 0 ? items.map((item, index) => <Composer.TriggerPopover.Item
    key={item.id}
    item={item}
    index={index}
    icon={resolveTriggerIcon(item)}
  />) : <Composer.TriggerPopover.Empty>일치하는 대상이 없어요</Composer.TriggerPopover.Empty>}
      </Composer.TriggerPopover.Items>
    </Composer.TriggerPopover>;
};

// ComposerMentionsRoundTrip.example.tsx
var roundTripWrapper = css({
  display: "flex",
  flexDirection: "column",
  gap: "16",
  minHeight: "[60vh]",
  padding: "16"
});
var RoundTripUserMessage = () => <Message role="user">
    <Message.Content>
      <Message.Parts components={{ Text: MildangDirectiveText }} />
    </Message.Content>
  </Message>;
var RoundTripAssistantMessage = () => <Message role="assistant">
    <Message.Content>
      <Message.Parts />
    </Message.Content>
  </Message>;
var ComposerMentionsRoundTripExample = () => <MockRuntimeProvider>
    <DemoModelContextTools />
    <div className={roundTripWrapper}>
      <ThreadPrimitive.Root>
        <ThreadPrimitive.Viewport>
          <ThreadPrimitive.Messages
  components={{
    UserMessage: RoundTripUserMessage,
    AssistantMessage: RoundTripAssistantMessage
  }}
/>
        </ThreadPrimitive.Viewport>
        <Composer2.TriggerPopoverRoot>
          <Composer2 sx={{ width: "100%", marginTop: "16" }}>
            <MentionPopover />
            <Composer2.RichInput
  directiveChip={MildangDirectiveChip}
  placeholder="@민준 을 멘션하고 전송해 보세요…"
/>
            <Composer2.Footer>
              <Composer2.TrailingActions>
                <Composer2.Send />
              </Composer2.TrailingActions>
            </Composer2.Footer>
          </Composer2>
        </Composer2.TriggerPopoverRoot>
      </ThreadPrimitive.Root>
    </div>
  </MockRuntimeProvider>;
var ComposerMentionsRoundTrip_example_default = ComposerMentionsRoundTripExample;
export {
  ComposerMentionsRoundTrip_example_default as default
};

슬래시 커맨드 — 액션

/ 로 시작하는 즉시 실행 액션 커맨드 예시다.

코드

// ComposerSlashCommandsAction.example.tsx
import { useState as useState2 } from "react";
import { css } from "@mildang/styled-system/css";
import { Composer as Composer2 } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ../../examples/triggerScenario.tsx
import {
  makeAssistantTool,
  unstable_defaultDirectiveFormatter,
  unstable_useMentionAdapter,
  unstable_useSlashCommandAdapter
} from "@assistant-ui/react";
import GraduationCapIcon from "@mildang/icons/react/graduation_cap";
import MagicWandIcon from "@mildang/icons/react/magic-wand";
import ReportPencilIcon from "@mildang/icons/react/report-pencil";
import BookIcon from "@mildang/icons/react/book";
import { Composer, createDirectiveChip, createDirectiveText } from "@mildang/design-system/unofficial/Chat";
var TRIGGER_ICON_MAP = {
  student: GraduationCapIcon,
  tool: MagicWandIcon,
  command: MagicWandIcon,
  report: ReportPencilIcon,
  context: BookIcon
};
var TRIGGER_FALLBACK_ICON = MagicWandIcon;
var resolveTriggerIcon = (item, iconMap = TRIGGER_ICON_MAP, fallbackIcon = TRIGGER_FALLBACK_ICON) => {
  const key = typeof item.metadata?.icon === "string" ? item.metadata.icon : void 0;
  const Icon = (key ? iconMap[key] : void 0) ?? fallbackIcon;
  return Icon ? <Icon /> : void 0;
};
var MILDANG_DIRECTIVE_CHIP_OPTIONS = {
  iconMap: {
    student: GraduationCapIcon,
    tool: MagicWandIcon,
    command: MagicWandIcon,
    context: BookIcon,
    report: ReportPencilIcon
  },
  colorMap: {
    student: "brand",
    tool: "orange",
    command: "blue",
    context: "green",
    report: "red"
  },
  fallbackColor: "neutral"
};
var MildangDirectiveChip = createDirectiveChip(MILDANG_DIRECTIVE_CHIP_OPTIONS);
var MildangDirectiveText = createDirectiveText(
  unstable_defaultDirectiveFormatter,
  MILDANG_DIRECTIVE_CHIP_OPTIONS
);
var emptyParams = { type: "object", properties: {} };
var StudentProgressTool = makeAssistantTool({
  toolName: "getStudentProgress",
  description: "학생의 최근 학습 현황을 조회합니다",
  parameters: emptyParams,
  execute: async () => ({ ok: true })
});
var WrongAnswerTool = makeAssistantTool({
  toolName: "analyzeWrongAnswers",
  description: "최근 오답을 분석해 취약 유형을 찾습니다",
  parameters: emptyParams,
  execute: async () => ({ ok: true })
});
var buildAuditCommands = (log) => [
  {
    id: "요약",
    label: "/요약",
    description: "오늘 학습을 요약하는 프롬프트 삽입",
    icon: "report",
    execute: () => log("요약")
  },
  {
    id: "추천",
    label: "/추천",
    description: "다음 학습 목표 추천",
    icon: "command",
    execute: () => log("추천")
  },
  {
    id: "리포트",
    label: "/리포트",
    description: "학습 리포트 생성",
    icon: "tool",
    execute: () => log("리포트")
  }
];
var buildActionCommands = (run) => [
  {
    id: "오답노트",
    label: "/오답노트",
    description: "오답 노트를 바로 엽니다",
    icon: "context",
    execute: () => run("오답노트")
  },
  {
    id: "새대화",
    label: "/새대화",
    description: "현재 대화를 초기화합니다",
    icon: "command",
    execute: () => run("새대화")
  }
];
var SlashPopover = ({ commands, removeOnExecute }) => {
  const slash = unstable_useSlashCommandAdapter({
    commands,
    ...removeOnExecute !== void 0 ? { removeOnExecute } : {},
    iconMap: TRIGGER_ICON_MAP,
    fallbackIcon: TRIGGER_FALLBACK_ICON
  });
  return <Composer.TriggerPopover char="/" adapter={slash.adapter}>
      <Composer.TriggerPopover.Action {...slash.action} />
      <Composer.TriggerPopover.Items>
        {(items) => items.length > 0 ? items.map((item, index) => <Composer.TriggerPopover.Item
    key={item.id}
    item={item}
    index={index}
    icon={resolveTriggerIcon(item)}
  />) : <Composer.TriggerPopover.Empty>커맨드를 찾을 수 없어요</Composer.TriggerPopover.Empty>}
      </Composer.TriggerPopover.Items>
    </Composer.TriggerPopover>;
};

// ComposerSlashCommandsAction.example.tsx
var triggerWrapper = css({
  display: "flex",
  alignItems: "flex-end",
  minHeight: "[60vh]",
  padding: "16"
});
var logLine = css({
  marginTop: "8",
  textStyle: "chat-caption-M",
  color: "brand.text.base"
});
var slashComposerWrapper = css({ width: "100%" });
var SlashComposer = ({ mode }) => {
  const [log, setLog] = useState2(null);
  const commands = mode === "directive" ? buildAuditCommands((label) => setLog(`directive 삽입(audit chip): /${label}`)) : buildActionCommands((label) => setLog(`실행됨(텍스트 제거): /${label}`));
  return <div className={slashComposerWrapper}>
      <Composer2.TriggerPopoverRoot>
        <Composer2 sx={{ width: "100%" }}>
          <SlashPopover commands={commands} removeOnExecute={mode === "action"} />
          <Composer2.RichInput
    directiveChip={MildangDirectiveChip}
    placeholder={mode === "directive" ? "/요약 처럼 입력하면 directive(audit chip)가 남습니다…" : "/오답노트 처럼 입력하면 즉시 실행되고 트리거 텍스트가 지워집니다…"}
  />
          <Composer2.Footer>
            <Composer2.TrailingActions>
              <Composer2.Send />
            </Composer2.TrailingActions>
          </Composer2.Footer>
        </Composer2>
      </Composer2.TriggerPopoverRoot>
      {log && <div className={logLine}>{log}</div>}
    </div>;
};
var ComposerSlashCommandsActionExample = () => <MockRuntimeProvider>
    <div className={triggerWrapper}>
      <SlashComposer mode="action" />
    </div>
  </MockRuntimeProvider>;
var ComposerSlashCommandsAction_example_default = ComposerSlashCommandsActionExample;
export {
  ComposerSlashCommandsAction_example_default as default
};

슬래시 커맨드 — 디렉티브

/ 로 시작해 입력에 남는 디렉티브 커맨드 예시다.

코드

// ComposerSlashCommandsDirective.example.tsx
import { useState as useState2 } from "react";
import { css } from "@mildang/styled-system/css";
import { Composer as Composer2 } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ../../examples/triggerScenario.tsx
import {
  makeAssistantTool,
  unstable_defaultDirectiveFormatter,
  unstable_useMentionAdapter,
  unstable_useSlashCommandAdapter
} from "@assistant-ui/react";
import GraduationCapIcon from "@mildang/icons/react/graduation_cap";
import MagicWandIcon from "@mildang/icons/react/magic-wand";
import ReportPencilIcon from "@mildang/icons/react/report-pencil";
import BookIcon from "@mildang/icons/react/book";
import { Composer, createDirectiveChip, createDirectiveText } from "@mildang/design-system/unofficial/Chat";
var TRIGGER_ICON_MAP = {
  student: GraduationCapIcon,
  tool: MagicWandIcon,
  command: MagicWandIcon,
  report: ReportPencilIcon,
  context: BookIcon
};
var TRIGGER_FALLBACK_ICON = MagicWandIcon;
var resolveTriggerIcon = (item, iconMap = TRIGGER_ICON_MAP, fallbackIcon = TRIGGER_FALLBACK_ICON) => {
  const key = typeof item.metadata?.icon === "string" ? item.metadata.icon : void 0;
  const Icon = (key ? iconMap[key] : void 0) ?? fallbackIcon;
  return Icon ? <Icon /> : void 0;
};
var MILDANG_DIRECTIVE_CHIP_OPTIONS = {
  iconMap: {
    student: GraduationCapIcon,
    tool: MagicWandIcon,
    command: MagicWandIcon,
    context: BookIcon,
    report: ReportPencilIcon
  },
  colorMap: {
    student: "brand",
    tool: "orange",
    command: "blue",
    context: "green",
    report: "red"
  },
  fallbackColor: "neutral"
};
var MildangDirectiveChip = createDirectiveChip(MILDANG_DIRECTIVE_CHIP_OPTIONS);
var MildangDirectiveText = createDirectiveText(
  unstable_defaultDirectiveFormatter,
  MILDANG_DIRECTIVE_CHIP_OPTIONS
);
var emptyParams = { type: "object", properties: {} };
var StudentProgressTool = makeAssistantTool({
  toolName: "getStudentProgress",
  description: "학생의 최근 학습 현황을 조회합니다",
  parameters: emptyParams,
  execute: async () => ({ ok: true })
});
var WrongAnswerTool = makeAssistantTool({
  toolName: "analyzeWrongAnswers",
  description: "최근 오답을 분석해 취약 유형을 찾습니다",
  parameters: emptyParams,
  execute: async () => ({ ok: true })
});
var buildAuditCommands = (log) => [
  {
    id: "요약",
    label: "/요약",
    description: "오늘 학습을 요약하는 프롬프트 삽입",
    icon: "report",
    execute: () => log("요약")
  },
  {
    id: "추천",
    label: "/추천",
    description: "다음 학습 목표 추천",
    icon: "command",
    execute: () => log("추천")
  },
  {
    id: "리포트",
    label: "/리포트",
    description: "학습 리포트 생성",
    icon: "tool",
    execute: () => log("리포트")
  }
];
var buildActionCommands = (run) => [
  {
    id: "오답노트",
    label: "/오답노트",
    description: "오답 노트를 바로 엽니다",
    icon: "context",
    execute: () => run("오답노트")
  },
  {
    id: "새대화",
    label: "/새대화",
    description: "현재 대화를 초기화합니다",
    icon: "command",
    execute: () => run("새대화")
  }
];
var SlashPopover = ({ commands, removeOnExecute }) => {
  const slash = unstable_useSlashCommandAdapter({
    commands,
    ...removeOnExecute !== void 0 ? { removeOnExecute } : {},
    iconMap: TRIGGER_ICON_MAP,
    fallbackIcon: TRIGGER_FALLBACK_ICON
  });
  return <Composer.TriggerPopover char="/" adapter={slash.adapter}>
      <Composer.TriggerPopover.Action {...slash.action} />
      <Composer.TriggerPopover.Items>
        {(items) => items.length > 0 ? items.map((item, index) => <Composer.TriggerPopover.Item
    key={item.id}
    item={item}
    index={index}
    icon={resolveTriggerIcon(item)}
  />) : <Composer.TriggerPopover.Empty>커맨드를 찾을 수 없어요</Composer.TriggerPopover.Empty>}
      </Composer.TriggerPopover.Items>
    </Composer.TriggerPopover>;
};

// ComposerSlashCommandsDirective.example.tsx
var triggerWrapper = css({
  display: "flex",
  alignItems: "flex-end",
  minHeight: "[60vh]",
  padding: "16"
});
var logLine = css({
  marginTop: "8",
  textStyle: "chat-caption-M",
  color: "brand.text.base"
});
var slashComposerWrapper = css({ width: "100%" });
var SlashComposer = ({ mode }) => {
  const [log, setLog] = useState2(null);
  const commands = mode === "directive" ? buildAuditCommands((label) => setLog(`directive 삽입(audit chip): /${label}`)) : buildActionCommands((label) => setLog(`실행됨(텍스트 제거): /${label}`));
  return <div className={slashComposerWrapper}>
      <Composer2.TriggerPopoverRoot>
        <Composer2 sx={{ width: "100%" }}>
          <SlashPopover commands={commands} removeOnExecute={mode === "action"} />
          <Composer2.RichInput
    directiveChip={MildangDirectiveChip}
    placeholder={mode === "directive" ? "/요약 처럼 입력하면 directive(audit chip)가 남습니다…" : "/오답노트 처럼 입력하면 즉시 실행되고 트리거 텍스트가 지워집니다…"}
  />
          <Composer2.Footer>
            <Composer2.TrailingActions>
              <Composer2.Send />
            </Composer2.TrailingActions>
          </Composer2.Footer>
        </Composer2>
      </Composer2.TriggerPopoverRoot>
      {log && <div className={logLine}>{log}</div>}
    </div>;
};
var ComposerSlashCommandsDirectiveExample = () => <MockRuntimeProvider>
    <div className={triggerWrapper}>
      <SlashComposer mode="directive" />
    </div>
  </MockRuntimeProvider>;
var ComposerSlashCommandsDirective_example_default = ComposerSlashCommandsDirectiveExample;
export {
  ComposerSlashCommandsDirective_example_default as default
};

받아쓰기

음성 받아쓰기를 시작·중지하는 예시다.

코드

// ComposerDictation.example.tsx
import { css } from "@mildang/styled-system/css";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerDictation.example.tsx
var wrapper = css({ padding: "16" });
var ComposerDictationExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <Composer>
        <Composer.If dictation>
          <Composer.Header>
            <Composer.DictationTranscript />
          </Composer.Header>
        </Composer.If>
        <Composer.Input placeholder="마이크로 받아쓰거나 입력하세요…" />
        <Composer.Footer>
          <Composer.LeadingActions>
            <Composer.Dictate />
          </Composer.LeadingActions>
          <Composer.TrailingActions>
            <Composer.If dictation>
              <Composer.StopDictation />
            </Composer.If>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockRuntimeProvider>;
var ComposerDictation_example_default = ComposerDictationExample;
export {
  ComposerDictation_example_default as default
};

드래그 앤 드롭 첨부

코드

// ComposerAttachmentDropzoneDefault.example.tsx
import { css } from "@mildang/styled-system/css";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerAttachmentDropzoneDefault.example.tsx
var wrapper = css({ padding: "16" });
var ComposerAttachmentDropzoneDefaultExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <Composer.AttachmentDropzone data-testid="attachment-dropzone" aria-label="파일 드롭 영역">
        <Composer>
          <Composer.Input aria-label="메시지" placeholder="파일을 끌어다 놓으세요…" />
          <Composer.Footer>
            <Composer.LeadingActions>
              <Composer.AddAttachment />
            </Composer.LeadingActions>
            <Composer.TrailingActions>
              <Composer.Send />
            </Composer.TrailingActions>
          </Composer.Footer>
        </Composer>
      </Composer.AttachmentDropzone>
    </div>
  </MockRuntimeProvider>;
var ComposerAttachmentDropzoneDefault_example_default = ComposerAttachmentDropzoneDefaultExample;
export {
  ComposerAttachmentDropzoneDefault_example_default as default
};

대기열

생성 중 추가로 입력한 메시지를 대기열에 쌓는 예시다.

코드

// ComposerQueueDefault.example.tsx
import { css } from "@mildang/styled-system/css";
import { useAuiState } from "@assistant-ui/react";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var queueMockAdapter = {
  async run({ abortSignal }) {
    await new Promise((resolve) => {
      abortSignal.addEventListener("abort", () => resolve(), { once: true });
    });
    return {
      content: [{ type: "text", text: "대기열 데모 응답" }]
    };
  }
};
function MockQueueRuntimeProvider({ children }) {
  const runtime = useLocalRuntime(queueMockAdapter, {
    unstable_enableMessageQueue: true
  });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerQueueDefault.example.tsx
var wrapper = css({ padding: "16" });
var DefaultQueueDrawer = () => {
  const queueLength = useAuiState((state) => state.composer.queue.length);
  if (queueLength === 0) {
    return null;
  }
  return <Composer.Drawer count={queueLength} label="전송 대기 메시지">
      <Composer.Queue />
    </Composer.Drawer>;
};
var ComposerQueueDefaultExample = () => <MockQueueRuntimeProvider>
    <div className={wrapper}>
      <Composer drawer={<DefaultQueueDrawer />}>
        <Composer.Input aria-label="메시지" placeholder="실행 중 메시지를 추가해 보세요…" />
        <Composer.Footer>
          <Composer.TrailingActions>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockQueueRuntimeProvider>;
var ComposerQueueDefault_example_default = ComposerQueueDefaultExample;
export {
  ComposerQueueDefault_example_default as default
};

대기열 고급 조립

대기열 아이템을 Item/Text/Actions 슬롯으로 직접 조립한 예시다.

코드

// ComposerQueueAdvancedComposition.example.tsx
import { css } from "@mildang/styled-system/css";
import { useAuiState } from "@assistant-ui/react";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var queueMockAdapter = {
  async run({ abortSignal }) {
    await new Promise((resolve) => {
      abortSignal.addEventListener("abort", () => resolve(), { once: true });
    });
    return {
      content: [{ type: "text", text: "대기열 데모 응답" }]
    };
  }
};
function MockQueueRuntimeProvider({ children }) {
  const runtime = useLocalRuntime(queueMockAdapter, {
    unstable_enableMessageQueue: true
  });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerQueueAdvancedComposition.example.tsx
var wrapper = css({ padding: "16" });
var AdvancedQueueDrawer = () => {
  const queueLength = useAuiState((state) => state.composer.queue.length);
  if (queueLength === 0) {
    return null;
  }
  return <Composer.Drawer count={queueLength} label="사용자 지정 대기열">
      <Composer.Queue aria-label="사용자 지정 대기열">
        {({ queueItem }) => <Composer.Queue.Item aria-label={`대기 메시지: ${queueItem.prompt}`}>
            <Composer.Queue.Text />
            <Composer.Queue.Actions>
              <Composer.Queue.Remove />
              <Composer.Queue.Steer />
            </Composer.Queue.Actions>
          </Composer.Queue.Item>}
      </Composer.Queue>
    </Composer.Drawer>;
};
var ComposerQueueAdvancedCompositionExample = () => <MockQueueRuntimeProvider>
    <div className={wrapper}>
      <Composer drawer={<AdvancedQueueDrawer />}>
        <Composer.Input aria-label="메시지" placeholder="고급 Queue 조립 예시" />
        <Composer.Footer>
          <Composer.TrailingActions>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockQueueRuntimeProvider>;
var ComposerQueueAdvancedComposition_example_default = ComposerQueueAdvancedCompositionExample;
export {
  ComposerQueueAdvancedComposition_example_default as default
};

서랍

입력창 위에 추가 옵션 서랍을 펼치는 예시다.

코드

// ComposerDrawerDefault.example.tsx
import { css } from "@mildang/styled-system/css";
import { Chip } from "@mildang/design-system/Chip";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerDrawerDefault.example.tsx
var wrapper = css({
  width: "100%",
  maxWidth: "chat.thread.maxWidth",
  padding: "16"
});
var contextItems = ["report.pdf", "민준 학생", "최근 4주 학습 기록"];
var ContextChips = () => <>
    {contextItems.map((label) => <Chip key={label} size="sm" type="outlined" label={label} />)}
  </>;
var ComposerShell = ({ drawer }) => <Composer drawer={drawer}>
    <Composer.Input aria-label="메시지" placeholder="컨텍스트를 참고해 질문하세요…" />
    <Composer.Footer>
      <Composer.TrailingActions>
        <Composer.Send />
      </Composer.TrailingActions>
    </Composer.Footer>
  </Composer>;
var ComposerDrawerDefaultExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <ComposerShell
  drawer={<Composer.Drawer count={contextItems.length} label="사용 중인 컨텍스트">
            <ContextChips />
          </Composer.Drawer>}
/>
    </div>
  </MockRuntimeProvider>;
var ComposerDrawerDefault_example_default = ComposerDrawerDefaultExample;
export {
  ComposerDrawerDefault_example_default as default
};

서랍 — 접힘

코드

// ComposerDrawerDefaultCollapsed.example.tsx
import { css } from "@mildang/styled-system/css";
import { Chip } from "@mildang/design-system/Chip";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerDrawerDefaultCollapsed.example.tsx
var wrapper = css({
  width: "100%",
  maxWidth: "chat.thread.maxWidth",
  padding: "16"
});
var contextItems = ["report.pdf", "민준 학생", "최근 4주 학습 기록"];
var ContextChips = () => <>
    {contextItems.map((label) => <Chip key={label} size="sm" type="outlined" label={label} />)}
  </>;
var ComposerShell = ({ drawer }) => <Composer drawer={drawer}>
    <Composer.Input aria-label="메시지" placeholder="컨텍스트를 참고해 질문하세요…" />
    <Composer.Footer>
      <Composer.TrailingActions>
        <Composer.Send />
      </Composer.TrailingActions>
    </Composer.Footer>
  </Composer>;
var ComposerDrawerDefaultCollapsedExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <ComposerShell
  drawer={<Composer.Drawer count={contextItems.length} label="사용 중인 컨텍스트" defaultCollapsed>
            <ContextChips />
          </Composer.Drawer>}
/>
    </div>
  </MockRuntimeProvider>;
var ComposerDrawerDefaultCollapsed_example_default = ComposerDrawerDefaultCollapsedExample;
export {
  ComposerDrawerDefaultCollapsed_example_default as default
};

서랍 — 제어 모드

서랍 열림 상태를 외부에서 제어하는 예시다.

코드

// ComposerDrawerAdvancedControlled.example.tsx
import { useState as useState2 } from "react";
import { css } from "@mildang/styled-system/css";
import { Chip } from "@mildang/design-system/Chip";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerDrawerAdvancedControlled.example.tsx
var wrapper = css({
  width: "100%",
  maxWidth: "chat.thread.maxWidth",
  padding: "16"
});
var contextItems = ["report.pdf", "민준 학생", "최근 4주 학습 기록"];
var ContextChips = () => <>
    {contextItems.map((label) => <Chip key={label} size="sm" type="outlined" label={label} />)}
  </>;
var ComposerShell = ({ drawer }) => <Composer drawer={drawer}>
    <Composer.Input aria-label="메시지" placeholder="컨텍스트를 참고해 질문하세요…" />
    <Composer.Footer>
      <Composer.TrailingActions>
        <Composer.Send />
      </Composer.TrailingActions>
    </Composer.Footer>
  </Composer>;
var ControlledDrawer = () => {
  const [collapsed, setCollapsed] = useState2(false);
  return <ComposerShell
    drawer={<Composer.Drawer.Root collapsed={collapsed} onCollapsedChange={setCollapsed}>
          <Composer.Drawer.Trigger aria-label="학습 컨텍스트 접기">
            <span>학습 컨텍스트 {collapsed ? "펼치기" : "접기"}</span>
          </Composer.Drawer.Trigger>
          <Composer.Drawer.Content>
            <ContextChips />
          </Composer.Drawer.Content>
        </Composer.Drawer.Root>}
  />;
};
var ComposerDrawerAdvancedControlledExample = () => <MockRuntimeProvider>
    <div className={wrapper}>
      <ControlledDrawer />
    </div>
  </MockRuntimeProvider>;
var ComposerDrawerAdvancedControlled_example_default = ComposerDrawerAdvancedControlledExample;
export {
  ComposerDrawerAdvancedControlled_example_default as default
};

서랍 + 런타임 대기열

서랍과 런타임 대기열을 함께 조합한 예시다.

코드

// ComposerDrawerWithRuntimeQueue.example.tsx
import { css } from "@mildang/styled-system/css";
import { useAuiState } from "@assistant-ui/react";
import { Composer } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var queueMockAdapter = {
  async run({ abortSignal }) {
    await new Promise((resolve) => {
      abortSignal.addEventListener("abort", () => resolve(), { once: true });
    });
    return {
      content: [{ type: "text", text: "대기열 데모 응답" }]
    };
  }
};
function MockQueueRuntimeProvider({ children }) {
  const runtime = useLocalRuntime(queueMockAdapter, {
    unstable_enableMessageQueue: true
  });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// ComposerDrawerWithRuntimeQueue.example.tsx
var wrapper = css({
  width: "100%",
  maxWidth: "chat.thread.maxWidth",
  padding: "16"
});
var RuntimeQueueDrawer = () => {
  const queueLength = useAuiState((state) => state.composer.queue.length);
  if (queueLength === 0) {
    return null;
  }
  return <Composer.Drawer count={queueLength} label="전송 대기 메시지">
      <Composer.Queue />
    </Composer.Drawer>;
};
var ComposerDrawerWithRuntimeQueueExample = () => <MockQueueRuntimeProvider>
    <div className={wrapper}>
      <Composer drawer={<RuntimeQueueDrawer />}>
        <Composer.Input aria-label="메시지" placeholder="실행 중 메시지를 추가해 보세요…" />
        <Composer.Footer>
          <Composer.TrailingActions>
            <Composer.Send />
          </Composer.TrailingActions>
        </Composer.Footer>
      </Composer>
    </div>
  </MockQueueRuntimeProvider>;
var ComposerDrawerWithRuntimeQueue_example_default = ComposerDrawerWithRuntimeQueueExample;
export {
  ComposerDrawerWithRuntimeQueue_example_default as default
};