vibefast-cli 0.7.12 → 0.7.14
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/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +28 -2
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +5 -3
- package/dist/commands/init.js.map +1 -1
- package/package.json +1 -1
- package/recipes/audio-recorder/recipe.json +1 -1
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/{charts → (root)/(protected)/charts}/index.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/app/preview.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/area-chart.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/index.ts +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/stacked-area-chart.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +0 -3
- package/recipes/charts/apps/native/src/features/charts/data/mock-data.ts +0 -3
- package/recipes/charts/apps/native/src/features/charts/types/index.ts +0 -3
- package/recipes/charts/recipe.json +1 -1
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/api-client/chatbot.ts +83 -0
- package/recipes/chatbot/apps/native/src/app/{chatbot → (root)/(protected)/chatbot}/index.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/app/index.tsx +56 -60
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-input-bar.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +3 -26
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/image-preview-list.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/index.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/message-error-boundary.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/message-list.tsx +10 -14
- package/recipes/chatbot/apps/native/src/features/chatbot/components/model-selector.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/report-content-modal.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/components/suggested-messages.tsx +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/constants/models.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/constants/report-reasons.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-config.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-conversation.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-image-picker.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/models/index.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/models/models.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/models/providers.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/models/types.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/services/file-uploader.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/services/message-handler-service.ts +0 -1
- package/recipes/chatbot/apps/native/src/features/chatbot/types/index.ts +5 -3
- package/recipes/chatbot/apps/native/src/features/chatbot/utils/chat-telemetry.ts +0 -1
- package/recipes/chatbot/packages/backend/convex/agents.ts +3 -4
- package/recipes/chatbot/packages/backend/convex/chatbot/content.ts +35 -0
- package/recipes/chatbot/packages/backend/convex/chatbot/sessions.ts +52 -0
- package/recipes/chatbot/packages/backend/convex/chatbot/streaming.ts +422 -0
- package/recipes/chatbot/packages/backend/convex/chatbot/telemetry.ts +56 -0
- package/recipes/chatbot/packages/backend/convex/chatbot/tools.ts +128 -0
- package/recipes/chatbot/packages/backend/convex/chatbotAgent.ts +6 -651
- package/recipes/chatbot/packages/backend/convex/ragKnowledge.ts +0 -714
- package/recipes/chatbot/packages/backend/convex/tools/knowledgeRetrieval.ts +12 -7
- package/recipes/chatbot/recipe.json +6 -1
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/image-generator/apps/native/src/api-client/image-generator.ts +34 -0
- package/recipes/image-generator/packages/backend/convex/{imageGeneratorFunctions.ts → imageGenerator.ts} +1 -1
- package/recipes/image-generator/recipe.json +5 -1
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/payments/apps/native/src/api-client/payments.ts +44 -0
- package/recipes/payments/packages/backend/convex/payments/index.ts +13 -0
- package/recipes/payments/packages/backend/convex/payments.ts +119 -0
- package/recipes/payments/recipe.json +15 -2
- package/recipes/payments@latest.zip +0 -0
- package/recipes/quiz/recipe.json +1 -1
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/tracker-app/recipe.json +1 -1
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/voice-bot/recipe.json +1 -1
- package/recipes/voice-bot@latest.zip +0 -0
- package/src/commands/add.ts +108 -70
- package/src/commands/init.ts +5 -3
- package/tmp-npm-cache/_update-notifier-last-checked +0 -0
- /package/recipes/audio-recorder/apps/native/src/app/{audio-recorder → (root)/(protected)/audio-recorder}/index.tsx +0 -0
- /package/recipes/image-generator/apps/native/src/app/{image-generator → (root)/(protected)/image-generator}/gallery.tsx +0 -0
- /package/recipes/image-generator/apps/native/src/app/{image-generator → (root)/(protected)/image-generator}/index.tsx +0 -0
- /package/recipes/quiz/apps/native/src/app/{quiz → (root)/(protected)/quiz}/index.tsx +0 -0
- /package/recipes/tracker-app/apps/native/src/app/{tracker-app → (root)/(protected)/tracker-app}/index.tsx +0 -0
- /package/recipes/voice-bot/apps/native/src/app/{voice-bot → (root)/(protected)/voice-bot}/index.tsx +0 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
1
|
import { useAuthToken } from '@convex-dev/auth/react';
|
|
3
2
|
import { api } from '@vibefast/backend/_generated/api';
|
|
4
3
|
import type { Id } from '@vibefast/backend/_generated/dataModel';
|
|
5
4
|
import { useQuery } from 'convex/react';
|
|
6
5
|
import { Stack } from 'expo-router';
|
|
7
6
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
8
|
-
import { Keyboard,
|
|
7
|
+
import { Keyboard, View } from 'react-native';
|
|
9
8
|
import Animated from 'react-native-reanimated';
|
|
10
9
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
11
10
|
|
|
@@ -236,68 +235,65 @@ export default function ChatConversationScreen() {
|
|
|
236
235
|
|
|
237
236
|
return (
|
|
238
237
|
<NetworkConnectivityWrapper>
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
238
|
+
<View style={{ flex: 1 }}>
|
|
239
|
+
<Stack.Screen
|
|
240
|
+
options={{
|
|
241
|
+
title: translate('chatbot.title'),
|
|
242
|
+
headerRight: clearChatButton,
|
|
243
|
+
}}
|
|
244
|
+
/>
|
|
245
|
+
<FocusAwareStatusBar />
|
|
246
|
+
|
|
247
|
+
<View
|
|
248
|
+
style={{ flex: 1, paddingTop: insets.top }}
|
|
249
|
+
onTouchStart={() => Keyboard.dismiss()}
|
|
250
|
+
>
|
|
251
|
+
<MessageList
|
|
252
|
+
messages={messagesWithUrls}
|
|
253
|
+
onReportMessage={handleReportMessage}
|
|
254
|
+
testID="chat-conversation-screen"
|
|
250
255
|
/>
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
<View style={{ flex: 1, paddingTop: insets.top + 12 }}>
|
|
254
|
-
<MessageList
|
|
255
|
-
messages={messagesWithUrls}
|
|
256
|
-
onReportMessage={handleReportMessage}
|
|
257
|
-
testID="chat-conversation-screen"
|
|
258
|
-
/>
|
|
259
|
-
</View>
|
|
256
|
+
</View>
|
|
260
257
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
</View>
|
|
258
|
+
{statusLabel ? (
|
|
259
|
+
<View className="pb-6 pl-5 pr-4 pt-2">
|
|
260
|
+
<View className="flex-row gap-3">
|
|
261
|
+
<Spinner
|
|
262
|
+
size="sm"
|
|
263
|
+
variant="pulse"
|
|
264
|
+
color={theme.colors.foreground}
|
|
265
|
+
/>
|
|
266
|
+
<Text className="text-base font-medium text-neutral-400 dark:text-neutral-500">
|
|
267
|
+
{statusLabel}
|
|
268
|
+
</Text>
|
|
273
269
|
</View>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
270
|
+
</View>
|
|
271
|
+
) : null}
|
|
272
|
+
|
|
273
|
+
<ChatInputBar
|
|
274
|
+
input={input}
|
|
275
|
+
onInputChange={handleTextInputChange}
|
|
276
|
+
onSubmit={handleSubmitWithKeyboardReset}
|
|
277
|
+
isLoading={conversationLoading || isChatLoading}
|
|
278
|
+
showSuggestedMessages={messages.length === 0}
|
|
279
|
+
selectedModel={selectedModel}
|
|
280
|
+
onModelChange={handleModelChange}
|
|
281
|
+
/>
|
|
282
|
+
|
|
283
|
+
{/* Animated spacer for keyboard */}
|
|
284
|
+
<Animated.View
|
|
285
|
+
pointerEvents="none"
|
|
286
|
+
style={keyboardCoordinator.keyboardPadding}
|
|
287
|
+
/>
|
|
288
|
+
|
|
289
|
+
{messageToReport && (
|
|
290
|
+
<ReportContentModal
|
|
291
|
+
message={messageToReport}
|
|
292
|
+
onSubmit={handleSubmitReport}
|
|
293
|
+
onCancel={handleCancelReport}
|
|
290
294
|
/>
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
<ReportContentModal
|
|
294
|
-
message={messageToReport}
|
|
295
|
-
onSubmit={handleSubmitReport}
|
|
296
|
-
onCancel={handleCancelReport}
|
|
297
|
-
/>
|
|
298
|
-
)}
|
|
299
|
-
</View>
|
|
300
|
-
</TouchableWithoutFeedback>
|
|
295
|
+
)}
|
|
296
|
+
</View>
|
|
301
297
|
</NetworkConnectivityWrapper>
|
|
302
298
|
);
|
|
303
299
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
1
|
import { Feather, Ionicons } from '@expo/vector-icons';
|
|
3
2
|
import type { Id } from '@vibefast/backend/_generated/dataModel';
|
|
4
|
-
import type { Message } from 'ai'; // Base Message type
|
|
5
3
|
import * as Clipboard from 'expo-clipboard';
|
|
6
4
|
import React from 'react';
|
|
7
5
|
import { View } from 'react-native';
|
|
@@ -9,45 +7,24 @@ import { View } from 'react-native';
|
|
|
9
7
|
import { Image, Pressable, Text } from '@/components/ui';
|
|
10
8
|
import { useToast } from '@/components/ui/utils';
|
|
11
9
|
import { ChatMarkdown } from '@/features/chatbot/components/chat-markdown';
|
|
10
|
+
import type { AppMessage } from '@/features/chatbot/types';
|
|
12
11
|
import { translate } from '@/lib';
|
|
13
12
|
import { useThemeConfig } from '@/lib/use-theme-config';
|
|
14
13
|
|
|
15
|
-
// Local definition for MessageContentPart compatible with Vercel AI SDK structure
|
|
16
|
-
// This should be consistent with how `useChat` hook and AI SDK handle content.
|
|
17
|
-
export type LocalMessageContentPart =
|
|
18
|
-
| { type: 'text'; text: string }
|
|
19
|
-
| { type: 'image'; image: string | URL }; // Allow string for input from client, server can convert to URL
|
|
20
|
-
|
|
21
|
-
type ConvexAttachment = {
|
|
22
|
-
type: 'image';
|
|
23
|
-
storageId: Id<'_storage'>;
|
|
24
|
-
url?: string; // This is now pre-fetched from the parent component
|
|
25
|
-
fileName?: string;
|
|
26
|
-
mimeType?: string;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// The message prop for ChatMessageBubble
|
|
30
|
-
export type AppMessage = Omit<Message, 'content'> & {
|
|
31
|
-
content: string | LocalMessageContentPart[]; // Content matches AI SDK
|
|
32
|
-
attachments?: ConvexAttachment[]; // Custom prop for Convex-stored attachments
|
|
33
|
-
};
|
|
34
|
-
|
|
35
14
|
type ChatMessageBubbleProps = {
|
|
36
15
|
message: AppMessage;
|
|
37
|
-
onReportMessage?: (message: AppMessage) => void;
|
|
16
|
+
onReportMessage?: (message: AppMessage) => void;
|
|
38
17
|
};
|
|
39
18
|
|
|
40
19
|
const ConvexImage: React.FC<{
|
|
41
20
|
storageId: Id<'_storage'>;
|
|
42
21
|
isUser: boolean;
|
|
43
22
|
fileName?: string;
|
|
44
|
-
preloadedUrl?: string | null;
|
|
23
|
+
preloadedUrl?: string | null;
|
|
45
24
|
}> = React.memo(({ storageId, isUser, fileName, preloadedUrl }) => {
|
|
46
|
-
// Use pre-loaded URL if available, otherwise show loading
|
|
47
25
|
const imageUrl = preloadedUrl;
|
|
48
26
|
const theme = useThemeConfig();
|
|
49
27
|
|
|
50
|
-
// Fixed aspect ratio container to prevent layout shift when image loads
|
|
51
28
|
return (
|
|
52
29
|
<View
|
|
53
30
|
className="my-1 w-48 overflow-hidden rounded-lg"
|
|
@@ -1,23 +1,16 @@
|
|
|
1
|
-
|
|
2
1
|
import type { FlashListRef } from '@shopify/flash-list';
|
|
3
2
|
import { FlashList } from '@shopify/flash-list';
|
|
4
|
-
import type { Message } from 'ai';
|
|
5
3
|
import React, { useCallback, useEffect, useRef } from 'react';
|
|
6
4
|
import { Keyboard, View } from 'react-native';
|
|
7
5
|
|
|
8
|
-
import { Text } from '@/components/ui';
|
|
9
|
-
import type { AppMessage } from '@/features/chatbot/components/chat-message-bubble';
|
|
10
6
|
import { ChatMessageBubble } from '@/features/chatbot/components/chat-message-bubble';
|
|
11
7
|
import { MessageErrorBoundary } from '@/features/chatbot/components/message-error-boundary';
|
|
12
8
|
import { useSmartScrollManager } from '@/features/chatbot/hooks/use-smart-scroll-manager';
|
|
13
|
-
import {
|
|
9
|
+
import type { AppMessage } from '@/features/chatbot/types';
|
|
14
10
|
|
|
15
11
|
type MessageListProps = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/** Handler for reporting a message */
|
|
19
|
-
onReportMessage?: (message: Message) => void; // Base Message type for handler is fine
|
|
20
|
-
/** Test ID for E2E testing */
|
|
12
|
+
messages: AppMessage[];
|
|
13
|
+
onReportMessage?: (message: AppMessage) => void;
|
|
21
14
|
testID?: string;
|
|
22
15
|
};
|
|
23
16
|
|
|
@@ -140,10 +133,13 @@ export const MessageList: React.FC<MessageListProps> = ({
|
|
|
140
133
|
return (
|
|
141
134
|
<View className="flex-1 px-4" testID={testID}>
|
|
142
135
|
{messages.length === 0 ? (
|
|
143
|
-
<View
|
|
144
|
-
|
|
136
|
+
<View
|
|
137
|
+
className="flex-1 items-center justify-center px-6"
|
|
138
|
+
onTouchStart={handleTouchStart}
|
|
139
|
+
>
|
|
140
|
+
{/* <Text className="text-center text-base text-muted-foreground">
|
|
145
141
|
{translate('chatbot.start_conversation')}
|
|
146
|
-
</Text>
|
|
142
|
+
</Text> */}
|
|
147
143
|
</View>
|
|
148
144
|
) : (
|
|
149
145
|
<FlashList<AppMessage>
|
|
@@ -163,7 +159,7 @@ export const MessageList: React.FC<MessageListProps> = ({
|
|
|
163
159
|
scrollEventThrottle={16}
|
|
164
160
|
keyboardShouldPersistTaps="handled"
|
|
165
161
|
keyboardDismissMode="on-drag"
|
|
166
|
-
contentContainerStyle={{
|
|
162
|
+
contentContainerStyle={{ paddingTop: 50, paddingBottom: 8 }}
|
|
167
163
|
accessibilityLabel="Chat messages"
|
|
168
164
|
accessibilityRole="list"
|
|
169
165
|
testID="messages-flashlist"
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
1
|
import type { Id } from '@vibefast/backend/_generated/dataModel';
|
|
3
|
-
import type { Message } from 'ai';
|
|
4
2
|
|
|
5
3
|
export type LocalMessageContentPart =
|
|
6
4
|
| { type: 'text'; text: string }
|
|
@@ -43,8 +41,12 @@ export type AgentStatus =
|
|
|
43
41
|
startedAt: number;
|
|
44
42
|
};
|
|
45
43
|
|
|
46
|
-
export type AppMessage =
|
|
44
|
+
export type AppMessage = {
|
|
45
|
+
id: string;
|
|
46
|
+
role: 'user' | 'assistant' | 'data';
|
|
47
47
|
content: string | LocalMessageContentPart[];
|
|
48
|
+
createdAt?: Date;
|
|
49
|
+
parts?: unknown; // Optional hook into AI SDK shapes when present
|
|
48
50
|
attachments?: ConvexMessageAttachment[];
|
|
49
51
|
toolCalls?: ToolCallRecord[];
|
|
50
52
|
metadata?: MessageMetadata;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
2
2
|
import { openai } from '@ai-sdk/openai';
|
|
3
|
-
import type { LanguageModelV1 } from '@ai-sdk/provider';
|
|
4
3
|
import { Agent } from '@convex-dev/agent';
|
|
5
4
|
|
|
6
5
|
import { components } from './_generated/api';
|
|
@@ -15,14 +14,14 @@ const google = createGoogleGenerativeAI();
|
|
|
15
14
|
type ModelProvider = 'openai' | 'google';
|
|
16
15
|
|
|
17
16
|
type ModelResolver = () => {
|
|
18
|
-
model:
|
|
17
|
+
model: any;
|
|
19
18
|
provider: ModelProvider;
|
|
20
19
|
modelId: string;
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
const PROVIDER_CHAT_FACTORIES: Record<
|
|
24
23
|
ModelProvider,
|
|
25
|
-
(modelId: string) =>
|
|
24
|
+
(modelId: string) => unknown
|
|
26
25
|
> = {
|
|
27
26
|
openai: (modelId) => openai.chat(modelId),
|
|
28
27
|
google: (modelId) => google.chat(modelId),
|
|
@@ -86,7 +85,7 @@ export function resolveChatModel(preferredModel?: string) {
|
|
|
86
85
|
|
|
87
86
|
export const agent: Agent = new Agent(components.agent, {
|
|
88
87
|
name: 'VibeFast Assistant',
|
|
89
|
-
|
|
88
|
+
languageModel: MODEL_RESOLVERS[DEFAULT_MODEL]().model as any,
|
|
90
89
|
instructions: `
|
|
91
90
|
You are VibeFast, a proactive mobile AI assistant. Produce concise, friendly responses with clear structure.
|
|
92
91
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FilePart, ImagePart, TextPart } from 'ai';
|
|
2
|
+
|
|
3
|
+
import type { Doc } from '../_generated/dataModel';
|
|
4
|
+
import type { MutationCtx } from '../_generated/server';
|
|
5
|
+
|
|
6
|
+
export async function buildUserContent(
|
|
7
|
+
ctx: Pick<MutationCtx, 'storage'>,
|
|
8
|
+
message: Doc<'messages'>,
|
|
9
|
+
): Promise<string | (TextPart | ImagePart | FilePart)[]> {
|
|
10
|
+
const attachments = message.attachments ?? [];
|
|
11
|
+
if (attachments.length === 0) {
|
|
12
|
+
return message.text ?? '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const parts: (TextPart | ImagePart)[] = [];
|
|
16
|
+
if (message.text) {
|
|
17
|
+
parts.push({ type: 'text', text: message.text });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const attachment of attachments) {
|
|
21
|
+
try {
|
|
22
|
+
const url = await ctx.storage.getUrl(attachment.storageId);
|
|
23
|
+
if (url) {
|
|
24
|
+
parts.push({ type: 'image', image: new URL(url) });
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('[chatbotAgent] Failed to resolve attachment URL', {
|
|
28
|
+
storageId: attachment.storageId,
|
|
29
|
+
error,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return parts.length > 0 ? parts : (message.text ?? '');
|
|
35
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { MutationCtx, QueryCtx } from '../_generated/server';
|
|
2
|
+
import type { Doc, Id } from '../_generated/dataModel';
|
|
3
|
+
import { agent, DEFAULT_MODEL } from '../agents';
|
|
4
|
+
|
|
5
|
+
type AgentSessionDoc = Doc<'agentSessions'>;
|
|
6
|
+
|
|
7
|
+
export async function getSessionForConversation(
|
|
8
|
+
ctx: Pick<MutationCtx, 'db'> | Pick<QueryCtx, 'db'>,
|
|
9
|
+
conversationId: Id<'conversations'>,
|
|
10
|
+
) {
|
|
11
|
+
return await ctx.db
|
|
12
|
+
.query('agentSessions')
|
|
13
|
+
.withIndex('by_conversationId', (q) =>
|
|
14
|
+
q.eq('conversationId', conversationId),
|
|
15
|
+
)
|
|
16
|
+
.first();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function ensureAgentSession(
|
|
20
|
+
ctx: MutationCtx,
|
|
21
|
+
conversationId: Id<'conversations'>,
|
|
22
|
+
userId: Id<'users'>,
|
|
23
|
+
preferredModel?: string,
|
|
24
|
+
): Promise<{ session: AgentSessionDoc; wasCreated: boolean }> {
|
|
25
|
+
const existing = await getSessionForConversation(ctx, conversationId);
|
|
26
|
+
if (existing) {
|
|
27
|
+
return { session: existing, wasCreated: false };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { threadId } = await agent.createThread(ctx, {
|
|
31
|
+
userId,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const sessionId = await ctx.db.insert('agentSessions', {
|
|
35
|
+
conversationId,
|
|
36
|
+
userId,
|
|
37
|
+
agentThreadId: threadId,
|
|
38
|
+
activeStreamId: undefined,
|
|
39
|
+
lastUserMessageId: undefined,
|
|
40
|
+
lastAssistantMessageId: undefined,
|
|
41
|
+
preferredModel: preferredModel ?? DEFAULT_MODEL,
|
|
42
|
+
updatedAt: Date.now(),
|
|
43
|
+
lastErrorMessage: undefined,
|
|
44
|
+
lastErrorAt: undefined,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const session = await ctx.db.get(sessionId);
|
|
48
|
+
if (!session) {
|
|
49
|
+
throw new Error('Failed to create agent session');
|
|
50
|
+
}
|
|
51
|
+
return { session, wasCreated: true };
|
|
52
|
+
}
|