본문으로 건너뛰기
색인되지 않은 문서
이 문서는 색인되지 않습니다. 검색 엔진이 이 문서를 색인하지 않으며, 주소를 알고 있는 사용자만 접근할 수 있습니다.

Thinkfree 채팅 UI를 자체 루프와 함께 사용

Editor SDK와 Office 서버가 필요합니다

채팅 UI는 문서를 여는 Office 서버가 제공합니다. 애플리케이션 페이지가 그 UI를 iframe으로 띄우고 @thinkfree.dev/tfo-ai-sdk/chat-ui의 createChatUiBridge()로 구동합니다. 문서 도구는 여전히 Office 툴킷에서 옵니다. 먼저 빠른 시작을 완료하십시오.

Thinkfree 채팅 UI는 진행 칩, 알림 카드, 선택 요청, 편집 계획 승인, 첨부를 제공합니다. 채팅 UI 브리지를 쓰면 이 UI를 그대로 두고 두 가지만 가져오면 됩니다 - 도구 루프와 모델 연결. 브리지는 채팅 UI 도구 8종을 모델에 노출하고, 모델의 출력을 UI에 렌더링하며, 사용자의 행동(선택·승인·중단)을 루프에 돌려줍니다.

구성 요소제공 주체
채팅 UI (React, Office 서버가 제공)Thinkfree
문서 도구, 프롬프트 규칙, 영수증Office 툴킷 (@thinkfree.dev/tfo-sdk)
채팅 UI 도구, 프롬프트 규칙, turn 상태, 핸드셰이크채팅 UI 브리지 (@thinkfree.dev/tfo-ai-sdk/chat-ui)
도구 루프, 모델 연결, 릴레이애플리케이션

1. 채팅 UI를 external 모드로 로드​

Office 서버의 채팅 UI 페이지를 mode=external로 iframe에 띄웁니다. 이 모드에서 페이지는 UI만 렌더링하고 자체 에이전트를 시작하는 대신 여러분의 창과 통신합니다.

<iframe id="chat-frame" title="Thinkfree chat UI"
src="https://office.example.com/webword/commonFrame/js/addIn/tfoAiAssistant/index.html?mode=external&brainOrigin=https%3A%2F%2Fapp.example.com&module=word&lang=en"></iframe>
쿼리 매개변수필수의미
mode=external예UI만 렌더링합니다. 에이전트는 이 iframe 밖에 있습니다
brainOrigin예브리지를 만드는 페이지(여러분의 페이지)의 정확한 origin. *는 거부됩니다
brainWindow=opener아니요window.open()으로 UI를 띄운 경우 window.parent 대신 window.opener로 보냅니다
module아니요word, cell, show - 시작 화면과 빠른 작업을 선택합니다
lang아니요UI 언어. 예: ko_KR, en

2. 브리지 생성​

import { createOfficeToolkit } from "@thinkfree.dev/tfo-sdk";
import { createChatUiBridge } from "@thinkfree.dev/tfo-ai-sdk/chat-ui";

const toolkit = await createOfficeToolkit(word, { provider: "anthropic", preset: "core" });
const chatFrame = document.querySelector("#chat-frame");

const chat = createChatUiBridge({
peerWindow: chatFrame.contentWindow,
peerOrigin: "https://office.example.com", // exact origin of the chat UI, never "*"
provider: "anthropic", // same wire shape as toolkit.tools
language: { code: "en", name: "English" }, // language named in the prompt rules
listTools: () => toolkit.catalog, // answers the "/tools" command locally
onUserInput: runTurn, // your loop - see step 3
onAbort: () => console.log("user pressed stop"),
onSettingsChange: (settings, ctx) => { /* ctx.systemPrompt changed; use it on the next turn */ },
});
핸들 멤버용도
chat.tools공급자 와이어 형식의 채팅 UI 도구 8종. toolkit.tools와 함께 보냅니다
chat.systemPrompt채팅 UI 도구 규칙(편집 수락 또는 자동 진행 모드, 개발자 모드). toolkit.systemPrompt 뒤에 붙입니다
chat.handles(name)도구 호출이 채팅 UI의 것이면 true. 그 호출은 chat.dispatch로, 나머지는 toolkit.dispatch로 보냅니다
chat.dispatch(name, args)채팅 UI 도구를 실행하고 영수증으로 끝납니다. 대화형 카드는 사용자가 응답할 때 끝납니다
chat.notify(text)모델의 최종 텍스트를 카드로 렌더링합니다. 이번 turn에 이미 카드로 보고했으면 생략됩니다
chat.settings현재 어시스턴트 설정: mode(edit-accept 또는 auto-proceed)와 developerMode
chat.destroy()리스너를 제거하고 대기 중인 카드를 닫습니다

3. 사용자 입력마다 루프 실행​

onUserInput은 사용자가 보낸 텍스트와 첨부, 그리고 turn 컨텍스트를 받습니다. 모델의 최종 텍스트를 반환하면 브리지가 렌더링하고, 예외를 던지면 오류 카드를 띄웁니다. 사용자가 중단을 누르면 turn.signal이 abort됩니다.

async function runTurn({ text, attachments }, turn) {
const system = `${toolkit.systemPrompt}\n\n${chat.systemPrompt}`;
const tools = [...toolkit.tools, ...chat.tools];
const history = [{ role: "user", content: text }];
for (let step = 0; step < 12; step++) {
if (turn.signal.aborted) return "";
const res = await createMessage({ system, messages: history, tools }); // see the quickstart
history.push({ role: "assistant", content: res.content });
if (res.stop_reason !== "tool_use") {
return res.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
}
const results = [];
for (const call of res.content.filter((b) => b.type === "tool_use")) {
const receipt = chat.handles(call.name)
? await chat.dispatch(call.name, call.input)
: await toolkit.dispatch(call.name, call.input);
results.push({ type: "tool_result", tool_use_id: call.id,
content: JSON.stringify(receipt), is_error: !receipt.success });
}
history.push({ role: "user", content: results });
}
return "";
}

두 dispatch 함수 모두 영수증을 반환하므로 모델은 문서 도구와 채팅 UI 도구에서 같은 결과 형태를 읽습니다. 모델이 show_request_input이나 show_edit_plan_list를 호출하면 chat.dispatch는 사용자가 선택·수락·닫기를 할 때까지 기다리고, 영수증 텍스트가 무슨 일이 있었는지 모델에 알립니다. 편집 수락 모드(기본)에서는 프롬프트 규칙이 모델에게 문서 도구를 호출하기 전에 편집 계획을 보여 주고 승인을 기다리라고 지시하므로, 이 경로에 대해 루프가 승인 단계를 따로 추가할 필요가 없습니다.

4. 핸드셰이크와 수명 주기​

  • 핸드셰이크. 브리지는 생성될 때 tfo-ai:hello를 보내고, UI는 프로토콜 버전을 담은 tfo-ai:ready로 응답합니다. UI는 로드될 때도 ready를 보냅니다. 어느 쪽이 먼저 떠도 됩니다. ready 전에 브리지가 보낸 메시지는 큐에 들어갑니다. 버전이 다르면 UI가 오류 카드를 띄우고 브리지는 멈추며 onProtocolMismatch가 호출됩니다.
  • turn 상태. 브리지는 onUserInput이 시작될 때 turn 진행 중으로, 끝날 때 완료로 표시합니다. 그동안 UI는 중단 버튼을 보여 주고 추가 입력을 큐에 넣습니다.
  • 중단. 사용자의 중단은 turn.signal을 abort하고 onAbort를 호출하며, 대기 중인 카드를 닫아 보류 중인 chat.dispatch 호출이 끝나게 합니다. 중단 뒤에 도착한 결과나 오류는 버려집니다.
  • 설정. 사용자가 UI에서 어시스턴트 설정을 바꾸면 onSettingsChange(settings, { systemPrompt, notice })가 호출됩니다. 새 systemPrompt는 다음 turn부터 사용하고, notice는 모델이 변경을 알아차리도록 대화에 넣을 수 있는 짧은 메시지입니다.
  • 정리. iframe을 제거할 때 chat.destroy()를 호출합니다.

프로토콜 레퍼런스 (v1)​

모든 메시지는 양쪽 모두 정확한 origin을 지정한 window.postMessage({ type, payload }, origin)입니다.

메시지방향페이로드
tfo-ai:readyUI → 브리지{ protocolVersion: 1, capabilities?: string[] }
tfo-ai:hello브리지 → UI{ protocolVersion: 1 }
tfo-ai:user-inputUI → 브리지{ text, attachments?: [{ fileName, mimeType, size, base64 }] }
tfo-ai:user-actionUI → 브리지{ componentId, action: "select" | "select-custom" | "accept-all" | "close", value? }
tfo-ai:set-assistant-settingsUI → 브리지{ settings: { mode, developerMode } }
tfo-ai:abort-turn, tfo-ai:clear-conversationUI → 브리지없음
tfo-ai:component-add / -update / -remove브리지 → UI{ id, kind, data } / { id, data } / { id }
tfo-ai:turn-state브리지 → UI{ inProgress }
tfo-ai:trigger-input브리지 → UI{ text } - 사용자가 입력한 것처럼 발화를 넣습니다

컴포넌트 종류는 progress-status, notification, request-input, edit-plan-list, tool-call-info이며, 채팅 UI 도구 (show_progress_status, update_progress_status, show_notification, show_request_input, show_edit_plan_list, update_edit_plan_list, show_tool_call_info, remove_component)가 이를 만듭니다. 컴포넌트 메시지를 직접 보낼 필요는 없습니다. 모델이 채팅 UI 도구를 호출하면 브리지가 보냅니다.

이번 릴리스의 제한​

  • 첨부는 base64로 onUserInput에 전달되며 브리지가 어디에도 업로드하지 않습니다. 텍스트·이미지 첨부는 실용적이지만, Office 바이너리 첨부(예: .pptx)는 external 모드에서 브리지되지 않습니다.
  • 채팅 UI는 Office 서버가 제공하므로 거기서 로드해야 합니다. 자체 도메인에서 호스팅하는 것은 지원하지 않습니다.
  • 프롬프트 규칙은 한국어로 작성되어 있고 UI 언어가 그 안에 명시됩니다. 모델은 사용자의 언어로 답합니다.