feat: enhance agent sharing functionality and UI improvements

- Added shared agents state management in Navigation and AgentsList components.
- Implemented fetching and displaying shared agents in the AgentsList.
- Introduced functionality to hide shared agents with appropriate API integration.
- Updated the SharedAgent component layout for better UI consistency.
- Improved error handling in conversation fetching logic.
- Added new API endpoint for hiding shared agents.
- Updated Redux slice to manage shared agents state.
- Refactored AgentCard and AgentSection components for better code organization and readability.
This commit is contained in:
Siddhant Rai
2025-05-17 05:53:56 +05:30
parent 9d8073d468
commit 56793c8db7
11 changed files with 296 additions and 213 deletions

View File

@@ -525,8 +525,7 @@ class Stream(Resource):
user_api_key=user_api_key,
decoded_token=decoded_token,
)
is_shared_usage_val = data.get("is_shared_usage", False)
is_shared_token_val = data.get("shared_token", None)
return Response(
complete_stream(
question=question,
@@ -539,8 +538,8 @@ class Stream(Resource):
index=index,
should_save_conversation=save_conv,
agent_id=agent_id,
is_shared_usage=is_shared_usage_val,
shared_token=is_shared_token_val,
is_shared_usage=is_shared_usage,
shared_token=shared_token,
),
mimetype="text/event-stream",
)

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,7 @@ import {
selectConversations,
selectModalStateDeleteConv,
selectSelectedAgent,
selectSharedAgents,
selectToken,
setAgents,
setConversations,
@@ -67,6 +68,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
const conversationId = useSelector(selectConversationId);
const modalStateDeleteConv = useSelector(selectModalStateDeleteConv);
const agents = useSelector(selectAgents);
const sharedAgents = useSelector(selectSharedAgents);
const selectedAgent = useSelector(selectSelectedAgent);
const { isMobile } = useMediaQuery();
@@ -129,7 +131,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
useEffect(() => {
fetchRecentAgents();
}, [agents, token, dispatch]);
}, [agents, sharedAgents, token, dispatch]);
useEffect(() => {
if (!conversations?.data) fetchConversations();
@@ -179,8 +181,16 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
dispatch(setSelectedAgent(null));
conversationService
.getConversation(index, token)
.then((response) => response.json())
.then((response) => {
if (!response.ok) {
navigate('/');
dispatch(setSelectedAgent(null));
return null;
}
return response.json();
})
.then((data) => {
if (!data) return;
dispatch(setConversation(data.queries));
dispatch(
updateConversationId({
@@ -192,20 +202,30 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
userService
.getSharedAgent(data.shared_token, token)
.then((response) => {
if (response.ok) {
response.json().then((agent: Agent) => {
navigate(`/agents/shared/${agent.shared_token}`);
});
if (!response.ok) {
navigate('/');
dispatch(setSelectedAgent(null));
return;
}
response.json().then((agent: Agent) => {
navigate(`/agents/shared/${agent.shared_token}`);
});
});
} else {
userService.getAgent(data.agent_id, token).then((response) => {
if (response.ok) {
response.json().then((agent: Agent) => {
navigate('/');
dispatch(setSelectedAgent(agent));
});
if (!response.ok) {
navigate('/');
dispatch(setSelectedAgent(null));
return;
}
response.json().then((agent: Agent) => {
if (agent.shared_token)
navigate(`/agents/shared/${agent.shared_token}`);
else {
dispatch(setSelectedAgent(agent));
navigate('/');
}
});
});
}
} else {

View File

@@ -181,7 +181,7 @@ export default function SharedAgent() {
}
/>
</div>
<div className="flex w-[95%] max-w-[1500px] flex-col items-center gap-4 pb-2 md:w-9/12 lg:w-8/12 xl:w-8/12 2xl:w-6/12">
<div className="flex w-[95%] max-w-[1500px] flex-col items-center pb-2 md:w-9/12 lg:w-8/12 xl:w-8/12 2xl:w-6/12">
<MessageInput
value={input}
onChange={(e) => setInput(e.target.value)}

View File

@@ -4,11 +4,11 @@ import { Route, Routes, useNavigate } from 'react-router-dom';
import userService from '../api/services/userService';
import Edit from '../assets/edit.svg';
import Link from '../assets/link-gray.svg';
import Monitoring from '../assets/monitoring.svg';
import Pin from '../assets/pin.svg';
import Trash from '../assets/red-trash.svg';
import Robot from '../assets/robot.svg';
import Link from '../assets/link-gray.svg';
import ThreeDots from '../assets/three-dots.svg';
import UnPin from '../assets/unpin.svg';
import ContextMenu, { MenuOption } from '../components/ContextMenu';
@@ -22,9 +22,11 @@ import { ActiveState } from '../models/misc';
import {
selectAgents,
selectSelectedAgent,
selectSharedAgents,
selectToken,
setAgents,
setSelectedAgent,
setSharedAgents,
} from '../preferences/preferenceSlice';
import AgentLogs from './AgentLogs';
import NewAgent from './NewAgent';
@@ -59,13 +61,12 @@ const sectionConfig = {
};
function AgentsList() {
const navigate = useNavigate();
const dispatch = useDispatch();
const token = useSelector(selectToken);
const agents = useSelector(selectAgents);
const sharedAgents = useSelector(selectSharedAgents);
const selectedAgent = useSelector(selectSelectedAgent);
const [sharedAgents, setSharedAgents] = useState<Agent[]>([]);
const [loadingUserAgents, setLoadingUserAgents] = useState<boolean>(true);
const [loadingSharedAgents, setLoadingSharedAgents] = useState<boolean>(true);
@@ -89,7 +90,7 @@ function AgentsList() {
const response = await userService.getSharedAgents(token);
if (!response.ok) throw new Error('Failed to fetch shared agents');
const data = await response.json();
setSharedAgents(data);
dispatch(setSharedAgents(data));
setLoadingSharedAgents(false);
} catch (error) {
console.error('Error:', error);
@@ -162,11 +163,17 @@ function AgentsList() {
</div> */}
<AgentSection
agents={agents ?? []}
updateAgents={(updatedAgents) => {
dispatch(setAgents(updatedAgents));
}}
loading={loadingUserAgents}
section="user"
/>
<AgentSection
agents={sharedAgents ?? []}
updateAgents={(updatedAgents) => {
dispatch(setSharedAgents(updatedAgents));
}}
loading={loadingSharedAgents}
section="shared"
/>
@@ -176,10 +183,12 @@ function AgentsList() {
function AgentSection({
agents,
updateAgents,
loading,
section,
}: {
agents: Agent[];
updateAgents?: (agents: Agent[]) => void;
loading: boolean;
section: keyof typeof sectionConfig;
}) {
@@ -204,20 +213,23 @@ function AgentSection({
</button>
)}
</div>
<div className="grid w-full grid-cols-2 gap-2 md:flex md:flex-wrap md:gap-4">
<div>
{loading ? (
<div className="flex h-72 w-full items-center justify-center">
<Spinner />
</div>
) : agents && agents.length > 0 ? (
agents.map((agent) => (
<AgentCard
key={agent.id}
agent={agent}
agents={agents}
section={section}
/>
))
<div className="grid grid-cols-1 gap-4 sm:flex sm:flex-wrap">
{agents.map((agent, idx) => (
<AgentCard
key={agent.id}
agent={agent}
agents={agents}
updateAgents={updateAgents}
section={section}
/>
))}
</div>
) : (
<div className="flex h-72 w-full flex-col items-center justify-center gap-3 text-base text-[#18181B] dark:text-[#E0E0E0]">
<p>{sectionConfig[section].emptyStateDescription}</p>
@@ -239,10 +251,12 @@ function AgentSection({
function AgentCard({
agent,
agents,
updateAgents,
section,
}: {
agent: Agent;
agents: Agent[];
updateAgents?: (agents: Agent[]) => void;
section: keyof typeof sectionConfig;
}) {
const navigate = useNavigate();
@@ -264,7 +278,20 @@ function AgentCard({
return { ...prevAgent, pinned: !prevAgent.pinned };
return prevAgent;
});
dispatch(setAgents(updatedAgents));
updateAgents?.(updatedAgents);
} catch (error) {
console.error('Error:', error);
}
};
const handleHideSharedAgent = async () => {
try {
const response = await userService.hideSharedAgent(agent.id ?? '', token);
if (!response.ok) throw new Error('Failed to hide shared agent');
const updatedAgents = agents.filter(
(prevAgent) => prevAgent.id !== agent.id,
);
updateAgents?.(updatedAgents);
} catch (error) {
console.error('Error:', error);
}
@@ -326,8 +353,30 @@ function AgentCard({
navigate(`/agents/shared/${agent.shared_token}`);
},
variant: 'primary',
iconWidth: 14,
iconHeight: 14,
iconWidth: 12,
iconHeight: 12,
},
{
icon: agent.pinned ? UnPin : Pin,
label: agent.pinned ? 'Unpin' : 'Pin agent',
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
togglePin();
},
variant: 'primary',
iconWidth: 18,
iconHeight: 18,
},
{
icon: Trash,
label: 'Remove',
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
handleHideSharedAgent();
},
variant: 'danger',
iconWidth: 13,
iconHeight: 13,
},
],
};

View File

@@ -18,6 +18,7 @@ const endpoints = {
SHARED_AGENT: (id: string) => `/api/shared_agent?token=${id}`,
SHARED_AGENTS: '/api/shared_agents',
SHARE_AGENT: `/api/share_agent`,
HIDE_SHARED_AGENT: (id: string) => `/api/hide_shared_agent?id=${id}`,
AGENT_WEBHOOK: (id: string) => `/api/agent_webhook?id=${id}`,
PROMPTS: '/api/get_prompts',
CREATE_PROMPT: '/api/create_prompt',

View File

@@ -41,6 +41,8 @@ const userService = {
apiClient.get(endpoints.USER.SHARED_AGENTS, token),
shareAgent: (data: any, token: string | null): Promise<any> =>
apiClient.put(endpoints.USER.SHARE_AGENT, data, token),
hideSharedAgent: (id: string, token: string | null): Promise<any> =>
apiClient.delete(endpoints.USER.HIDE_SHARED_AGENT(id), token),
getAgentWebhook: (id: string, token: string | null): Promise<any> =>
apiClient.get(endpoints.USER.AGENT_WEBHOOK(id), token),
getPrompts: (token: string | null): Promise<any> =>

View File

@@ -1,3 +1,3 @@
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 1.5H3C2.46957 1.5 1.96086 1.71071 1.58579 2.08579C1.21071 2.46086 1 2.96957 1 3.5V15.5C1 16.0304 1.21071 16.5391 1.58579 16.9142C1.96086 17.2893 2.46957 17.5 3 17.5H15C15.5304 17.5 16.0391 17.2893 16.4142 16.9142C16.7893 16.5391 17 16.0304 17 15.5V11.5M9 9.5L17 1.5M17 1.5V6.5M17 1.5H12" stroke="#949494" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 1.5H3C2.46957 1.5 1.96086 1.71071 1.58579 2.08579C1.21071 2.46086 1 2.96957 1 3.5V15.5C1 16.0304 1.21071 16.5391 1.58579 16.9142C1.96086 17.2893 2.46957 17.5 3 17.5H15C15.5304 17.5 16.0391 17.2893 16.4142 16.9142C16.7893 16.5391 17 16.0304 17 15.5V11.5M9 9.5L17 1.5M17 1.5V6.5M17 1.5H12" stroke="#747474" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 486 B

After

Width:  |  Height:  |  Size: 486 B

View File

@@ -150,10 +150,7 @@ export function handleFetchAnswerSteaming(
done,
value,
}: ReadableStreamReadResult<Uint8Array>) => {
if (done) {
console.log(counterrr);
return;
}
if (done) return;
counterrr += 1;
@@ -163,7 +160,7 @@ export function handleFetchAnswerSteaming(
const events = buffer.split('\n\n');
buffer = events.pop() ?? '';
for (let event of events) {
for (const event of events) {
if (event.trim().startsWith('data:')) {
const dataLine: string = event
.split('\n')

View File

@@ -25,6 +25,7 @@ export interface Preference {
modalState: ActiveState;
paginatedDocuments: Doc[] | null;
agents: Agent[] | null;
sharedAgents: Agent[] | null;
selectedAgent: Agent | null;
}
@@ -51,6 +52,7 @@ const initialState: Preference = {
modalState: 'INACTIVE',
paginatedDocuments: null,
agents: null,
sharedAgents: null,
selectedAgent: null,
};
@@ -91,6 +93,9 @@ export const prefSlice = createSlice({
setAgents: (state, action) => {
state.agents = action.payload;
},
setSharedAgents: (state, action) => {
state.sharedAgents = action.payload;
},
setSelectedAgent: (state, action) => {
state.selectedAgent = action.payload;
},
@@ -109,6 +114,7 @@ export const {
setModalStateDeleteConv,
setPaginatedDocuments,
setAgents,
setSharedAgents,
setSelectedAgent,
} = prefSlice.actions;
export default prefSlice.reducer;
@@ -185,5 +191,7 @@ export const selectTokenLimit = (state: RootState) =>
export const selectPaginatedDocuments = (state: RootState) =>
state.preference.paginatedDocuments;
export const selectAgents = (state: RootState) => state.preference.agents;
export const selectSharedAgents = (state: RootState) =>
state.preference.sharedAgents;
export const selectSelectedAgent = (state: RootState) =>
state.preference.selectedAgent;

View File

@@ -42,6 +42,7 @@ const preloadedState: { preference: Preference } = {
modalState: 'INACTIVE',
paginatedDocuments: null,
agents: null,
sharedAgents: null,
selectedAgent: null,
},
};