@extrachill/chat 0.3.1 → 0.5.1
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/CHANGELOG.md +36 -0
- package/css/chat.css +189 -2
- package/dist/Chat.d.ts +10 -1
- package/dist/Chat.d.ts.map +1 -1
- package/dist/Chat.js +3 -2
- package/dist/api.d.ts +28 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +25 -2
- package/dist/components/ChatInput.d.ts +11 -4
- package/dist/components/ChatInput.d.ts.map +1 -1
- package/dist/components/ChatInput.js +68 -10
- package/dist/components/ChatMessage.d.ts +1 -0
- package/dist/components/ChatMessage.d.ts.map +1 -1
- package/dist/components/ChatMessage.js +23 -3
- package/dist/hooks/useChat.d.ts +28 -4
- package/dist/hooks/useChat.d.ts.map +1 -1
- package/dist/hooks/useChat.js +70 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/normalizer.d.ts.map +1 -1
- package/dist/normalizer.js +84 -1
- package/dist/types/api.d.ts +25 -1
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/message.d.ts +23 -0
- package/dist/types/message.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Chat.tsx +15 -0
- package/src/api.ts +39 -0
- package/src/components/ChatInput.tsx +173 -28
- package/src/components/ChatMessage.tsx +102 -5
- package/src/hooks/useChat.ts +120 -10
- package/src/index.ts +3 -1
- package/src/normalizer.ts +88 -3
- package/src/types/api.ts +26 -1
- package/src/types/index.ts +2 -0
- package/src/types/message.ts +24 -0
package/src/normalizer.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* maps them into the package's role-based message model.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { ChatMessage, ToolCall } from './types/message.ts';
|
|
10
|
+
import type { ChatMessage, MediaAttachment, ToolCall } from './types/message.ts';
|
|
11
11
|
import type { ChatSession } from './types/session.ts';
|
|
12
|
-
import type { RawMessage, RawSession } from './types/api.ts';
|
|
12
|
+
import type { RawMessage, RawAttachment, RawSession } from './types/api.ts';
|
|
13
13
|
|
|
14
14
|
let idCounter = 0;
|
|
15
15
|
function generateId(): string {
|
|
@@ -75,10 +75,16 @@ export function normalizeMessage(raw: RawMessage, index: number): ChatMessage {
|
|
|
75
75
|
const message: ChatMessage = {
|
|
76
76
|
id: generateId(),
|
|
77
77
|
role: raw.role === 'user' ? 'user' : 'assistant',
|
|
78
|
-
content: raw.content,
|
|
78
|
+
content: extractTextContent(raw.content),
|
|
79
79
|
timestamp,
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
// Extract attachments from metadata (user uploads and tool-produced media).
|
|
83
|
+
const attachments = normalizeAttachments(raw);
|
|
84
|
+
if (attachments.length > 0) {
|
|
85
|
+
message.attachments = attachments;
|
|
86
|
+
}
|
|
87
|
+
|
|
82
88
|
// Assistant messages may carry tool_calls at the top level
|
|
83
89
|
if (raw.role === 'assistant' && raw.tool_calls?.length) {
|
|
84
90
|
message.toolCalls = raw.tool_calls.map((tc, i) => ({
|
|
@@ -110,3 +116,82 @@ export function normalizeSession(raw: RawSession): ChatSession {
|
|
|
110
116
|
messageCount: raw.message_count,
|
|
111
117
|
};
|
|
112
118
|
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Extract text content from a message that may be multi-modal.
|
|
122
|
+
*
|
|
123
|
+
* When the backend sends multi-modal content, `content` is an array of
|
|
124
|
+
* content blocks. We extract the text portion for display.
|
|
125
|
+
*/
|
|
126
|
+
function extractTextContent(content: unknown): string {
|
|
127
|
+
if (typeof content === 'string') return content;
|
|
128
|
+
|
|
129
|
+
if (Array.isArray(content)) {
|
|
130
|
+
const textParts = content
|
|
131
|
+
.filter((block: Record<string, unknown>) => block.type === 'text' && typeof block.text === 'string')
|
|
132
|
+
.map((block: Record<string, unknown>) => block.text as string);
|
|
133
|
+
return textParts.join('\n');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return '';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Normalize a single raw attachment into a MediaAttachment.
|
|
141
|
+
*/
|
|
142
|
+
function normalizeRawAttachment(raw: RawAttachment): MediaAttachment | null {
|
|
143
|
+
if (!raw.url) return null;
|
|
144
|
+
|
|
145
|
+
const mimeType = raw.mime_type ?? '';
|
|
146
|
+
let type: MediaAttachment['type'] = 'file';
|
|
147
|
+
|
|
148
|
+
if (raw.type === 'image' || raw.type === 'video' || raw.type === 'file') {
|
|
149
|
+
type = raw.type;
|
|
150
|
+
} else if (mimeType.startsWith('image/')) {
|
|
151
|
+
type = 'image';
|
|
152
|
+
} else if (mimeType.startsWith('video/')) {
|
|
153
|
+
type = 'video';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const attachment: MediaAttachment = {
|
|
157
|
+
type,
|
|
158
|
+
url: raw.url,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (raw.alt) attachment.alt = raw.alt;
|
|
162
|
+
if (raw.filename) attachment.filename = raw.filename;
|
|
163
|
+
if (mimeType) attachment.mimeType = mimeType;
|
|
164
|
+
if (raw.size) attachment.size = raw.size;
|
|
165
|
+
if (raw.media_id) attachment.mediaId = raw.media_id;
|
|
166
|
+
if (raw.thumbnail_url) attachment.thumbnailUrl = raw.thumbnail_url;
|
|
167
|
+
|
|
168
|
+
return attachment;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Extract all attachments from a raw message.
|
|
173
|
+
*
|
|
174
|
+
* Checks metadata.attachments (user uploads) and metadata.media
|
|
175
|
+
* (tool-produced media) for renderable media.
|
|
176
|
+
*/
|
|
177
|
+
function normalizeAttachments(raw: RawMessage): MediaAttachment[] {
|
|
178
|
+
const attachments: MediaAttachment[] = [];
|
|
179
|
+
|
|
180
|
+
// User-uploaded attachments.
|
|
181
|
+
if (raw.metadata?.attachments) {
|
|
182
|
+
for (const rawAtt of raw.metadata.attachments) {
|
|
183
|
+
const att = normalizeRawAttachment(rawAtt);
|
|
184
|
+
if (att) attachments.push(att);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Tool-produced media.
|
|
189
|
+
if (raw.metadata?.media) {
|
|
190
|
+
for (const rawMedia of raw.metadata.media) {
|
|
191
|
+
const att = normalizeRawAttachment(rawMedia);
|
|
192
|
+
if (att) attachments.push(att);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return attachments;
|
|
197
|
+
}
|
package/src/types/api.ts
CHANGED
|
@@ -10,18 +10,36 @@
|
|
|
10
10
|
* A raw message as stored/returned by the backend.
|
|
11
11
|
* The package normalizes these into ChatMessage before rendering.
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* A raw media attachment as returned by the backend.
|
|
15
|
+
*/
|
|
16
|
+
export interface RawAttachment {
|
|
17
|
+
type?: string;
|
|
18
|
+
url?: string;
|
|
19
|
+
alt?: string;
|
|
20
|
+
filename?: string;
|
|
21
|
+
mime_type?: string;
|
|
22
|
+
size?: number;
|
|
23
|
+
media_id?: number;
|
|
24
|
+
thumbnail_url?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
export interface RawMessage {
|
|
14
28
|
role: 'user' | 'assistant';
|
|
15
29
|
content: string;
|
|
16
30
|
metadata?: {
|
|
17
31
|
timestamp?: string;
|
|
18
|
-
type?: 'text' | 'tool_call' | 'tool_result';
|
|
32
|
+
type?: 'text' | 'multimodal' | 'tool_call' | 'tool_result';
|
|
19
33
|
tool_name?: string;
|
|
20
34
|
parameters?: Record<string, unknown>;
|
|
21
35
|
success?: boolean;
|
|
22
36
|
error?: string;
|
|
23
37
|
turn?: number;
|
|
24
38
|
tool_data?: Record<string, unknown>;
|
|
39
|
+
/** Attachments sent with the message. */
|
|
40
|
+
attachments?: RawAttachment[];
|
|
41
|
+
/** Media produced by tool results. */
|
|
42
|
+
media?: RawAttachment[];
|
|
25
43
|
};
|
|
26
44
|
tool_calls?: Array<{
|
|
27
45
|
tool_name: string;
|
|
@@ -36,6 +54,13 @@ export interface SendRequest {
|
|
|
36
54
|
message: string;
|
|
37
55
|
session_id?: string;
|
|
38
56
|
agent_id?: number;
|
|
57
|
+
/** Media attachments to send with the message. */
|
|
58
|
+
attachments?: Array<{
|
|
59
|
+
url?: string;
|
|
60
|
+
media_id?: number;
|
|
61
|
+
mime_type?: string;
|
|
62
|
+
filename?: string;
|
|
63
|
+
}>;
|
|
39
64
|
}
|
|
40
65
|
|
|
41
66
|
export interface SendResponse {
|
package/src/types/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type {
|
|
|
2
2
|
MessageRole,
|
|
3
3
|
ToolCall,
|
|
4
4
|
ToolResultMeta,
|
|
5
|
+
MediaAttachment,
|
|
5
6
|
ChatMessage,
|
|
6
7
|
ContentFormat,
|
|
7
8
|
} from './message.ts';
|
|
@@ -13,6 +14,7 @@ export type {
|
|
|
13
14
|
} from './session.ts';
|
|
14
15
|
|
|
15
16
|
export type {
|
|
17
|
+
RawAttachment,
|
|
16
18
|
RawMessage,
|
|
17
19
|
RawSession,
|
|
18
20
|
SendRequest,
|
package/src/types/message.ts
CHANGED
|
@@ -38,6 +38,28 @@ export interface ToolResultMeta {
|
|
|
38
38
|
success: boolean;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* A media attachment on a chat message (image, video, or file).
|
|
43
|
+
*/
|
|
44
|
+
export interface MediaAttachment {
|
|
45
|
+
/** Media type. */
|
|
46
|
+
type: 'image' | 'video' | 'file';
|
|
47
|
+
/** Public URL of the media. */
|
|
48
|
+
url: string;
|
|
49
|
+
/** Alt text or description. */
|
|
50
|
+
alt?: string;
|
|
51
|
+
/** Original filename. */
|
|
52
|
+
filename?: string;
|
|
53
|
+
/** MIME type (e.g. 'image/jpeg'). */
|
|
54
|
+
mimeType?: string;
|
|
55
|
+
/** File size in bytes. */
|
|
56
|
+
size?: number;
|
|
57
|
+
/** WordPress media library attachment ID. */
|
|
58
|
+
mediaId?: number;
|
|
59
|
+
/** Thumbnail URL for previews. */
|
|
60
|
+
thumbnailUrl?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
41
63
|
/**
|
|
42
64
|
* A single message in a chat conversation.
|
|
43
65
|
*/
|
|
@@ -54,6 +76,8 @@ export interface ChatMessage {
|
|
|
54
76
|
toolCalls?: ToolCall[];
|
|
55
77
|
/** Tool result metadata (only on tool_result messages). */
|
|
56
78
|
toolResult?: ToolResultMeta;
|
|
79
|
+
/** Media attachments (images, videos, files) on this message. */
|
|
80
|
+
attachments?: MediaAttachment[];
|
|
57
81
|
}
|
|
58
82
|
|
|
59
83
|
/**
|