@arcote.tech/arc-chat 0.4.7 → 0.5.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/package.json +6 -6
- package/src/aggregates/message.ts +68 -78
- package/src/chat-builder.ts +75 -51
- package/src/index.ts +22 -4
- package/src/listeners/ai-generation-listener.ts +293 -0
- package/src/react/index.ts +8 -9
- package/src/react/use-chat.ts +260 -0
- package/src/routes/chat-stream-route.ts +31 -0
- package/src/routes/tool-results-route.ts +49 -0
- package/src/streaming/stream-registry.ts +146 -0
- package/src/aggregates/conversation.ts +0 -151
- package/src/react/chat-input.tsx +0 -79
- package/src/react/chat-message.tsx +0 -100
- package/src/react/chat.tsx +0 -117
- package/src/react/question-tabs.tsx +0 -157
- package/src/react/tool-use-block.tsx +0 -34
- package/src/react/types.ts +0 -36
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { Trans } from "@arcote.tech/platform";
|
|
2
|
-
import { Box, Button, TextareaField } from "@arcote.tech/arc-ds";
|
|
3
|
-
import { Send, Check } from "lucide-react";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
-
import type { Question, QuestionAnswers } from "./types";
|
|
6
|
-
|
|
7
|
-
interface QuestionTabsProps {
|
|
8
|
-
questions: Question[];
|
|
9
|
-
onSubmit: (answers: QuestionAnswers) => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function QuestionTabs({ questions, onSubmit }: QuestionTabsProps) {
|
|
13
|
-
const [activeTab, setActiveTab] = useState(0);
|
|
14
|
-
const [answers, setAnswers] = useState<QuestionAnswers>(
|
|
15
|
-
() =>
|
|
16
|
-
Object.fromEntries(
|
|
17
|
-
questions.map((q) => [q.id, { selected: [], text: "" }]),
|
|
18
|
-
),
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
const current = questions[activeTab];
|
|
22
|
-
const currentAnswer = answers[current.id] ?? { selected: [], text: "" };
|
|
23
|
-
|
|
24
|
-
const toggleOption = (option: string) => {
|
|
25
|
-
const selected = currentAnswer.selected.includes(option)
|
|
26
|
-
? currentAnswer.selected.filter((o) => o !== option)
|
|
27
|
-
: [...currentAnswer.selected, option];
|
|
28
|
-
setAnswers((prev) => ({
|
|
29
|
-
...prev,
|
|
30
|
-
[current.id]: { ...prev[current.id], selected },
|
|
31
|
-
}));
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const setText = (text: string) => {
|
|
35
|
-
setAnswers((prev) => ({
|
|
36
|
-
...prev,
|
|
37
|
-
[current.id]: { ...prev[current.id], text },
|
|
38
|
-
}));
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const hasCustomText = currentAnswer.text.trim().length > 0;
|
|
42
|
-
|
|
43
|
-
const totalAnswered = questions.filter(
|
|
44
|
-
(q) =>
|
|
45
|
-
(answers[q.id]?.selected.length ?? 0) > 0 ||
|
|
46
|
-
(answers[q.id]?.text ?? "").trim().length > 0,
|
|
47
|
-
).length;
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<Box className="p-0 overflow-hidden">
|
|
51
|
-
{/* Tabs */}
|
|
52
|
-
<div className="flex border-b border-border overflow-x-auto">
|
|
53
|
-
{questions.map((q, i) => {
|
|
54
|
-
const hasAnswer =
|
|
55
|
-
(answers[q.id]?.selected.length ?? 0) > 0 ||
|
|
56
|
-
(answers[q.id]?.text ?? "").trim().length > 0;
|
|
57
|
-
return (
|
|
58
|
-
<button
|
|
59
|
-
key={q.id}
|
|
60
|
-
type="button"
|
|
61
|
-
onClick={() => setActiveTab(i)}
|
|
62
|
-
className={`flex items-center gap-1.5 px-3 py-2 text-xs font-medium whitespace-nowrap transition-colors border-b-2 -mb-px ${
|
|
63
|
-
i === activeTab
|
|
64
|
-
? "border-primary text-primary"
|
|
65
|
-
: "border-transparent text-muted-foreground hover:text-foreground"
|
|
66
|
-
}`}
|
|
67
|
-
>
|
|
68
|
-
{hasAnswer && (
|
|
69
|
-
<span className="h-1.5 w-1.5 rounded-full bg-primary shrink-0" />
|
|
70
|
-
)}
|
|
71
|
-
{q.label}
|
|
72
|
-
</button>
|
|
73
|
-
);
|
|
74
|
-
})}
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
{/* Content */}
|
|
78
|
-
<div className="p-3 space-y-1.5">
|
|
79
|
-
<p className="text-xs text-muted-foreground mb-2">
|
|
80
|
-
{current.description}
|
|
81
|
-
</p>
|
|
82
|
-
|
|
83
|
-
{/* Options */}
|
|
84
|
-
{current.options.map((option) => {
|
|
85
|
-
const isSelected = currentAnswer.selected.includes(option);
|
|
86
|
-
return (
|
|
87
|
-
<button
|
|
88
|
-
key={option}
|
|
89
|
-
type="button"
|
|
90
|
-
onClick={() => toggleOption(option)}
|
|
91
|
-
className={`flex items-center gap-2.5 w-full rounded-lg px-3 py-2.5 text-left transition-colors ${
|
|
92
|
-
isSelected
|
|
93
|
-
? "bg-primary/10 border border-primary/20"
|
|
94
|
-
: "bg-muted/50 border border-transparent hover:bg-muted"
|
|
95
|
-
}`}
|
|
96
|
-
>
|
|
97
|
-
<div
|
|
98
|
-
className={`flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors ${
|
|
99
|
-
isSelected
|
|
100
|
-
? "border-primary bg-primary"
|
|
101
|
-
: "border-input bg-transparent"
|
|
102
|
-
}`}
|
|
103
|
-
>
|
|
104
|
-
{isSelected && (
|
|
105
|
-
<Check className="h-3 w-3 text-primary-foreground" />
|
|
106
|
-
)}
|
|
107
|
-
</div>
|
|
108
|
-
<span className="text-sm">{option}</span>
|
|
109
|
-
</button>
|
|
110
|
-
);
|
|
111
|
-
})}
|
|
112
|
-
|
|
113
|
-
{/* Custom option */}
|
|
114
|
-
<div
|
|
115
|
-
className={`flex items-start gap-2.5 w-full rounded-lg px-3 py-2.5 transition-colors ${
|
|
116
|
-
hasCustomText
|
|
117
|
-
? "bg-primary/10 border border-primary/20"
|
|
118
|
-
: "bg-muted/50 border border-transparent"
|
|
119
|
-
}`}
|
|
120
|
-
>
|
|
121
|
-
<div
|
|
122
|
-
className={`flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors mt-0.5 ${
|
|
123
|
-
hasCustomText
|
|
124
|
-
? "border-primary bg-primary"
|
|
125
|
-
: "border-input bg-transparent"
|
|
126
|
-
}`}
|
|
127
|
-
>
|
|
128
|
-
{hasCustomText && (
|
|
129
|
-
<Check className="h-3 w-3 text-primary-foreground" />
|
|
130
|
-
)}
|
|
131
|
-
</div>
|
|
132
|
-
<div className="flex-1">
|
|
133
|
-
<TextareaField
|
|
134
|
-
value={currentAnswer.text}
|
|
135
|
-
onChange={(val) => setText(val ?? "")}
|
|
136
|
-
placeholder="Własna odpowiedź..."
|
|
137
|
-
rows={1}
|
|
138
|
-
/>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
{/* Footer */}
|
|
144
|
-
<div className="flex items-center justify-between border-t border-border px-3 py-2">
|
|
145
|
-
<span className="text-[10px] text-muted-foreground">
|
|
146
|
-
{totalAnswered}/{questions.length} <Trans>uzupełnione</Trans>
|
|
147
|
-
</span>
|
|
148
|
-
<Button
|
|
149
|
-
size="sm"
|
|
150
|
-
icon={Send}
|
|
151
|
-
label={<Trans>Wyślij</Trans>}
|
|
152
|
-
onClick={() => onSubmit(answers)}
|
|
153
|
-
/>
|
|
154
|
-
</div>
|
|
155
|
-
</Box>
|
|
156
|
-
);
|
|
157
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { CheckCircle2, ArrowRight } from "lucide-react";
|
|
2
|
-
import type { ToolUse } from "./types";
|
|
3
|
-
|
|
4
|
-
interface ToolUseBlockProps {
|
|
5
|
-
toolUse: ToolUse;
|
|
6
|
-
onClick?: () => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function ToolUseBlock({ toolUse, onClick }: ToolUseBlockProps) {
|
|
10
|
-
const Component = onClick ? "button" : "div";
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<Component
|
|
14
|
-
type={onClick ? "button" : undefined}
|
|
15
|
-
onClick={onClick}
|
|
16
|
-
className={`flex items-start gap-3 w-full rounded-xl border border-green-500/20 bg-green-500/5 p-3 text-left transition-colors ${
|
|
17
|
-
onClick ? "hover:bg-green-500/10 cursor-pointer" : ""
|
|
18
|
-
}`}
|
|
19
|
-
>
|
|
20
|
-
<CheckCircle2 className="h-4 w-4 text-green-500 mt-0.5 shrink-0" />
|
|
21
|
-
<div className="flex-1 min-w-0">
|
|
22
|
-
<p className="text-xs font-medium text-green-700 dark:text-green-400">
|
|
23
|
-
{toolUse.action}
|
|
24
|
-
</p>
|
|
25
|
-
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-1">
|
|
26
|
-
{toolUse.value}
|
|
27
|
-
</p>
|
|
28
|
-
</div>
|
|
29
|
-
{onClick && (
|
|
30
|
-
<ArrowRight className="h-3.5 w-3.5 text-muted-foreground mt-0.5 shrink-0" />
|
|
31
|
-
)}
|
|
32
|
-
</Component>
|
|
33
|
-
);
|
|
34
|
-
}
|
package/src/react/types.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export interface ToolUse {
|
|
2
|
-
action: string;
|
|
3
|
-
value: string;
|
|
4
|
-
link?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface Question {
|
|
8
|
-
id: string;
|
|
9
|
-
label: string;
|
|
10
|
-
description: string;
|
|
11
|
-
options: string[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface QuestionAnswers {
|
|
15
|
-
[questionId: string]: { selected: string[]; text: string };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface ChatMessageData {
|
|
19
|
-
id: string;
|
|
20
|
-
role: "user" | "assistant" | "system" | "tool";
|
|
21
|
-
content: string;
|
|
22
|
-
toolUses?: ToolUse[];
|
|
23
|
-
questions?: Question[];
|
|
24
|
-
isStreaming?: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ChatModel {
|
|
28
|
-
value: string;
|
|
29
|
-
label: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface SendMessageOptions {
|
|
33
|
-
model: string;
|
|
34
|
-
webSearch: boolean;
|
|
35
|
-
attachments?: string[];
|
|
36
|
-
}
|