react-optimistic-chat 2.2.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,7 +92,8 @@ async function getChat(roomId: string, page: number): Promise<Raw[]> {
|
|
|
92
92
|
const json = await res.json();
|
|
93
93
|
return json.result;
|
|
94
94
|
}
|
|
95
|
-
|
|
95
|
+
```
|
|
96
|
+
```ts
|
|
96
97
|
async function sendAI(content: string): Promise<Raw> {
|
|
97
98
|
const res = await fetch(`/sendAI`, {
|
|
98
99
|
method: "POST",
|
|
@@ -116,7 +117,10 @@ async function sendAI(content: string): Promise<Raw> {
|
|
|
116
117
|
<code>ChatContainer</code> 컴포넌트에 전달해 채팅 UI + 무한 스크롤을 구성합니다.
|
|
117
118
|
|
|
118
119
|
이때 서버의 Raw 데이터를 Message 타입의
|
|
119
|
-
<code>id</code>, <code>role</code>, <code>content</code> 필드에
|
|
120
|
+
<code>id</code>, <code>role</code>, <code>content</code> 필드에 **키 기반으로 매핑**합니다.
|
|
121
|
+
|
|
122
|
+
- `keyMap`은 **어떤 Raw 필드가 Message의 필드에 대응되는지 선언**합니다.
|
|
123
|
+
- `roleResolver`는 Raw의 role 값을 내부 표준 role 타입(`"AI" | "USER"`)으로 **정규화**합니다.
|
|
120
124
|
|
|
121
125
|
```tsx
|
|
122
126
|
export default function ChatExample() {
|
|
@@ -130,7 +134,7 @@ export default function ChatExample() {
|
|
|
130
134
|
fetchNextPage,
|
|
131
135
|
hasNextPage,
|
|
132
136
|
isFetchingNextPage,
|
|
133
|
-
} = useChat
|
|
137
|
+
} = useChat({
|
|
134
138
|
queryKey: ["chat", roomId],
|
|
135
139
|
queryFn: (pageParam) => getChat(roomId, pageParam as number),
|
|
136
140
|
initialPageParam: 0,
|
|
@@ -140,11 +144,14 @@ export default function ChatExample() {
|
|
|
140
144
|
|
|
141
145
|
mutationFn: sendAI,
|
|
142
146
|
|
|
143
|
-
|
|
144
|
-
id:
|
|
145
|
-
role:
|
|
146
|
-
content:
|
|
147
|
+
keyMap: {
|
|
148
|
+
id: "chatId",
|
|
149
|
+
role: "sender",
|
|
150
|
+
content: "body",
|
|
147
151
|
}),
|
|
152
|
+
roleResolver: (sender) => {
|
|
153
|
+
return sender === "ai" ? "AI" : "USER"
|
|
154
|
+
},
|
|
148
155
|
});
|
|
149
156
|
|
|
150
157
|
return (
|
|
@@ -226,13 +233,17 @@ type Raw = {
|
|
|
226
233
|
서버로부터 다음과 같은 <code>Raw</code> 채팅 데이터가 전달된다고 가정합니다.
|
|
227
234
|
|
|
228
235
|
```ts
|
|
229
|
-
|
|
230
|
-
id:
|
|
231
|
-
role:
|
|
232
|
-
content:
|
|
233
|
-
}
|
|
236
|
+
keyMap: {
|
|
237
|
+
id: "messageId",
|
|
238
|
+
role: "sender",
|
|
239
|
+
content: "text",
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
roleResolver: (sender) =>
|
|
243
|
+
sender === "user" ? "USER" : "AI",
|
|
244
|
+
|
|
234
245
|
```
|
|
235
|
-
Hook에서 필수로 제공하는 <code>
|
|
246
|
+
Hook에서 필수로 제공하는 <code>keyMap</code>과 <code>roleResolver</code>를 다음과 같이 정의하면
|
|
236
247
|
|
|
237
248
|
```ts
|
|
238
249
|
{
|
|
@@ -315,11 +326,14 @@ const {
|
|
|
315
326
|
initialPageParam: 0,
|
|
316
327
|
getNextPageParam,
|
|
317
328
|
mutationFn: sendAI,
|
|
318
|
-
|
|
319
|
-
id:
|
|
320
|
-
role:
|
|
321
|
-
content:
|
|
322
|
-
}
|
|
329
|
+
keyMap: {
|
|
330
|
+
id: "chatId",
|
|
331
|
+
role: "sender",
|
|
332
|
+
content: "body",
|
|
333
|
+
},
|
|
334
|
+
roleResolver: (sender) => {
|
|
335
|
+
return sender === "ai" ? "AI" : "USER"
|
|
336
|
+
}
|
|
323
337
|
});
|
|
324
338
|
```
|
|
325
339
|
|
|
@@ -346,7 +360,8 @@ const {
|
|
|
346
360
|
| `initialPageParam` | `unknown` | ✅ | 첫 페이지 요청 시 사용할 pageParam |
|
|
347
361
|
| `getNextPageParam` | `(lastPage: Message[], allPages: Message[][]) => unknown` | ✅ | 다음 페이지 요청을 위한 pageParam 계산 함수 |
|
|
348
362
|
| `mutationFn` | `(content: string) => Promise<Raw>` | ✅ | 유저 입력을 받아 AI 응답 1개를 반환하는 함수 |
|
|
349
|
-
| `
|
|
363
|
+
| `keyMap` | `{ id: keyof Raw; role: keyof Raw; content: keyof Raw }` | ✅ | Raw 필드와 Message(id, role, content) 필드 간의 키 매핑 정의 |
|
|
364
|
+
| `roleResolver` | `(value: Raw[KeyMap["role"]]) => "AI" \| "USER"` | ✅ | Raw의 role 값을 내부 표준 role 타입으로 정규화하는 함수 |
|
|
350
365
|
| `onError` | `(error: unknown) => void` | ❌ | mutation 에러 발생 시 호출되는 콜백 |
|
|
351
366
|
| `staleTime` | `number` | ❌ | 캐시가 fresh 상태로 유지되는 시간 (ms) |
|
|
352
367
|
| `gcTime` | `number` | ❌ | 캐시가 GC 되기 전까지 유지되는 시간 (ms) |
|
|
@@ -484,11 +499,14 @@ const {
|
|
|
484
499
|
initialPageParam: 0,
|
|
485
500
|
getNextPageParam,
|
|
486
501
|
mutationFn: sendAI,
|
|
487
|
-
|
|
488
|
-
id:
|
|
489
|
-
role:
|
|
490
|
-
content:
|
|
491
|
-
}
|
|
502
|
+
keyMap: {
|
|
503
|
+
id: "chatId",
|
|
504
|
+
role: "sender",
|
|
505
|
+
content: "body",
|
|
506
|
+
},
|
|
507
|
+
roleResolver: (sender) => {
|
|
508
|
+
return sender === "ai" ? "AI" : "USER"
|
|
509
|
+
}
|
|
492
510
|
});
|
|
493
511
|
```
|
|
494
512
|
|
|
@@ -517,7 +535,8 @@ const {
|
|
|
517
535
|
| `initialPageParam` | `unknown` | ✅ | 첫 페이지 요청 시 사용할 pageParam |
|
|
518
536
|
| `getNextPageParam` | `(lastPage: Message[], allPages: Message[][]) => unknown` | ✅ | 다음 페이지 요청을 위한 pageParam 계산 함수 |
|
|
519
537
|
| `mutationFn` | `(content: string) => Promise<Raw>` | ✅ | 음성 인식 결과를 받아 AI 응답 1개를 반환하는 함수 |
|
|
520
|
-
| `
|
|
538
|
+
| `keyMap` | `{ id: keyof Raw; role: keyof Raw; content: keyof Raw }` | ✅ | Raw 필드와 Message(id, role, content) 필드 간의 키 매핑 정의 |
|
|
539
|
+
| `roleResolver` | `(value: Raw[KeyMap["role"]]) => "AI" \| "USER"` | ✅ | Raw의 role 값을 내부 표준 role 타입으로 정규화하는 함수 | |
|
|
521
540
|
| `onError` | `(error: unknown) => void` | ❌ | mutation 에러 발생 시 호출되는 콜백 |
|
|
522
541
|
| `staleTime` | `number` | ❌ | 캐시가 fresh 상태로 유지되는 시간 (ms) |
|
|
523
542
|
| `gcTime` | `number` | ❌ | 캐시가 GC 되기 전까지 유지되는 시간 (ms) |
|
|
@@ -874,24 +893,27 @@ page[0] = [
|
|
|
874
893
|
|
|
875
894
|
<br>
|
|
876
895
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
See the [LICENSE](./LICENSE) file for details.
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
896
|
+
## 5. Tailwind 클래스를 적용할 때 "!" 접두사가 필요할 수 있습니다
|
|
897
|
+
이 라이브러리의 UI 컴포넌트들은 **내부 스타일을 순수 CSS로 정의**하고 있습니다.
|
|
893
898
|
|
|
899
|
+
이로 인해 외부에서 Tailwind 클래스(className)을 통해 동일한 CSS 속성을 덮어쓰는 경우,
|
|
900
|
+
CSS 우선 순위에 따라 스타일이 적용되지 않을 수 있습니다.
|
|
894
901
|
|
|
902
|
+
이 경우 Tailwind의 **important modifier(!)** 를 사용하면
|
|
903
|
+
내부 CSS보다 우선하여 스타일을 적용할 수 있습니다.
|
|
904
|
+
```
|
|
905
|
+
<ChatMessage
|
|
906
|
+
content="Hello!"
|
|
907
|
+
bubbleClassName="!bg-black !text-white"
|
|
908
|
+
/>
|
|
909
|
+
```
|
|
910
|
+
이는 **컴포넌트 내부 CSS와 사용자 정의 스타일 간의 우선순위 충돌을 명시적으로 해결하기 위한 방식**입니다.
|
|
911
|
+
> 기본 스타일을 유지하면서 일부 속성만 변경하고 싶은 경우,
|
|
912
|
+
> 실제로 충돌하는 클래스에만 선택적으로 `!`를 사용하는 것을 권장합니다.
|
|
895
913
|
|
|
914
|
+
<br>
|
|
896
915
|
|
|
916
|
+
# 📄 License
|
|
917
|
+
MIT License © 2025
|
|
918
|
+
See the [LICENSE](./LICENSE) file for details.
|
|
897
919
|
|