Skip to content

Commit

Permalink
ChatLink: move to /link/chat, update DB, cleanups
Browse files Browse the repository at this point in the history
  • Loading branch information
enricoros committed Oct 18, 2023
1 parent 2d0ec4d commit 989b446
Show file tree
Hide file tree
Showing 16 changed files with 217 additions and 185 deletions.
18 changes: 18 additions & 0 deletions pages/link/chat/[linkId].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import { useRouter } from 'next/router';

import { AppChatLink } from '../../../src/apps/link/AppChatLink';

import { AppLayout } from '~/common/layout/AppLayout';


export default function ChatLinkPage() {
const { query } = useRouter();
const linkId = query?.linkId as string ?? '';

return (
<AppLayout suspendAutoModelsSetup>
<AppChatLink linkId={linkId} />
</AppLayout>
);
}
14 changes: 0 additions & 14 deletions pages/shared/[sharedId].tsx

This file was deleted.

24 changes: 17 additions & 7 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ datasource db {
directUrl = env("POSTGRES_URL_NON_POOLING") // uses a direct connection
}

model Sharing {
//
// Storage of Linked Data
//
model LinkStorage {
id String @id @default(uuid())
ownerId String
isPublic Boolean @default(true)
ownerId String
visibility LinkStorageVisibility
dataType SharingDataType
dataSize Int
data Json
dataType LinkStorageDataType
dataTitle String?
dataSize Int
data Json
upVotes Int @default(0)
downVotes Int @default(0)
Expand All @@ -48,6 +52,12 @@ model Sharing {
updatedAt DateTime @updatedAt
}

enum SharingDataType {
enum LinkStorageVisibility {
PUBLIC
UNLISTED
PRIVATE
}

enum LinkStorageDataType {
CHAT_V1
}
4 changes: 2 additions & 2 deletions src/apps/chat/trade/TradeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Divider } from '@mui/joy';

import { GoodModal } from '~/common/components/GoodModal';

import { ExportConfig, ExportChats } from './ExportChats';
import { ImportConfig, ImportConversations } from './ImportChats';
import { ExportConfig, ExportChats } from './export/ExportChats';
import { ImportConfig, ImportConversations } from './import/ImportChats';

export type TradeConfig = ImportConfig | ExportConfig;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { Brand } from '~/common/brand';
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
import { Link } from '~/common/components/Link';
import { apiAsyncNode } from '~/common/util/trpc.client';
import { useChatStore } from '~/common/state/store-chats';
import { conversationTitle, useChatStore } from '~/common/state/store-chats';
import { useUICounter, useUIPreferencesStore } from '~/common/state/store-ui';

import type { PublishedSchema, SharePutSchema } from './server/trade.router';
import { ExportPublishedModal } from './ExportPublishedModal';
import { ExportSharedModal } from './ExportSharedModal';
import { conversationToJsonV1, conversationToMarkdown, downloadAllConversationsJson, downloadConversationJson } from './trade.client';
import type { PublishedSchema, StoragePutSchema } from '../server/trade.router';

import { conversationToJsonV1, conversationToMarkdown, downloadAllConversationsJson, downloadConversationJson } from '../trade.client';

import { ExportedChatLink } from './ExportedChatLink';
import { ExportedPublish } from './ExportedPublish';


// global flag to enable/disable the sharing mechanics
Expand All @@ -43,52 +45,52 @@ function findConversation(conversationId: string) {

/**
* Export Buttons and functionality
* Supports Share locally, Pulish to Paste.gg and Download in own format
*/
export function ExportChats(props: { config: ExportConfig, onClose: () => void }) {

// state
const [downloadedState, setDownloadedState] = React.useState<'ok' | 'fail' | null>(null);
const [downloadedAllState, setDownloadedAllState] = React.useState<'ok' | 'fail' | null>(null);
const [shareConversationId, setShareConversationId] = React.useState<string | null>(null);
const [shareUploading, setShareUploading] = React.useState(false);
const [shareResponse, setShareResponse] = React.useState<SharePutSchema | null>(null);
const [chatLinkConfirmId, setChatLinkConfirmId] = React.useState<string | null>(null);
const [chatLinkUploading, setChatLinkUploading] = React.useState(false);
const [chatLinkResponse, setChatLinkResponse] = React.useState<StoragePutSchema | null>(null);
const [publishConversationId, setPublishConversationId] = React.useState<string | null>(null);
const [publishUploading, setPublishUploading] = React.useState(false);
const [publishResponse, setPublishResponse] = React.useState<PublishedSchema | null>(null);

// external state
const { novel: shareWebBadge, touch: shareWebTouch } = useUICounter('share-web');
const { novel: chatLinkBadge, touch: clearChatLinkBadge } = useUICounter('share-chat-link');


// share
// chat link

const handleShareConversation = () => setShareConversationId(props.config.conversationId);
const handleChatLinkCreate = () => setChatLinkConfirmId(props.config.conversationId);

const handleConfirmedShare = async () => {
if (!shareConversationId) return;
const handleChatLinkConfirmed = async () => {
if (!chatLinkConfirmId) return;

const conversation = findConversation(shareConversationId);
setShareConversationId(null);
const conversation = findConversation(chatLinkConfirmId);
setChatLinkConfirmId(null);
if (!conversation) return;

setShareUploading(true);
setChatLinkUploading(true);
try {
const chatV1 = conversationToJsonV1(conversation);
const response: SharePutSchema = await apiAsyncNode.trade.sharePut.mutate({
const response: StoragePutSchema = await apiAsyncNode.trade.storagePut.mutate({
ownerId: undefined, // TODO: save owner id and reuse every time
dataType: 'CHAT_V1',
dataTitle: conversationTitle(conversation) || undefined,
dataObject: chatV1,
});
setShareResponse(response);
shareWebTouch();
setChatLinkResponse(response);
clearChatLinkBadge();
} catch (error: any) {
setShareResponse({
setChatLinkResponse({
type: 'error',
error: error?.message ?? error?.toString() ?? 'unknown error',
});
}
setShareUploading(false);
setChatLinkUploading(false);
};


Expand Down Expand Up @@ -154,16 +156,18 @@ export function ExportChats(props: { config: ExportConfig, onClose: () => void }
Share or download this conversation
</Typography>

{ENABLE_SHARING && <Badge color='danger' invisible={!shareWebBadge}>
<Button variant='soft' disabled={!hasConversation || shareUploading}
loading={shareUploading}
color={shareResponse ? 'success' : 'primary'}
endDecorator={shareResponse ? <DoneIcon /> : <IosShareIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleShareConversation}>
Share on {Brand.Title.Base}
</Button>
</Badge>}
{ENABLE_SHARING && (
<Badge color='danger' invisible={!chatLinkBadge}>
<Button variant='soft' disabled={!hasConversation || chatLinkUploading}
loading={chatLinkUploading}
color={chatLinkResponse ? 'success' : 'primary'}
endDecorator={chatLinkResponse ? <DoneIcon /> : <IosShareIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleChatLinkCreate}>
Share on {Brand.Title.Base}
</Button>
</Badge>
)}

<Button variant='soft' disabled={!hasConversation || publishUploading}
loading={publishUploading}
Expand Down Expand Up @@ -198,36 +202,44 @@ export function ExportChats(props: { config: ExportConfig, onClose: () => void }
</Button>
</Box>

{/* [share] confirmation */}
{ENABLE_SHARING && shareConversationId && (

{/* [chat link] confirmation */}
{ENABLE_SHARING && !!chatLinkConfirmId && (
<ConfirmationModal
open onClose={() => setShareConversationId(null)} onPositive={handleConfirmedShare}
open onClose={() => setChatLinkConfirmId(null)} onPositive={handleChatLinkConfirmed}
title='Upload Confirmation'
confirmationText={<>
Everyone with the link will be able to see this chat.
Everyone who has the unlisted link will be able to access this chat.
It will be automatically deleted after 30 days.
For more information, please see the <Link href={Brand.URIs.PrivacyPolicy} target='_blank'>privacy
policy</Link> of this server. <br />
Are you sure you want to proceed?
</>} positiveActionText={'Yes, create shared link'}
Do you wish to continue?
</>} positiveActionText={'Yes, Create Link'}
/>
)}

{/* [share] outcome */}
{!!shareResponse && <ExportSharedModal open onClose={() => setShareResponse(null)} response={shareResponse} />}
{/* [chat link] response */}
{ENABLE_SHARING && !!chatLinkResponse && (
<ExportedChatLink open onClose={() => setChatLinkResponse(null)} response={chatLinkResponse} />
)}


{/* [publish] confirmation */}
{publishConversationId && <ConfirmationModal
open onClose={() => setPublishConversationId(null)} onPositive={handlePublishConfirmed}
confirmationText={<>
Share your conversation anonymously on <Link href='https://paste.gg' target='_blank'>paste.gg</Link>?
It will be unlisted and available to share and read for 30 days. Keep in mind, deletion may not be possible.
Are you sure you want to proceed?
</>} positiveActionText={'Understood, upload to paste.gg'}
/>}

{/* [publish] outcome */}
{!!publishResponse && <ExportPublishedModal open onClose={handlePublishResponseClosed} response={publishResponse} />}
{publishConversationId && (
<ConfirmationModal
open onClose={() => setPublishConversationId(null)} onPositive={handlePublishConfirmed}
confirmationText={<>
Share your conversation anonymously on <Link href='https://paste.gg' target='_blank'>paste.gg</Link>?
It will be unlisted and available to share and read for 30 days. Keep in mind, deletion may not be possible.
Do you wish to continue?
</>} positiveActionText={'Understood, Upload to Paste.gg'}
/>
)}

{/* [publish] response */}
{!!publishResponse && (
<ExportedPublish open onClose={handlePublishResponseClosed} response={publishResponse} />
)}

</>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ import { InlineError } from '~/common/components/InlineError';
import { Link } from '~/common/components/Link';
import { apiAsyncNode } from '~/common/util/trpc.client';
import { copyToClipboard } from '~/common/util/copyToClipboard';
import { getChatLinkRelativePath } from '~/common/routes';
import { getOriginUrl } from '~/common/util/urlUtils';
import { getSharingRelativePath } from '~/common/routes';
import { webShare, webSharePresent } from '~/common/util/pwaUtils';

import { type ShareDeleteSchema, type SharePutSchema } from './server/trade.router';
import { type StorageDeleteSchema, type StoragePutSchema } from '../server/trade.router';


export function ExportSharedModal(props: { onClose: () => void, response: SharePutSchema, open: boolean }) {
export function ExportedChatLink(props: { onClose: () => void, response: StoragePutSchema, open: boolean }) {

// state
const [opened, setOpened] = React.useState(false);
const [copied, setCopied] = React.useState(false);
const [shared, setShared] = React.useState(false);
const [confirmDeletion, setConfirmDeletion] = React.useState(false);
const [deletionResponse, setDeletionResponse] = React.useState<ShareDeleteSchema | null>(null);
const [deletionResponse, setDeletionResponse] = React.useState<StorageDeleteSchema | null>(null);

// in case of 'put' error, just display the message
if (props.response.type === 'error') {
Expand All @@ -41,8 +41,8 @@ export function ExportSharedModal(props: { onClose: () => void, response: ShareP
}

// success
const { sharedId, deletionKey, expiresAt } = props.response;
const relativeUrl = getSharingRelativePath(sharedId);
const { objectId, deletionKey, expiresAt } = props.response;
const relativeUrl = getChatLinkRelativePath(objectId);
const fullUrl = getOriginUrl() + relativeUrl;


Expand All @@ -53,7 +53,7 @@ export function ExportSharedModal(props: { onClose: () => void, response: ShareP
setCopied(true);
};

const onShare = async () => webShare(Brand.Title.Common, 'Check out this chat!', fullUrl,
const onShare = async () => webShare(Brand.Title.Base, 'Check out this chat!', fullUrl,
() => setShared(true));


Expand All @@ -62,7 +62,7 @@ export function ExportSharedModal(props: { onClose: () => void, response: ShareP
const onDeleteCancelled = () => setConfirmDeletion(false);

const onConfirmedDeletion = async () => {
const result: ShareDeleteSchema = await apiAsyncNode.trade.shareDelete.mutate({ sharedId, deletionKey });
const result: StorageDeleteSchema = await apiAsyncNode.trade.storageDelete.mutate({ objectId, deletionKey });
setDeletionResponse(result);
setConfirmDeletion(false);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { Alert, Box, Button, Divider, Input, Modal, ModalDialog, Stack, Typograp

import { Link } from '~/common/components/Link';

import type { PublishedSchema } from './server/trade.router';
import type { PublishedSchema } from '../server/trade.router';


/**
* Displays the result of a Paste.gg paste as a modal dialog.
* This is to give the user the chance to write down the deletion key, mainly.
*/
export function ExportPublishedModal(props: { onClose: () => void, response: PublishedSchema, open: boolean }) {
export function ExportedPublish(props: { onClose: () => void, response: PublishedSchema, open: boolean }) {
if (!props.response || !props.response.url)
return null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import { InlineError } from '~/common/components/InlineError';
import { OpenAIIcon } from '~/common/components/icons/OpenAIIcon';
import { createDConversation, createDMessage, DMessage, useChatStore } from '~/common/state/store-chats';

import type { ChatGptSharedChatSchema } from './server/import.chatgpt';
import type { ChatGptSharedChatSchema } from '../server/import.chatgpt';
import { loadAllConversationsFromJson } from '../trade.client';

import { ImportedOutcome, ImportOutcomeModal } from './ImportOutcomeModal';
import { loadAllConversationsFromJson } from './trade.client';


export type ImportConfig = { dir: 'import' };
Expand Down
File renamed without changes.
Loading

0 comments on commit 989b446

Please sign in to comment.