Skip to content

Commit

Permalink
Merge pull request #64 from dcSpark/nico/fix_agent_channel
Browse files Browse the repository at this point in the history
fix agent channel + refactor redux
  • Loading branch information
nicarq authored Sep 8, 2023
2 parents aa59f75 + 55e51ea commit bc1e3a6
Show file tree
Hide file tree
Showing 39 changed files with 1,388 additions and 602 deletions.
50 changes: 48 additions & 2 deletions shinkai-app/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ import {
receiveAllInboxesForProfile,
receiveLoadMoreMessagesFromInbox,
addAgents,
receiveUnreadMessagesFromInbox,
} from "../store/actions";
import { AppThunk } from "../types";
import { ShinkaiMessageBuilderWrapper } from "../lib/wasm/ShinkaiMessageBuilderWrapper";
import { MergedSetupType } from "../pages/Connect";
import { ApiConfig } from "./api_config";
import { SetupDetailsState } from "../store/reducers";
import { ShinkaiMessage } from "../models/ShinkaiMessage";
import { ShinkaiNameWrapper } from "../lib/wasm/ShinkaiNameWrapper";
import { InboxNameWrapper } from "../pkg/shinkai_message_wasm";
import { SerializedAgent } from "../models/SchemaTypes";
import { SerializedAgentWrapper } from "../lib/wasm/SerializedAgentWrapper";
import { SetupDetailsState } from "../store/reducers/setupDetailsReducer";

// Helper function to handle HTTP errors
export const handleHttpError = (response: any) => {
Expand Down Expand Up @@ -234,6 +235,51 @@ export const getLastMessagesFromInbox =
}
};

export const getLastUnreadMessagesFromInbox =
(
inbox: string,
count: number,
fromKey: string | undefined,
setupDetailsState: SetupDetailsState
) =>
async (dispatch: AppDispatch) => {
try {
console.log("fromKey: ", fromKey);
let sender =
setupDetailsState.shinkai_identity + "/" + setupDetailsState.profile;

const messageStr =
ShinkaiMessageBuilderWrapper.get_last_messages_from_inbox(
setupDetailsState.profile_encryption_sk,
setupDetailsState.profile_identity_sk,
setupDetailsState.node_encryption_pk,
inbox,
count,
fromKey,
sender,
"",
setupDetailsState.shinkai_identity
);

const message = JSON.parse(messageStr);
console.log("Message:", message);

const apiEndpoint = ApiConfig.getInstance().getEndpoint();
const response = await axios.post(
`${apiEndpoint}/v1/last_unread_messages_from_inbox`,
message
);

handleHttpError(response);
let results = response.data;

console.log("getLastUnreadMessagesFromInbox Response:", results);
dispatch(receiveUnreadMessagesFromInbox(inbox, results));
} catch (error) {
console.error("Error getting last messages from inbox:", error);
}
};

export const submitRequestRegistrationCode =
(
identity_permissions: string,
Expand Down Expand Up @@ -409,7 +455,7 @@ export const sendMessageToJob =
);

handleHttpError(response);
dispatch(response.data);
dispatch({ type: "SEND_MESSAGE_SUCCESS", payload: response.data });
} catch (error) {
console.error("Error sending message to job:", error);
}
Expand Down
173 changes: 173 additions & 0 deletions shinkai-app/src/components/ChatMessages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getLastMessagesFromInbox, getLastUnreadMessagesFromInbox } from "../api/index";
import { ShinkaiMessage } from "../models/ShinkaiMessage";
import { IonList, IonItem, IonButton } from "@ionic/react";
import Avatar from "../components/ui/Avatar";
import { cn } from "../theme/lib/utils";
import { IonContentCustom } from "./ui/Layout";
import { calculateMessageHash } from "../utils/shinkai_message_handler";
import { RootState } from "../store";

interface ChatMessagesProps {
deserializedId: string;
}

const ChatMessages: React.FC<ChatMessagesProps> = ({ deserializedId }) => {
console.log("Loading ChatMessages.tsx");
const dispatch = useDispatch();
const setupDetailsState = useSelector(
(state: RootState) => state.setupDetails
);
const reduxMessages = useSelector(
(state: RootState) => state.messages.inboxes[deserializedId]
);

const [lastKey, setLastKey] = useState<string | undefined>(undefined);
const [mostRecentKey, setMostRecentKey] = useState<string | undefined>(undefined);
const [prevMessagesLength, setPrevMessagesLength] = useState(0);
const [hasMoreMessages, setHasMoreMessages] = useState(true);
const [messages, setMessages] = useState<ShinkaiMessage[]>([]);

useEffect(() => {
console.log("deserializedId:", deserializedId);
dispatch(
getLastMessagesFromInbox(deserializedId, 10, lastKey, setupDetailsState)
);
}, [dispatch, setupDetailsState]);

useEffect(() => {
const interval = setInterval(() => {
const lastMessage = reduxMessages[reduxMessages.length - 1];
const hashKey = calculateMessageHash(lastMessage);
dispatch(
getLastUnreadMessagesFromInbox(deserializedId, 10, mostRecentKey, setupDetailsState)
);
}, 5000); // 2000 milliseconds = 2 seconds
return () => clearInterval(interval);
}, [dispatch, deserializedId, mostRecentKey, setupDetailsState, reduxMessages]);

useEffect(() => {
if (reduxMessages && reduxMessages.length > 0) {
// console.log("Redux Messages:", reduxMessages);
const lastMessage = reduxMessages[reduxMessages.length - 1];
console.log("Last Message:", lastMessage);
const timeKey = lastMessage.external_metadata.scheduled_time;
const hashKey = calculateMessageHash(lastMessage);
const lastMessageKey = `${timeKey}:::${hashKey}`;
setLastKey(lastMessageKey);

const mostRecentMessage = reduxMessages[0];
const mostRecentTimeKey = mostRecentMessage.external_metadata.scheduled_time;
const mostRecentHashKey = calculateMessageHash(mostRecentMessage);
const mostRecentMessageKey = `${mostRecentTimeKey}:::${mostRecentHashKey}`;
setMostRecentKey(mostRecentMessageKey);

setMessages(reduxMessages);

if (reduxMessages.length - prevMessagesLength < 10) {
setHasMoreMessages(false);
}
setPrevMessagesLength(reduxMessages.length);
}
}, [reduxMessages]);

const extractContent = (messageBody: any) => {
// TODO: extend it so it can be re-used by JobChat or normal Chat
if (messageBody && "unencrypted" in messageBody) {
if ("unencrypted" in messageBody.unencrypted.message_data) {
return JSON.parse(
messageBody.unencrypted.message_data.unencrypted.message_raw_content
).content;
} else {
return JSON.parse(
messageBody.unencrypted.message_data.encrypted.content
).content;
}
} else if (messageBody?.encrypted) {
return JSON.parse(messageBody.encrypted.content).content;
}
return "";
};

return (
<IonContentCustom>
<div className="py-10 md:rounded-[1.25rem] bg-white dark:bg-slate-800">
{hasMoreMessages && (
<IonButton
onClick={() =>
dispatch(
getLastMessagesFromInbox(
deserializedId,
10,
lastKey,
setupDetailsState,
true
)
)
}
>
Load More
</IonButton>
)}
<IonList class="ion-list-chat p-0 divide-y divide-slate-200 dark:divide-slate-500/50 md:rounded=[1.25rem] ">
{messages &&
messages
.slice()
.map((message, index) => {
const { shinkai_identity, profile, registration_name } =
setupDetailsState;

const localIdentity = `${profile}/device/${registration_name}`;
// console.log("Message:", message);
let isLocalMessage = false;
if (message.body && "unencrypted" in message.body) {
isLocalMessage =
message.body.unencrypted.internal_metadata
.sender_subidentity === localIdentity;
}

return (
<IonItem
key={index}
lines="none"
className={cn(
"ion-item-chat relative w-full shadow",
isLocalMessage && "isLocalMessage"
)}
>
<div className="px-2 py-4 flex gap-4 pb-10 w-full">
<Avatar
className="shrink-0 mr-4"
url={
isLocalMessage
? "https://ui-avatars.com/api/?name=Me&background=FE6162&color=fff"
: "https://ui-avatars.com/api/?name=O&background=363636&color=fff"
}
/>

<p>{extractContent(message.body)}</p>
{message?.external_metadata?.scheduled_time && (
<span className="absolute bottom-[5px] right-5 text-muted text-sm">
{new Date(
message.external_metadata.scheduled_time
).toLocaleString(undefined, {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</span>
)}
</div>
</IonItem>
);
})}
</IonList>
</div>
</IonContentCustom>
);
};

export default ChatMessages;
13 changes: 8 additions & 5 deletions shinkai-app/src/hooks/usetSetup.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// hooks/useSetup.ts
import { useEffect } from "react";
import { useSelector } from "react-redux";
import { shallowEqual, useSelector } from "react-redux";
import { RootState } from "../store";
import { ApiConfig } from "../api/api_config";

export const useSetup = () => {
const { setupDetailsState } = useSelector((state: RootState) => state);
const setupDetails = useSelector(
(state: RootState) => state.setupDetails,
shallowEqual
);

useEffect(() => {
console.log("Redux State:", setupDetailsState);
ApiConfig.getInstance().setEndpoint(setupDetailsState.node_address);
}, [setupDetailsState]);
console.log("Redux State:", setupDetails);
ApiConfig.getInstance().setEndpoint(setupDetails.node_address);
}, [setupDetails]);
};
29 changes: 27 additions & 2 deletions shinkai-app/src/lib/wasm/ShinkaiMessageBuilderWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export class ShinkaiMessageBuilderWrapper {
receiver_public_key: string,
inbox: string,
count: number,
offset: string | undefined,
until_offset: string | undefined,
sender: string,
sender_profile_name: string,
receiver: string
Expand All @@ -276,7 +276,32 @@ export class ShinkaiMessageBuilderWrapper {
receiver_public_key,
inbox,
count,
offset,
until_offset,
sender,
sender_profile_name,
receiver,
""
);
}

static get_last_unread_messages_from_inbox(
my_subidentity_encryption_sk: string,
my_subidentity_signature_sk: string,
receiver_public_key: string,
inbox: string,
count: number,
from_offset: string | undefined,
sender: string,
sender_profile_name: string,
receiver: string
): string {
return ShinkaiMessageBuilderWrapperWASM.get_last_unread_messages_from_inbox(
my_subidentity_encryption_sk,
my_subidentity_signature_sk,
receiver_public_key,
inbox,
count,
from_offset,
sender,
sender_profile_name,
receiver,
Expand Down
20 changes: 9 additions & 11 deletions shinkai-app/src/pages/AddAgent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,19 @@ import {
import { useEffect, useState } from "react";
import { IonContentCustom, IonHeaderCustom } from "../components/ui/Layout";
import Button from "../components/ui/Button";
import Input from "../components/ui/Input";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../store";
import { SerializedAgent, AgentAPIModel } from "../models/SchemaTypes";
import { addAgent } from "../api";
import { useSetup } from "../hooks/usetSetup";
import { useHistory } from 'react-router-dom';


const AddAgent: React.FC = () => {
useSetup();
const dispatch = useDispatch();
const setupDetailsState = useSelector(
(state: RootState) => state.setupDetailsState
(state: RootState) => state.setupDetails
);
const [agent, setAgent] = useState<Partial<SerializedAgent>>({
perform_locally: false,
Expand Down Expand Up @@ -70,7 +71,7 @@ const AddAgent: React.FC = () => {
});
};

const handleSubmit = () => {
const handleSubmit = async () => {
const { shinkai_identity, profile } = setupDetailsState;
let node_name = shinkai_identity;

Expand All @@ -82,14 +83,11 @@ const AddAgent: React.FC = () => {
}

console.log("Submitting agent:", agent);
dispatch(
addAgent(
profile,
node_name,
agent as SerializedAgent,
setupDetailsState
)
);
const resp = await addAgent(profile, node_name, agent as SerializedAgent, setupDetailsState);
if (resp) {
// TODO: show a success toast
history.back();
}
};

return (
Expand Down
4 changes: 2 additions & 2 deletions shinkai-app/src/pages/AdminCommands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { useSetup } from "../hooks/usetSetup";
const AdminCommands: React.FC = () => {
useSetup();
const setupDetailsState = useSelector(
(state: RootState) => state.setupDetailsState
(state: RootState) => state.setupDetails
);
const [showCodeRegistrationActionSheet, setShowCodeRegistrationActionSheet] =
useState(false);
Expand All @@ -36,7 +36,7 @@ const AdminCommands: React.FC = () => {
const [profileName, setProfileName] = useState("");
const dispatch = useDispatch();
const registrationCode = useSelector(
(state: RootState) => state.registrationCode
(state: RootState) => state.other.registrationCode
);
const commands = [
"Get Peers",
Expand Down
Loading

0 comments on commit bc1e3a6

Please sign in to comment.