Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/server/api/src/app/ee/chat/chat-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
GetProviderConfigResponse,
isNil,
Project,
ProjectType,
SeekPage,
spreadIfDefined,
UpdateChatConversationRequest,
Expand Down Expand Up @@ -267,11 +268,14 @@ async function getUserProjects({ platformId, userId, log }: {
log: FastifyBaseLogger
}): Promise<Project[]> {
const user = await userService(log).getOneOrFail({ id: userId })
return projectService(log).getAllForUser({
const allProjects = await projectService(log).getAllForUser({
platformId,
userId,
isPrivileged: userService(log).isUserPrivileged(user),
})
return allProjects.filter(
(p) => p.type !== ProjectType.PERSONAL || p.ownerId === userId,
)
}

async function assertUserHasProjectAccess({ platformId, userId, projectId, log }: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ function CreateOrEditConnectionSection({
selectedAuth,
onTryAnotherMethodButtonClicked,
showTryAnotherMethodButton,
projectId: projectIdOverride,
}: CreateOrEditConnectionSectionProps) {
const formSchema = formUtils.buildConnectionSchema(
selectedAuth.authProperty,
Expand Down Expand Up @@ -98,6 +99,7 @@ function CreateOrEditConnectionSection({
oauth2App: selectedAuth.oauth2App,
grantType: selectedAuth.grantType,
redirectUrl: redirectUrl ?? '',
projectId: projectIdOverride ?? undefined,
}),
...(isGlobalConnection ? { scope: AppConnectionScope.PLATFORM } : {}),
projectIds: reconnectConnection?.projectIds ?? [],
Expand Down Expand Up @@ -357,6 +359,7 @@ function CreateOrEditConnectionDialog({
reconnectConnection,
isGlobalConnection,
externalIdComingFromSdk,
projectId: projectIdOverride,
}: ConnectionDialogProps) {
const { data: piecesOAuth2AppsMap, isPending: loadingPiecesOAuth2AppsMap } =
oauthAppsQueries.usePiecesOAuth2AppsMap();
Expand Down Expand Up @@ -391,6 +394,7 @@ function CreateOrEditConnectionDialog({
reconnectConnection={reconnectConnection}
isGlobalConnection={isGlobalConnection}
externalIdComingFromSdk={externalIdComingFromSdk}
projectId={projectIdOverride}
/>
)}
</DialogContent>
Expand Down Expand Up @@ -481,6 +485,7 @@ type ConnectionDialogProps = {
reconnectConnection: AppConnectionWithoutSensitiveData | null;
isGlobalConnection: boolean;
externalIdComingFromSdk?: string | null;
projectId?: string | null;
};

type CreateOrEditConnectionDialogContentProps = {
Expand All @@ -493,6 +498,7 @@ type CreateOrEditConnectionDialogContentProps = {
open: boolean,
connection?: AppConnectionWithoutSensitiveData,
) => void;
projectId?: string | null;
};

type CreateOrEditConnectionSectionProps =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,14 @@ async function openPopup({
let authorizationUrl, codeVerifier;
try {
setLoading(true);
const formProjectId = form.getValues().request.projectId;
const result = await appConnectionsApi.getOAuth2AuthorizationUrl({
pieceName,
clientId,
redirectUrl,
pieceVersion,
props,
projectId: formProjectId,
});
authorizationUrl = result.authorizationUrl;
codeVerifier = result.codeVerifier;
Expand Down
21 changes: 19 additions & 2 deletions packages/web/src/app/routes/chat-with-ai/ai-chat-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
ProjectType,
} from '@activepieces/shared';
import { t } from 'i18next';
import { AlertTriangle, RefreshCw, Square } from 'lucide-react';
import { AlertTriangle, RefreshCw, Square, X } from 'lucide-react';
import { motion } from 'motion/react';
import { useCallback, useEffect, useMemo, useState } from 'react';

Expand All @@ -16,6 +16,7 @@ import {
} from '@/components/prompt-kit/chat-container';
import { ScrollButton } from '@/components/prompt-kit/scroll-button';
import { Button } from '@/components/ui/button';
import { ChatUIMessage, DynamicToolPart } from '@/features/chat/lib/chat-types';
import { useAgentChat } from '@/features/chat/lib/use-chat';
import { useToolApproval } from '@/features/chat/lib/use-tool-approval';
import { aiProviderQueries } from '@/features/platform-admin';
Expand Down Expand Up @@ -157,6 +158,14 @@ function ChatBoxContent({
dismiss: dismissApproval,
} = useToolApproval({ pendingApprovalRequest });

const allConversationToolParts = useMemo(
() =>
messages.flatMap((m: ChatUIMessage) =>
m.parts.filter((p): p is DynamicToolPart => p.type === 'dynamic-tool'),
),
[messages],
);

const isEmpty = messages.length === 0 && !isLoadingHistory && !isStreaming;

if (isEmpty) {
Expand Down Expand Up @@ -217,6 +226,7 @@ function ChatBoxContent({
onRetry={handleRetry}
selectedProjectId={selectedProjectId}
onSelectProject={handleProjectChange}
allConversationToolParts={allConversationToolParts}
/>
);
})}
Expand Down Expand Up @@ -265,13 +275,20 @@ function ChatBoxContent({
<div className="max-w-3xl mx-auto relative">
{activeProject && (
<div
className="absolute top-0 right-3 z-20 -translate-y-1/2 rounded-full px-2.5 py-0.5 text-[10px] font-medium"
className="absolute top-0 right-3 z-20 -translate-y-1/2 rounded-full px-2.5 py-0.5 text-[10px] font-medium flex items-center gap-1"
style={{
backgroundColor: activeProject.color,
color: activeProject.textColor,
}}
>
{activeProject.name}
<button
type="button"
className="ml-0.5 rounded-full hover:opacity-70 transition-opacity"
onClick={() => handleProjectChange(null)}
>
<X className="h-2.5 w-2.5" />
</button>
</div>
)}
{hasActiveApproval ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,17 @@ export function BuildProgressCard({
<span>✨</span>
{progress.title}
</h3>
{isBuilt ? (
{isBuilt &&
(notesStatus === 'adding' ||
(notesStatus === 'none' && isStreaming)) ? (
<span className="inline-flex items-center gap-1.5 rounded-full border border-green-500/30 bg-green-500/10 text-green-600 dark:text-green-400 px-2.5 py-0.5 text-xs font-medium">
<Loader2 className="h-3 w-3 animate-spin" />
{t('Finishing up...')}
</span>
) : isBuilt ? (
<span className="inline-flex items-center gap-1 text-green-600 dark:text-green-400 text-xs font-medium">
<Check className="h-3.5 w-3.5" />
{t('Built')}
{t('Done')}
</span>
) : hasError ? (
<span className="inline-flex items-center gap-1.5 rounded-full border border-destructive/30 bg-destructive/10 text-destructive px-2.5 py-0.5 text-xs font-medium">
Expand Down Expand Up @@ -407,27 +414,6 @@ export function BuildProgressCard({
})}
</div>

{notesStatus !== 'none' && (
<motion.div
initial={reduce ? false : { opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: reduce ? 0 : 0.2 }}
className="mt-2 flex items-center gap-1.5 text-xs text-muted-foreground"
>
{notesStatus === 'adding' ? (
<>
<Loader2 className="h-3 w-3 animate-spin" />
{t('Adding notes...')}
</>
) : (
<>
<Check className="h-3 w-3 text-green-600 dark:text-green-400" />
{t('Notes added')}
</>
)}
</motion.div>
)}

{isBuilt && (
<motion.div
initial={reduce ? false : { opacity: 0, y: 8 }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function ChatMessage({
onSend,
selectedProjectId,
onSelectProject,
allConversationToolParts,
}: {
message: ChatUIMessage;
isStreaming: boolean;
Expand All @@ -40,6 +41,7 @@ export function ChatMessage({
onSend: (text: string, files?: File[]) => void;
selectedProjectId?: string | null;
onSelectProject?: (projectId: string) => void;
allConversationToolParts?: DynamicToolPart[];
}) {
if (message.role === 'user') {
return <UserMessage message={message} isLastMessage={isLastMessage} />;
Expand All @@ -54,6 +56,7 @@ export function ChatMessage({
onSend={onSend}
selectedProjectId={selectedProjectId}
onSelectProject={onSelectProject}
allConversationToolParts={allConversationToolParts}
/>
);
}
Expand Down Expand Up @@ -133,6 +136,7 @@ function AssistantMessage({
onSend,
selectedProjectId,
onSelectProject,
allConversationToolParts,
}: {
message: ChatUIMessage;
isStreaming: boolean;
Expand All @@ -141,6 +145,7 @@ function AssistantMessage({
onSend: (text: string, files?: File[]) => void;
selectedProjectId?: string | null;
onSelectProject?: (projectId: string) => void;
allConversationToolParts?: DynamicToolPart[];
}) {
const allToolParts = useMemo(
() =>
Expand Down Expand Up @@ -231,6 +236,7 @@ function AssistantMessage({
selectedProjectId,
onSelectProject,
allParts: message.parts,
allConversationToolParts,
})}
</motion.div>
)}
Expand Down Expand Up @@ -279,6 +285,7 @@ function renderTextParts({
onSelectProject,
isLastMessage,
allParts,
allConversationToolParts,
}: {
parts: Array<{ type: 'text'; text: string }>;
isStreaming: boolean;
Expand All @@ -287,14 +294,17 @@ function renderTextParts({
selectedProjectId?: string | null;
onSelectProject?: (projectId: string) => void;
allParts: ChatUIMessage['parts'];
allConversationToolParts?: DynamicToolPart[];
}): React.ReactNode[] {
const fullText = parts.map((p) => p.text).join('');
const { progress: buildProgress } = parseBuildProgress(fullText);

const nodes: React.ReactNode[] = [];

if (buildProgress) {
const toolParts = allParts.filter((p) => p.type === 'dynamic-tool');
const toolParts =
allConversationToolParts ??
allParts.filter((p) => p.type === 'dynamic-tool');
nodes.push(
<BuildProgressCard
key="build-progress"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,17 @@ export function ConnectionPickerCard({
picker,
onSelect,
isInteractive = true,
selectedProjectId,
}: ConnectionPickerCardProps) {
const queryClient = useQueryClient();
const pieceName = normalizePieceName(picker.piece);
const filteredPicker = useMemo(() => {
if (!selectedProjectId) return picker;
const filtered = picker.connections.filter(
(c) => c.projectId === selectedProjectId,
);
return { ...picker, connections: filtered };
}, [picker, selectedProjectId]);
const { pieceModel, isLoading: isPieceLoading } = piecesHooks.usePiece({
name: pieceName,
});
Expand All @@ -163,7 +171,7 @@ export function ConnectionPickerCard({
fullConnections,
isLoading: isLoadingStatuses,
} = useLiveConnections({
connections: picker.connections,
connections: filteredPicker.connections,
pieceName,
enabled: isInteractive && !selectedConnection,
});
Expand Down Expand Up @@ -201,7 +209,9 @@ export function ConnectionPickerCard({
</div>
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-semibold">{picker.displayName}</div>
<div className="text-sm font-semibold">
{filteredPicker.displayName}
</div>
<div className="text-xs text-muted-foreground">
{t('Connected')}
</div>
Expand All @@ -216,7 +226,7 @@ export function ConnectionPickerCard({
<SelectedState
pieceName={pieceName}
connection={selectedConnection}
displayName={picker.displayName}
displayName={filteredPicker.displayName}
/>
);
}
Expand All @@ -237,13 +247,13 @@ export function ConnectionPickerCard({
<div className="p-4 pb-3">
<h3 className="font-semibold text-base">
{t('Which {name} account should I use?', {
name: picker.displayName,
name: filteredPicker.displayName,
})}
</h3>
</div>

<div className="max-h-64 overflow-auto">
{picker.connections.map((conn) => {
{filteredPicker.connections.map((conn) => {
const status = liveStatuses[conn.externalId] ?? conn.status;
const healthy = isConnectionHealthy(status);
return (
Expand Down Expand Up @@ -316,7 +326,7 @@ export function ConnectionPickerCard({
</div>
<div className="text-xs text-muted-foreground">
{t('Connect a new {name} account', {
name: picker.displayName,
name: filteredPicker.displayName,
})}
</div>
</div>
Expand All @@ -336,6 +346,7 @@ export function ConnectionPickerCard({
<CreateOrEditConnectionDialog
piece={pieceModel}
open={connectDialogOpen}
projectId={selectedProjectId}
setOpen={(open, createdConnection) => {
setConnectDialogOpen(open);
if (createdConnection) {
Expand Down Expand Up @@ -364,4 +375,5 @@ type ConnectionPickerCardProps = {
picker: ConnectionPickerData;
onSelect: (text: string) => void;
isInteractive?: boolean;
selectedProjectId?: string | null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export function MessageContentWithAuth({
picker={connectionPicker}
onSelect={(text) => onSend?.(text)}
isInteractive={isLastMessage}
selectedProjectId={selectedProjectId}
/>
)}
{projectPicker && (
Expand Down Expand Up @@ -397,6 +398,7 @@ function ConnectionsRequiredCard({
key={activeConnection.piece}
piece={pieceModel}
open={true}
projectId={selectedProjectId}
setOpen={(open, createdConnection) => {
if (!open) {
if (createdConnection) {
Expand Down
Loading
Loading