주간 점검 요약
요약
- 결제 실패 알림을 이번 배포에 넣는다
- 환불 정책 문구는 법무 검토 뒤 다음 배포로 미룬다
- 볼 권한이 없는 출처에서 나온 줄이라 숨겼습니다
결정
- 웹훅 서명 검증 없이는 배포하지 않는다
할 일
- 환불 정책 문구 정리해 법무팀에 보내기
- 웹훅 서명 검증 구현
AI 비서 패널 · 요약 카드 · 출처 인용 · ‘AI 생성’ 표시 · 사용량입니다. AI 가 만든 것에는 언제나 ‘AI 생성’ 표시와 출처가 붙습니다.
묻는 말은 오른쪽 파인 칸, 답은 넓은 글입니다. 흐르는 동안에는 중지를, 끝나면 복사 · 다시 생성 · 피드백을 줍니다. 대화는 role=log 이지만 흐르는 글자는 읽지 않고 끝난 답만 한 번 읽습니다.
| 키 | 동작 |
|---|---|
| Enter | 보내기. 한 · 일 조합 중의 Enter 는 글자 확정이라 보내지 않는다 |
| Shift+Enter | 줄바꿈. 8줄까지 자라고 그 뒤로는 칸 안에서 스크롤한다 |
| Esc | 응답 중이면 중지(패널 어디에서나) |
| ↑ | 빈 묻는 칸에서 마지막 질문을 불러와 고친다 |
| ⌘⇧C | 패널 안에서 마지막 답을 마크다운으로 복사 |
| Tab | 답의 인용 칩 → 출처 → 동작(복사 · 다시 생성 · 좋아요 · 싫어요) 순 |
| F6 | 앱 셸의 영역 이동으로 패널에 들어오고 나간다(패널은 aside 영역) |
회의 · 채널을 출처가 달린 한 장으로 줄입니다. 요약은 상태가 아니라 내용이라 상태 색을 쓰지 않습니다. 공유받은 사람에게는 권한 없는 줄을 숨기되 숨겼다는 자리는 남깁니다.
| 키 | 동작 |
|---|---|
| Tab | 메뉴 · 인용 칩 · 할 일 체크 · ‘작업으로’ · 바닥 동작 순 |
| Enter | 인용 칩: 원문 자리를 연다(기록은 Transcript 의 그 줄) |
| 포커스 머묾 | 인용 칩의 원문 미리보기(비모달). Esc 로 닫고 칩에 남는다 |
| Space | 할 일 완료 토글 |
| ⌘C | 카드 안에서 아무것도 고르지 않았으면 요약 전체를 각주 마크다운으로 |
문장 끝 번호 칩은 링크이고, 이름에 출처 종류 · 시각 · 사람을 담습니다(‘출처 2: 기록 14:12, 김하늘’). 미리보기는 툴팁이 아니라 비모달 Hover Card 라 포커스로도 열립니다.
환불 정책 문구는 법무 검토 뒤 다음 배포로 미룹니다 . 칩에 손을 올리거나 Tab 으로 머물면 원문이 뜨고, 누르면 그 출처를 짚습니다.
‘누가 썼나’를 말하는 표시라 5톤 Badge 와 따로 둡니다. 중립 면 + 반짝 모양 + 글자이고, 끌 수 없습니다(숨기는 prop 이 없습니다). 사람이 고치면 ‘AI 생성 · ○○이 고침’, 사람이 받아 넣으면 라벨이 사라지고 기록에 남습니다.
이번 달 AI 사용량
11월 1일 00:00(한국 표준시)에 다시 채워집니다. 워크스페이스 전체가 함께 씁니다.
상한은 오류가 아니라 정해진 한도라 빨강을 쓰지 않습니다. 언제 다시 쓸 수 있는지와 누구에게 물을지를 꼭 말합니다. 잠깐 너무 많음(rate)의 남은 시간은 화면에서는 1초마다, 스크린 리더에는 10초마다 알립니다.
이번 달 AI 사용량을 86% 썼습니다
이번 달 AI 사용량을 모두 썼습니다
이 워크스페이스에서는 AI 를 쓰지 않습니다
| 키 | 동작 |
|---|---|
| Tab | 안내의 동작(상한 늘리기 요청 · 요금제 보기) → 닫기 |
| Esc | 닫을 수 있는 안내(80% 넘음)를 닫는다 |
소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| messages필수 | AssistantMessage[] | — | 대화. 앱이 정본이다(제어만) |
| onSend필수 | (text: string, scope: string) => void | Promise<void> | — | 보내기. Promise 가 실패하면 묻는 칸에 글을 되돌린다 |
| onStop필수 | (messageId: string) => void | — | |
| onRegenerate필수 | (messageId: string) => void | — | |
| onContinue | (messageId: string) => void | — | 중지한 답 이어서 쓰기. 없으면 중지한 답에도 다시 생성을 둔다 |
| onVersionChange | (messageId: string, index: number) => void | — | |
| onCopy | (messageId: string, markdown: string) => void | — | 복사한 뒤. 클립보드에는 패널이 마크다운 원문을 쓴다 |
| onFeedback | (messageId: string, value: "up" | "down" | null, detail?: FeedbackDetail) => void | — | 없으면 피드백 단추를 그리지 않는다 |
| onOpenSource | (source: Source) => void | — | |
| scope필수 | string | — | 지금 범위의 id. 앱이 정본이다 |
| scopes필수 | AssistantScope[] | — | |
| onScopeChange | (id: string) => void | — | |
| suggestions | AssistantSuggestion[] | — | 빈 패널의 제안 칩. 누르면 그 prompt 를 보낸다 |
| usage | AssistantUsage | null | — | 80% 를 넘으면 대화 위에 한 번 알린다 |
| disabled | AssistantDisabled | null | — | 관리자가 끔 · 요금제에 없음 · 상한 도달 · 잠깐 너무 많음. 묻는 칸이 비활성이 되고 이유를 잇는다 |
| onRequestLimit | () => void | — | |
| onSeePlans | () => void | — | |
| onRateReady | () => void | — | 잠깐 너무 많음(rate)의 시간이 다 됐을 때. 앱이 disabled 를 푼다 |
| draftKey | string | — | 대화별 초안을 sessionStorage 에 남기는 열쇠. 없으면 남기지 않는다 |
| onLongPaste | (text: string) => boolean | undefined | — | 문서 수준의 긴 붙여넣기(2,000자 이상, 또는 400자 이상이면서 제목 · 표 · 코드펜스가 보임). 첨부로 바꿀지는 앱이 정한다 — true 를 돌려주면 묻는 칸에 붙이지 않는다. 없으면 그대로 붙는다 |
| onNewThread | () => void | — | |
| onClose | () => void | — | |
| title | ReactNode | — | 머리 이름. 기본 labels.title |
| stream | StreamQueueHub | null | — | 앱이 SSE 델타를 넣는 흐름 큐(createStreamQueue). 없으면 messages 의 content 를 그대로 그린다 |
| renderMarkdown | RenderMarkdown | — | 답 본문 렌더. ./markdown 의 Markdown 을 넣는다. 없으면 문단 · 목록 · [n] 칩만 아는 글자 그림이다. ./ai 가 마크다운 의존성(react-markdown)을 끌고 오지 않게 주입받는다 |
| autoFocus | boolean | true | 처음 그릴 때 묻는 칸에 포커스(마우스 · 트랙패드 화면에서만). 기본 true |
| timeZone | string | — | IANA 시간대. 출처 시각 · 리셋 날짜(UTC)를 이 시간대로 적는다. 기본 서울 |
| className | string | — | |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<AssistantLabels> | — | |
| usageLabels | Partial<UsageNoticeLabels> | — | 사용량 안내(UsageNotice)의 문구 |
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| kind필수 | "meeting" | "channel" | "thread" | "document" | — | |
| title필수 | ReactNode | — | |
| source필수 | { label: string; startedAt?: string; endedAt?: string; size?: string } | — | 무엇을 요약했는지. size 는 ‘기록 45분’ · ‘메시지 48개’ 처럼 앱이 적는다 |
| sections필수 | SummarySection[] | — | |
| todos | SummaryTodo[] | — | 할 일. 사람이 확인하고 ‘작업으로’ 옮긴다(자동으로 만들지 않는다) |
| sources필수 | Source[] | — | |
| withheldCount | number | 0 | 권한으로 뺀 출처 수. 0 이면 문장을 그리지 않는다 |
| status필수 | "generating" | "done" | "stale" | "insufficient" | "error" | — | |
| staleCount | number | 0 | 요약 뒤로 생긴 새 메시지 수(stale) |
| editedBy | { name: string; at: string } | null | — | 사람이 고쳤으면. ‘AI 생성 · ○○이 고침’ 이 되고, 다시 요약하기 전에 묻는다 |
| onRegenerate | () => void | — | |
| onEdit | () => void | — | |
| onSaveAsDoc | () => void | — | |
| onCreateTask | (todo: SummaryTodo) => void | — | |
| onTodoChange | (id: string, done: boolean) => void | — | 할 일 완료(제어). 없으면 체크박스가 읽기 전용이다 |
| onOpenSource | (source: Source) => void | — | |
| onFeedback | (value: "up" | "down" | null) => void | — | |
| feedback | "up" | "down" | null | — | |
| onMarkAllRead | () => void | — | 채널 요약의 ‘모두 읽음으로’ |
| headingLevel | 2 | 3 | 4 | 5 | 3 | 카드 제목의 단계(카드가 놓인 자리의 제목 수준 + 1). 단락 제목은 그 아래 단계다. 기본 3 |
| timeZone | string | — | IANA 시간대. 회의 시각 · 출처 시각(UTC)을 이 시간대로 적는다. 기본 서울 |
| className | string | — | |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<SummaryLabels> | — | |
| citationLabels | Partial<CitationLabels> | — | 인용 칩 · 미리보기의 문구 |
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| timeZone | string | — | IANA 시간대. 출처 시각(UTC)을 이 시간대로 적는다. 기본 서울 |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<CitationLabels> | — | |
| n필수 | number | — | 답 · 요약 안의 번호(1부터) |
| source필수 | Source | — | |
| current | boolean | — | 지금 짚은 출처(출처 목록 · 기록과 같은 번호가 강조된다). aria-current |
| onOpen | (source: Source) => void | — | 칩을 누를 때. 주면 링크 이동 대신 이것을 부른다(기록은 Transcript 의 targetId 로) |
| open | boolean | — | 미리보기 열림(제어). 없으면 손 올림 · 포커스로 스스로 여닫는다 |
| onOpenChange | (open: boolean) => void | — | |
| className | string | — |
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| timeZone | string | — | IANA 시간대. 출처 시각(UTC)을 이 시간대로 적는다. 기본 서울 |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<CitationLabels> | — | |
| sources필수 | Source[] | — | |
| citations | CitationRef[] | — | [n] → 출처. 주면 그 번호로, 없으면 목록의 자리(1부터)로 번호를 단다 |
| withheldCount | number | — | 권한으로 뺀 출처 수. 0 이거나 없으면 문장을 그리지 않는다 |
| currentId | string | — | 지금 짚은 출처(aria-current) |
| onOpen | (source: Source) => void | — | |
| className | string | — |
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| size | "sm" | "md" | — | sm 은 한 줄(묻는 칸 위 · 표 위). 기본 md |
| timeZone | string | — | IANA 시간대. 리셋 날짜(UTC)를 이 시간대로 적는다. 기본 서울 |
| id | string | — | 비활성 묻는 칸이 aria-describedby 로 이을 id |
| className | string | — | |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<UsageNoticeLabels> | — | |
| kind필수 | "near" | "reached" | "rate" | "disabled" | "plan" | — | |
| used | number | — | |
| limit | number | — | |
| resetsAt | string | — | 다시 채워지는 시각(UTC) |
| onDismiss | () => void | — | |
| onRequest | () => void | — | |
| canManage | boolean | — | 상한을 바꿀 수 있는 사람이면 요청 대신 바꾸기 |
| onManage | () => void | — | |
| retryAfterSec | number | — | |
| onReady | () => void | — | |
| admin | string | — | |
| plan | string | — | |
| onUpgrade | () => void | — |
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| kind | "generated" | "suggested" | "generated" | generated = AI 가 만든 글, suggested = 사람이 아직 받지 않은 제안(폼 칸 · 편집기). 기본 generated |
| variant | "badge" | "text" | "badge" | badge = 중립 면 라벨, text = 바이라인(면 없이 보조 색). 기본 badge |
| size | "sm" | "md" | "md" | sm 16(줄 안 · 촘촘한 목록) · md 20. 기본 md |
| editedBy | string | null | — | 사람이 고쳤으면 그 사람 이름. 'AI 생성 · ○○이 고침' 이 되고 라벨은 떼지 않는다 |
| sourcesCount | number | — | text 변형에서 뒤에 붙는 출처 수(‘· 출처 3’) |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<AiLabelLabels> | — |
이 밖에 <span> props(children · hidden 제외)를 그대로 받습니다.
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| size | "md" | "lg" | "md" | md 24(대화 · 알림) · lg 32(요약 카드 머리 · 채널 메시지). 기본 md |
이 밖에 <span> props(children 제외)를 그대로 받습니다.
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| items필수 | UsageMeterItem[] | — | |
| resetsAt필수 | string | — | 다시 채워지는 시각(UTC). 보는 사람의 시간대로 바꿔 시간대 이름과 함께 적는다 |
| scope | "workspace" | "user" | — | 누가 함께 쓰는 한도인지 한 문장. 기본 없음 |
| title | ReactNode | — | 제목. 기본 labels.title, null 이면 그리지 않는다(카드 제목이 따로 있을 때) |
| disabled | boolean | { admin?: string } | — | 관리자가 AI 를 껐다. 막대 대신 이유 문장 |
| timeZone | string | — | IANA 시간대. 기본 서울 |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<UsageMeterLabels> | — |
이 밖에 <div> props(title 제외)를 그대로 받습니다.