Skip to content

Commit cdeddaa

Browse files
authored
Merge pull request #1196 from trycompai/mariano/qa-endpoint
[dev] [Marfuen] mariano/qa-endpoint
2 parents 9c812e9 + 15d7d66 commit cdeddaa

16 files changed

Lines changed: 230 additions & 93 deletions

File tree

apps/app/package.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
"name": "@comp/app",
33
"version": "0.1.0",
44
"dependencies": {
5-
"@ai-sdk/groq": "^1.2.8",
6-
"@ai-sdk/openai": "^1.3.19",
7-
"@ai-sdk/provider": "^1.1.3",
8-
"@ai-sdk/react": "^1.2.9",
5+
"@ai-sdk/anthropic": "^2.0.0",
6+
"@ai-sdk/groq": "^2.0.0",
7+
"@ai-sdk/openai": "^2.0.0",
8+
"@ai-sdk/provider": "^2.0.0",
9+
"@ai-sdk/react": "^2.0.0",
10+
"@ai-sdk/rsc": "^1.0.0",
911
"@aws-sdk/client-s3": "^3.806.0",
1012
"@aws-sdk/client-sts": "^3.808.0",
1113
"@aws-sdk/s3-request-presigner": "^3.832.0",
@@ -50,7 +52,7 @@
5052
"@uploadthing/react": "^7.3.0",
5153
"@upstash/ratelimit": "^2.0.5",
5254
"@vercel/sdk": "^1.7.1",
53-
"ai": "^4.3.16",
55+
"ai": "^5.0.0",
5456
"axios": "^1.9.0",
5557
"better-auth": "^1.2.8",
5658
"canvas-confetti": "^1.9.3",

apps/app/src/app/api/chat/route.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { tools } from '@/data/tools';
2-
import { model, type modelID } from '@/hooks/ai/providers';
32
import { auth } from '@/utils/auth';
4-
import { type UIMessage, streamText } from 'ai';
3+
import { groq } from '@ai-sdk/groq';
4+
import { type UIMessage, convertToModelMessages, streamText } from 'ai';
55
import { headers } from 'next/headers';
66

77
export const maxDuration = 30;
88

99
export async function POST(req: Request) {
10-
const { messages, selectedModel }: { messages: UIMessage[]; selectedModel: modelID } =
11-
await req.json();
10+
const { messages }: { messages: UIMessage[] } = await req.json();
1211

1312
const session = await auth.api.getSession({
1413
headers: await headers(),
@@ -31,11 +30,11 @@ export async function POST(req: Request) {
3130
`;
3231

3332
const result = streamText({
34-
model: model.languageModel(selectedModel),
33+
model: groq('deepseek-r1-distill-llama-70b'),
3534
system: systemPrompt,
36-
messages,
35+
messages: convertToModelMessages(messages),
3736
tools,
3837
});
3938

40-
return result.toDataStreamResponse({ sendReasoning: true });
39+
return result.toUIMessageStreamResponse();
4140
}

apps/app/src/components/ai/chat-text-area.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { modelID } from '@/hooks/ai/providers';
21
import { Icons } from '@comp/ui/icons';
32
import { Popover, PopoverContent, PopoverTrigger } from '@comp/ui/popover';
43
import { Textarea as ShadcnTextarea } from '@comp/ui/textarea';
@@ -10,8 +9,6 @@ interface InputProps {
109
isLoading: boolean;
1110
status: string;
1211
stop: () => void;
13-
selectedModel: modelID;
14-
setSelectedModel: (model: modelID) => void;
1512
}
1613

1714
export const ChatTextarea = ({ input, handleInputChange, isLoading }: InputProps) => {

apps/app/src/components/ai/chat.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use client';
22

3-
import type { modelID } from '@/hooks/ai/providers';
43
import { useSession } from '@/utils/auth-client';
54
import { useChat } from '@ai-sdk/react';
65
import { ScrollArea } from '@comp/ui/scroll-area';
6+
import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai';
77
import { useState } from 'react';
88
import { ChatEmpty } from './chat-empty';
99
import { ChatTextarea } from './chat-text-area';
@@ -12,13 +12,15 @@ import { Messages } from './messages';
1212
export default function Chat() {
1313
const { data: session } = useSession();
1414

15-
const [selectedModel, setSelectedModel] = useState<modelID>('deepseek-r1-distill-llama-70b');
15+
const [input, setInput] = useState('');
1616

17-
const { messages, input, handleInputChange, handleSubmit, error, status, stop } = useChat({
18-
maxSteps: 5,
19-
body: {
20-
selectedModel,
21-
},
17+
const { messages, sendMessage, addToolResult, error, status, stop } = useChat({
18+
transport: new DefaultChatTransport({
19+
api: '/api/chat',
20+
}),
21+
22+
// Automatically submit when all server-side tool calls are complete
23+
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
2224
});
2325

2426
const isLoading = status === 'streaming' || status === 'submitted';
@@ -37,11 +39,17 @@ export default function Chat() {
3739
)}
3840
</ScrollArea>
3941

40-
<form onSubmit={handleSubmit}>
42+
<form
43+
onSubmit={(e) => {
44+
e.preventDefault();
45+
if (input.trim()) {
46+
sendMessage({ text: input });
47+
setInput('');
48+
}
49+
}}
50+
>
4151
<ChatTextarea
42-
selectedModel={selectedModel}
43-
setSelectedModel={setSelectedModel}
44-
handleInputChange={handleInputChange}
52+
handleInputChange={(e) => setInput(e.target.value)}
4553
input={input}
4654
isLoading={isLoading}
4755
status={status}

apps/app/src/components/ai/message.tsx

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
'use client';
22

33
import { useStreamableText } from '@/hooks/use-streamable-text';
4+
import { type StreamableValue } from '@ai-sdk/rsc';
45
import { cn } from '@comp/ui/cn';
5-
import type { Message as TMessage } from 'ai';
6-
import type { StreamableValue } from 'ai/rsc';
6+
import type { UIMessage } from 'ai';
7+
78
import equal from 'fast-deep-equal';
89
import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
910
import { AnimatePresence, motion } from 'motion/react';
@@ -26,8 +27,8 @@ interface ExtendedToolInvocation extends ToolInvocation {
2627

2728
interface ReasoningPart {
2829
type: 'reasoning';
29-
reasoning: string;
30-
details: Array<{ type: 'text'; text: string }>;
30+
text: string;
31+
details?: Array<{ type: 'text'; text: string }>;
3132
}
3233

3334
interface ReasoningMessagePartProps {
@@ -117,13 +118,13 @@ export function ReasoningMessagePart({ part, isReasoning }: ReasoningMessagePart
117118
variants={variants}
118119
transition={{ duration: 0.2, ease: 'easeInOut' }}
119120
>
120-
{part.details.map((detail) =>
121+
{part.details?.map((detail) =>
121122
detail.type === 'text' ? (
122123
<StreamableMarkdown key={detail.text} text={detail.text} />
123124
) : (
124125
'<redacted>'
125126
),
126-
)}
127+
) || <StreamableMarkdown text={part.text} />}
127128
</motion.div>
128129
)}
129130
</AnimatePresence>
@@ -159,7 +160,7 @@ const PurePreviewMessage = ({
159160
isLatestMessage,
160161
status,
161162
}: {
162-
message: TMessage;
163+
message: UIMessage;
163164
isLoading: boolean;
164165
status: 'error' | 'submitted' | 'streaming' | 'ready';
165166
isLatestMessage: boolean;
@@ -180,7 +181,12 @@ const PurePreviewMessage = ({
180181
)}
181182
>
182183
<div className="flex w-full flex-col space-y-4">
183-
{message.parts?.map((part, i) => {
184+
{message.parts?.map((part: any, i: number) => {
185+
// Skip invalid parts
186+
if (!part || !part.type) {
187+
return null;
188+
}
189+
184190
switch (part.type) {
185191
case 'text':
186192
return message.role === 'user' ? (
@@ -196,7 +202,7 @@ const PurePreviewMessage = ({
196202
message.role === 'user',
197203
})}
198204
>
199-
<StreamableMarkdown text={part.text} />
205+
<StreamableMarkdown text={part.text || ''} />
200206
</div>
201207
</motion.div>
202208
) : (
@@ -207,7 +213,7 @@ const PurePreviewMessage = ({
207213
className="flex w-full flex-row items-start gap-2 pb-2"
208214
>
209215
<BotCard key={`message-${message.id}-part-${i}`}>
210-
<StreamableMarkdown text={part.text} />
216+
<StreamableMarkdown text={part.text || ''} />
211217
</BotCard>
212218
</motion.div>
213219
);
@@ -216,8 +222,7 @@ const PurePreviewMessage = ({
216222
return (
217223
<ReasoningMessagePart
218224
key={`message-${message.id}-${i}`}
219-
// @ts-expect-error part
220-
part={part}
225+
part={part as ReasoningPart}
221226
isReasoning={
222227
(message.parts &&
223228
status === 'streaming' &&
@@ -227,7 +232,9 @@ const PurePreviewMessage = ({
227232
/>
228233
);
229234
}
235+
230236
default:
237+
// Skip tool parts - only show final text response
231238
return null;
232239
}
233240
})}
@@ -303,8 +310,6 @@ export function UserMessage({ content }: { content: string }) {
303310
export const Message = memo(PurePreviewMessage, (prevProps, nextProps) => {
304311
if (prevProps.status !== nextProps.status) return false;
305312

306-
if (prevProps.message.annotations !== nextProps.message.annotations) return false;
307-
308313
if (!equal(prevProps.message.parts, nextProps.message.parts)) return false;
309314

310315
return true;

apps/app/src/components/ai/messages.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { useScrollToBottom } from '@/hooks/use-scroll-to-bottom';
2-
import type { Message as TMessage } from 'ai';
2+
import type { UIMessage } from 'ai';
33
import { Message } from './message';
44

55
export const Messages = ({
66
messages,
77
isLoading,
88
status,
99
}: {
10-
messages: TMessage[];
10+
messages: UIMessage[];
1111
isLoading: boolean;
1212
status: 'error' | 'submitted' | 'streaming' | 'ready';
1313
}) => {

apps/app/src/data/tools/organization.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { auth } from '@/utils/auth';
22
import { db } from '@db';
3-
import { tool } from 'ai';
43
import { headers } from 'next/headers';
54
import { z } from 'zod';
65

@@ -10,9 +9,9 @@ export function getOrganizationTools() {
109
};
1110
}
1211

13-
export const findOrganization = tool({
12+
export const findOrganization = {
1413
description: "Find the users organization and it's details",
15-
parameters: z.object({}),
14+
inputSchema: z.object({}),
1615
execute: async () => {
1716
const session = await auth.api.getSession({
1817
headers: await headers(),
@@ -40,4 +39,4 @@ export const findOrganization = tool({
4039
organization: org,
4140
};
4241
},
43-
});
42+
};

apps/app/src/data/tools/policies.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { auth } from '@/utils/auth';
22
import { db } from '@db';
3-
import { tool } from 'ai';
43
import { headers } from 'next/headers';
54
import { z } from 'zod';
65

@@ -11,12 +10,12 @@ export function getPolicyTools() {
1110
};
1211
}
1312

14-
export const getPolicies = tool({
13+
export const getPolicies = {
1514
description: 'Get all policies for the organization',
16-
parameters: z.object({
15+
inputSchema: z.object({
1716
status: z.enum(['draft', 'published']).optional(),
1817
}),
19-
execute: async ({ status }) => {
18+
execute: async ({ status }: { status?: 'draft' | 'published' }) => {
2019
const session = await auth.api.getSession({
2120
headers: await headers(),
2221
});
@@ -49,15 +48,15 @@ export const getPolicies = tool({
4948
policies,
5049
};
5150
},
52-
});
51+
};
5352

54-
export const getPolicyContent = tool({
53+
export const getPolicyContent = {
5554
description:
5655
'Get the content of a specific policy by id. We can only acquire the policy id by running the getPolicies tool first.',
57-
parameters: z.object({
56+
inputSchema: z.object({
5857
id: z.string(),
5958
}),
60-
execute: async ({ id }) => {
59+
execute: async ({ id }: { id: string }) => {
6160
const session = await auth.api.getSession({
6261
headers: await headers(),
6362
});
@@ -84,4 +83,4 @@ export const getPolicyContent = tool({
8483
content: policy?.content,
8584
};
8685
},
87-
});
86+
};

apps/app/src/data/tools/risks-tool.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { auth } from '@/utils/auth';
22
import { db, Departments, RiskCategory, RiskStatus } from '@db';
3-
import { tool } from 'ai';
43
import { headers } from 'next/headers';
54
import { z } from 'zod';
65

@@ -11,15 +10,25 @@ export function getRiskTools() {
1110
};
1211
}
1312

14-
export const getRisks = tool({
13+
export const getRisks = {
1514
description: 'Get risks for the organization',
16-
parameters: z.object({
15+
inputSchema: z.object({
1716
status: z.enum(Object.values(RiskStatus) as [RiskStatus, ...RiskStatus[]]).optional(),
1817
department: z.enum(Object.values(Departments) as [Departments, ...Departments[]]).optional(),
1918
category: z.enum(Object.values(RiskCategory) as [RiskCategory, ...RiskCategory[]]).optional(),
2019
owner: z.string().optional(),
2120
}),
22-
execute: async ({ status, department, category, owner }) => {
21+
execute: async ({
22+
status,
23+
department,
24+
category,
25+
owner,
26+
}: {
27+
status?: RiskStatus;
28+
department?: Departments;
29+
category?: RiskCategory;
30+
owner?: string;
31+
}) => {
2332
const session = await auth.api.getSession({
2433
headers: await headers(),
2534
});
@@ -54,14 +63,14 @@ export const getRisks = tool({
5463
risks,
5564
};
5665
},
57-
});
66+
};
5867

59-
export const getRiskById = tool({
68+
export const getRiskById = {
6069
description: 'Get a risk by id',
61-
parameters: z.object({
70+
inputSchema: z.object({
6271
id: z.string(),
6372
}),
64-
execute: async ({ id }) => {
73+
execute: async ({ id }: { id: string }) => {
6574
const session = await auth.api.getSession({
6675
headers: await headers(),
6776
});
@@ -85,4 +94,4 @@ export const getRiskById = tool({
8594
risk,
8695
};
8796
},
88-
});
97+
};

0 commit comments

Comments
 (0)