Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shinkai web: smart inboxes #69

Merged
merged 11 commits into from
Nov 7, 2023
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
4 changes: 2 additions & 2 deletions apps/shinkai-app/src/pages/Connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const Connect = () => {
registration_name: 'main_device',
identity_type: 'device',
permission_type: 'admin',
shinkai_identity: '@@node1.shinkai', // this should actually be read from ENV
shinkai_identity: '@@localhost.shinkai', // this should actually be read from ENV
node_encryption_pk: '',
node_signature_pk: '',
profile_encryption_sk: '',
Expand Down Expand Up @@ -656,7 +656,7 @@ function AutomaticForm() {
registration_name: 'main_device',
identity_type: 'device',
permission_type: 'admin',
shinkai_identity: '@@node1.shinkai', // this should actually be read from ENV
shinkai_identity: '@@localhost.shinkai', // this should actually be read from ENV
node_encryption_pk: '',
node_signature_pk: '',
profile_encryption_sk: '',
Expand Down
155 changes: 126 additions & 29 deletions apps/shinkai-app/src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,153 @@
import './Home.css';

import { zodResolver } from '@hookform/resolvers/zod';
import {
IonActionSheet,
IonAlert,
IonButton,
IonButtons,
IonIcon,
IonInput,
IonItem,
IonLabel,
IonPage,
IonText,
IonTitle,
} from '@ionic/react';
import { SmartInbox } from '@shinkai_network/shinkai-message-ts/models';
import { useUpdateInboxName } from '@shinkai_network/shinkai-node-state/lib/mutations/updateInboxName/useUpdateInboxName';
import { useGetInboxes } from '@shinkai_network/shinkai-node-state/lib/queries/getInboxes/useGetInboxes';
import { addOutline, arrowForward } from 'ionicons/icons';
import { Edit3Icon } from 'lucide-react';
import React, { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useHistory } from 'react-router-dom';
import { z } from 'zod';

import Avatar from '../components/ui/Avatar';
import Button from '../components/ui/Button';
import { IonContentCustom, IonHeaderCustom } from '../components/ui/Layout';
import { useSetup } from '../hooks/usetSetup';
import { useAuth } from '../store/auth';

const updateInboxNameSchema = z.object({
inboxName: z.string(),
});

const MessageButton = ({ inbox }: { inbox: SmartInbox }) => {
const history = useHistory();
const auth = useAuth((state) => state.auth);
const [isEditable, setIsEditable] = useState(false);
const updateInboxNameForm = useForm<z.infer<typeof updateInboxNameSchema>>({
resolver: zodResolver(updateInboxNameSchema),
});

const { inboxName: inboxNameValue } = updateInboxNameForm.watch();
const { mutateAsync: updateInboxName } = useUpdateInboxName();

const onSubmit = async (data: z.infer<typeof updateInboxNameSchema>) => {
if (!auth) return;
console.log('data', data);
await updateInboxName({
sender: auth?.shinkai_identity ?? '',
senderSubidentity: auth?.profile,
receiver: auth.shinkai_identity,
receiverSubidentity: '',
my_device_encryption_sk: auth.my_device_encryption_sk,
my_device_identity_sk: auth.my_device_identity_sk,
node_encryption_pk: auth.node_encryption_pk,
profile_encryption_sk: auth.profile_encryption_sk,
profile_identity_sk: auth.profile_identity_sk,
inboxId: decodeURIComponent(inbox.inbox_id),
inboxName: data.inboxName,
});
setIsEditable(false);
};

if (isEditable) {
return (
<form
className="flex justify-between items-center gap-2"
onSubmit={updateInboxNameForm.handleSubmit(onSubmit)}
>
<Controller
control={updateInboxNameForm.control}
name="inboxName"
render={({ field }) => (
<div className="flex-1">
<IonLabel className="sr-only">Rename inbox name</IonLabel>
<IonInput
onIonInput={(e) =>
updateInboxNameForm.setValue(
'inboxName',
e.detail.value as string
)
}
placeholder={decodeURIComponent(inbox.custom_name)}
value={field.value}
/>
</div>
)}
/>
{inboxNameValue ? (
<Button className="w-auto" isLoading={false} type="submit">
Save
</Button>
) : (
<Button
className="w-auto"
isLoading={false}
onClick={() => setIsEditable(false)}
variant="secondary"
>
Cancel
</Button>
)}
</form>
);
}

return (
<div className="flex items-center gap-2" key={inbox.inbox_id}>
<IonItem
button
className="ion-item-home"
onClick={() => {
const encodedInboxId = inbox.inbox_id.toString().replace(/\./g, '~');
if (encodedInboxId.startsWith('inbox')) {
history.push(`/chat/${encodeURIComponent(encodedInboxId)}`);
} else if (encodedInboxId.startsWith('job_inbox')) {
history.push(`/job-chat/${encodeURIComponent(encodedInboxId)}`);
}
}}
>
<Avatar
className="shrink-0"
url={`https://ui-avatars.com/api/?name=${inbox.custom_name}&background=FE6162&color=fff`}
/>
<IonText className="ml-4 font-medium md:text-lg">
{decodeURIComponent(inbox.custom_name)}
</IonText>
<IonIcon className="hidden md:ml-auto md:block" icon={arrowForward} />
</IonItem>
<button
onClick={() => {
console.log(inbox.inbox_id);
setIsEditable(true);
}}
>
<Edit3Icon />
</button>
</div>
);
};

const Home: React.FC = () => {
useSetup();
const auth = useAuth((state) => state.auth);
const setLogout = useAuth((state) => state.setLogout);

const { inboxIds } = useGetInboxes({
const { inboxes } = useGetInboxes({
sender: auth?.shinkai_identity ?? '',
senderSubidentity: `${auth?.profile}/device/${auth?.registration_name}`,
// Assuming receiver and target_shinkai_name_profile are the same as sender
Expand Down Expand Up @@ -74,34 +197,8 @@ const Home: React.FC = () => {
<IonContentCustom>
<div className="h-full flex flex-col mt-4">
<div className="flex-1 md:rounded-lg space-y-2 md:space-y-4">
{inboxIds?.map((inboxId) => (
<IonItem
button
className="ion-item-home"
key={inboxId}
onClick={() => {
const encodedInboxId = inboxId.toString().replace(/\./g, '~');
if (encodedInboxId.startsWith('inbox')) {
history.push(`/chat/${encodeURIComponent(encodedInboxId)}`);
} else if (encodedInboxId.startsWith('job_inbox')) {
history.push(
`/job-chat/${encodeURIComponent(encodedInboxId)}`
);
}
}}
>
<Avatar
className="shrink-0"
url={`https://ui-avatars.com/api/?name=${inboxId}&background=FE6162&color=fff`}
/>
<IonText className="ml-4 font-medium md:text-lg">
{JSON.stringify(inboxId)}
</IonText>
<IonIcon
className="hidden md:ml-auto md:block"
icon={arrowForward}
/>
</IonItem>
{inboxes?.map((inbox) => (
<MessageButton inbox={inbox} key={inbox.inbox_id} />
))}
</div>
</div>
Expand Down
21 changes: 13 additions & 8 deletions apps/shinkai-visor/src/components/inboxes/inboxes.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { isJobInbox } from '@shinkai_network/shinkai-message-ts/utils';
import {
getMessageContent,
isJobInbox,
} from '@shinkai_network/shinkai-message-ts/utils';
import { useAgents } from '@shinkai_network/shinkai-node-state/lib/queries/getAgents/useGetAgents';
import { useGetInboxes } from '@shinkai_network/shinkai-node-state/lib/queries/getInboxes/useGetInboxes';
import {
Expand Down Expand Up @@ -33,7 +36,7 @@ export const Inboxes = () => {
const dialContainerRef = useRef<HTMLDivElement>(null);
const [dialOpened, setDialOpened] = useState<boolean>(false);
const sender = auth?.shinkai_identity ?? '';
const { inboxIds } = useGetInboxes({
const { inboxes } = useGetInboxes({
sender: auth?.shinkai_identity ?? '',
senderSubidentity: `${auth?.profile}/device/${auth?.registration_name}`,
// Assuming receiver and target_shinkai_name_profile are the same as sender
Expand Down Expand Up @@ -73,27 +76,29 @@ export const Inboxes = () => {
/>
{!agents?.length ? (
<EmptyAgents></EmptyAgents>
) : !inboxIds?.length ? (
) : !inboxes?.length ? (
<EmptyInboxes></EmptyInboxes>
) : (
<>
<div className="grow flex flex-col overflow-hidden">
<ScrollArea className="[&>div>div]:!block">
{inboxIds?.map((inboxId) => (
<Fragment key={inboxId}>
{inboxes?.map((inbox) => (
<Fragment key={inbox.inbox_id}>
<Button
className="w-full"
onClick={() => navigateToInbox(inboxId)}
onClick={() => navigateToInbox(inbox.inbox_id)}
variant="tertiary"
>
{isJobInbox(decodeURIComponent(inboxId)) ? (
{isJobInbox(decodeURIComponent(inbox.inbox_id)) ? (
<Workflow className="h-4 w-4 shrink-0 mr-2" />
) : (
<MessageCircleIcon className="h-4 w-4 shrink-0 mr-2" />
)}

<span className="w-full truncate">
{decodeURIComponent(inboxId)}
{inbox.custom_name === inbox.inbox_id
? getMessageContent(inbox.last_message)?.slice(0, 40)
: inbox.custom_name}
</span>
</Button>
</Fragment>
Expand Down
2 changes: 1 addition & 1 deletion libs/shinkai-message-ts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shinkai_network/shinkai-message-ts",
"version": "0.0.12",
"version": "0.0.13",
"description": "Shinkai Message TS wrapper for wasm library",
"type": "module",
"main": "src/index.js",
Expand Down
67 changes: 55 additions & 12 deletions libs/shinkai-message-ts/src/api/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
LastMessagesFromInboxCredentialsPayload,
SetupPayload,
ShinkaiMessage,
SmartInbox,
} from '../models';
import { APIUseRegistrationCodeSuccessResponse } from '../models/Payloads';
import { SerializedAgent } from '../models/SchemaTypes';
Expand Down Expand Up @@ -32,7 +33,7 @@
}
};

export const fetchPublicKey = () => async (): Promise<any> => {

Check warning on line 36 in libs/shinkai-message-ts/src/api/methods.ts

View workflow job for this annotation

GitHub Actions / health-checks (lint)

Unexpected any. Specify a different type
const apiEndpoint = ApiConfig.getInstance().getEndpoint();
try {
const response = await fetch(`${apiEndpoint}/get_public_key`);
Expand Down Expand Up @@ -198,23 +199,24 @@
receiver: string,
target_shinkai_name_profile: string,
setupDetailsState: CredentialsPayload
): Promise<string[]> => {
): Promise<SmartInbox[]> => {
try {
const messageStr = ShinkaiMessageBuilderWrapper.get_all_inboxes_for_profile(
setupDetailsState.my_device_encryption_sk,
setupDetailsState.my_device_identity_sk,
setupDetailsState.node_encryption_pk,
sender + '/' + sender_subidentity,
'',
receiver,
target_shinkai_name_profile
);
const messageString =
ShinkaiMessageBuilderWrapper.get_all_inboxes_for_profile(
setupDetailsState.my_device_encryption_sk,
setupDetailsState.my_device_identity_sk,
setupDetailsState.node_encryption_pk,
sender,
sender_subidentity,
receiver,
target_shinkai_name_profile
);

const message = JSON.parse(messageStr);
const message = JSON.parse(messageString);

const apiEndpoint = ApiConfig.getInstance().getEndpoint();
const response = await fetch(
`${apiEndpoint}/v1/get_all_inboxes_for_profile`,
`${apiEndpoint}/v1/get_all_smart_inboxes_for_profile`,
{
method: 'POST',
body: JSON.stringify(message),
Expand All @@ -230,12 +232,53 @@
}
};

export const updateInboxName = async (
sender: string,
sender_subidentity: string,
receiver: string,
receiver_subidentity: string,
setupDetailsState: CredentialsPayload,
inboxName: string,
inboxId: string
// TODO: fix type
): Promise<any> => {

Check warning on line 244 in libs/shinkai-message-ts/src/api/methods.ts

View workflow job for this annotation

GitHub Actions / health-checks (lint)

Unexpected any. Specify a different type
try {
const messageString =
ShinkaiMessageBuilderWrapper.update_shinkai_inbox_name(
setupDetailsState.profile_encryption_sk,
setupDetailsState.profile_identity_sk,
setupDetailsState.node_encryption_pk,
sender,
sender_subidentity,
receiver,
"",
inboxId,
inboxName
);

const message = JSON.parse(messageString);

const apiEndpoint = ApiConfig.getInstance().getEndpoint();
const response = await fetch(`${apiEndpoint}/v1/update_smart_inbox_name`, {
method: 'POST',
body: JSON.stringify(message),
headers: { 'Content-Type': 'application/json' },
});
const data = await response.json();
await handleHttpError(response);
return data.data;
} catch (error) {
console.error('Error updating inbox name:', error);
throw error;
}
};

export const getLastMessagesFromInbox = async (
inbox: string,
count: number,
lastKey: string | undefined,
setupDetailsState: LastMessagesFromInboxCredentialsPayload
): Promise<any[]> => {

Check warning on line 281 in libs/shinkai-message-ts/src/api/methods.ts

View workflow job for this annotation

GitHub Actions / health-checks (lint)

Unexpected any. Specify a different type
try {
const messageStr =
ShinkaiMessageBuilderWrapper.get_last_messages_from_inbox(
Expand Down Expand Up @@ -272,7 +315,7 @@
count: number,
fromKey: string | undefined,
setupDetailsState: LastMessagesFromInboxCredentialsPayload
): Promise<any[]> => {

Check warning on line 318 in libs/shinkai-message-ts/src/api/methods.ts

View workflow job for this annotation

GitHub Actions / health-checks (lint)

Unexpected any. Specify a different type
try {
const messageStr =
ShinkaiMessageBuilderWrapper.get_last_messages_from_inbox(
Expand Down Expand Up @@ -448,7 +491,7 @@
};

export const createJob = async (
scope: any,

Check warning on line 494 in libs/shinkai-message-ts/src/api/methods.ts

View workflow job for this annotation

GitHub Actions / health-checks (lint)

Unexpected any. Specify a different type
sender: string,
sender_subidentity: string,
receiver: string,
Expand Down
6 changes: 6 additions & 0 deletions libs/shinkai-message-ts/src/models/ShinkaiMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,9 @@ export interface RegistrationCode {
encryptionPk: string;
permissionType: string;
}

export type SmartInbox = {
custom_name: string;
inbox_id: string;
last_message: ShinkaiMessage;
};
Loading
Loading