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

Show hottest convo #28

Merged
merged 23 commits into from
Jul 13, 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
11 changes: 9 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@
}
},
{
"type": "chrome",
"type": "msedge",
"request": "launch",
"name": "Debug Client (chrome)",
"name": "Debug Client (Edge)",
"url": "http://localhost:3080",
"webRoot": "${workspaceFolder}"
},
{
"type": "msedge",
"request": "launch",
"name": "Debug Client (Vite)",
"url": "http://localhost:3090",
"webRoot": "${workspaceFolder}"
}
]
}
11 changes: 11 additions & 0 deletions api/models/Conversation.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,16 @@ module.exports = {
let deleteCount = await Conversation.deleteMany({ ...filter, user }).exec();
deleteCount.messages = await deleteMessages({ conversationId: { $in: ids } });
return deleteCount;
},
getRecentConvos: async (userId) => {
try {
return await Conversation.find({
user: { $ne: userId },
isPrivate: {$eq: false}
}).sort( {updatedAt: -1} ).limit(3).exec();
} catch (error) {
console.log(error);
return { message: 'Error fetching recent conversations' };
}
}
};
40 changes: 40 additions & 0 deletions api/models/Message.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const crypto = require('crypto');
const Message = require('./schema/messageSchema');

module.exports = {
Expand Down Expand Up @@ -86,6 +87,45 @@ module.exports = {
}
},

async getRecentMessages() {
try {
return await Message.find().sort( {createdAt: -1} ).select('conversationId').limit(30).exec();
} catch (err) {
console.error(`Error fetching recents messages: ${err}`);
throw new Error('Failed to fetch recent messages.');
}
},

async duplicateMessages({ newConversationId, msgData }) {
try {
let newParentMessageId = "00000000-0000-0000-0000-000000000000";
let newMessageId = crypto.randomUUID();
const msgObjIds = [];

for (let i = 0; i < msgData.length; i++) {
let msgObj = structuredClone(msgData[i]);

delete msgObj._id;
msgObj.messageId = newMessageId;
msgObj.parentMessageId = newParentMessageId;
msgObj.conversationId = newConversationId;

newParentMessageId = newMessageId;
newMessageId = crypto.randomUUID();

const newMsg = new Message(msgObj);
const result = await newMsg.save();
msgObjIds.push(result.id);
}

return msgObjIds;
} catch (err) {
console.error(`Error duplicating messages: ${err}`);
throw new Error('Failed to duplicate messages.');
}

},

async getMessagesCount(filter) {
try {
return await Message.countDocuments(filter);
Expand Down
4 changes: 4 additions & 0 deletions api/models/schema/convoSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ const convoSchema = mongoose.Schema(
invocationId: {
type: Number,
default: 1
},
isPrivate: {
type: Boolean,
default: true
}
},
{ timestamps: true }
Expand Down
39 changes: 38 additions & 1 deletion api/server/routes/convos.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
const express = require('express');
const router = express.Router();
const { getConvo, saveConvo } = require('../../models');
const { getConvosByPage, deleteConvos } = require('../../models/Conversation');
const { getConvosByPage, deleteConvos, getRecentConvos } = require('../../models/Conversation');
const requireJwtAuth = require('../../middleware/requireJwtAuth');
const { duplicateMessages } = require('../../models/Message');
const crypto = require('crypto');
const Conversation = require('../../models/schema/convoSchema');

router.get('/', requireJwtAuth, async (req, res) => {
const pageNumber = req.query.pageNumber || 1;
res.status(200).send(await getConvosByPage(req.user.id, pageNumber));
});

router.get('/recent', requireJwtAuth, async (req, res) => {
try {
const userId = req.user.id;
const recentConvos = await getRecentConvos(userId);
res.status(200).send(recentConvos);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});

router.get('/:conversationId', requireJwtAuth, async (req, res) => {
const { conversationId } = req.params;
const convo = await getConvo(req.user.id, conversationId);
Expand Down Expand Up @@ -51,4 +65,27 @@ router.post('/update', requireJwtAuth, async (req, res) => {
}
});

router.post('/duplicate', requireJwtAuth, async (req, res) => {
const { conversation, msgData } = req.body.arg;

const newConversationId = crypto.randomUUID();

try {
let convoObj = structuredClone(conversation);

delete convoObj._id;
convoObj.user = req.user.id;
convoObj.conversationId = newConversationId;
convoObj.isPrivate = true;
convoObj.messages = await duplicateMessages({ newConversationId, msgData });

const newConvo = new Conversation(convoObj);
const dbResponse = await newConvo.save();
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});

module.exports = router;
16 changes: 15 additions & 1 deletion client/src/components/Conversations/Conversation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import DeleteButton from './DeleteButton';
import ConvoIcon from '../svg/ConvoIcon';

import store from '~/store';
import PrivateButton from './PrivateButton';

export default function Conversation({ conversation, retainView }) {
const [currentConversation, setCurrentConversation] = useRecoilState(store.conversation);
Expand All @@ -19,10 +20,12 @@ export default function Conversation({ conversation, retainView }) {
const [renaming, setRenaming] = useState(false);
const inputRef = useRef(null);

const { conversationId, title } = conversation;
const { conversationId, title, isPrivate } = conversation;

const [titleInput, setTitleInput] = useState(title);

const [privateState, setPrivateState] = useState(isPrivate);

const clickHandler = async () => {
if (currentConversation?.conversationId === conversationId) {
return;
Expand All @@ -43,6 +46,12 @@ export default function Conversation({ conversation, retainView }) {
}
};

const setPrivateHandler = (e) => {
e.preventDefault();
updateConvoMutation.mutate({ conversationId, isPrivate: !privateState });
setPrivateState(!privateState);
}

const renameHandler = (e) => {
e.preventDefault();
setTitleInput(title);
Expand Down Expand Up @@ -115,6 +124,11 @@ export default function Conversation({ conversation, retainView }) {
</div>
{currentConversation?.conversationId === conversationId ? (
<div className="visible absolute right-1 z-10 flex text-gray-300">
<PrivateButton
conversationId={conversationId}
isPrivate={privateState}
setPrivateHandler={setPrivateHandler}
/>
<RenameButton
conversationId={conversationId}
renaming={renaming}
Expand Down
16 changes: 16 additions & 0 deletions client/src/components/Conversations/PrivateButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import EyeIcon from '../svg/EyeIcon';
import CrossedEyeIcon from '../svg/CrossedEyeIcon';

export default function PrivateButton({ isPrivate, setPrivateHandler, twcss }) {
const classProp = { className: 'p-1 hover:text-white' };
const title = `将对话设置为${isPrivate ? '公开' : '私密'}。\n\n私密:其他用户不会看到此对话。\n公开:其他用户有可能在首页看到此对话,并且可以在现有对话内容的基础下创建一个新对话。`;
if (twcss) {
classProp.className = twcss;
}
return (
<button {...classProp} title={title} onClick={setPrivateHandler}>
{isPrivate ? <CrossedEyeIcon /> : <EyeIcon />}
</button>
);
}
2 changes: 1 addition & 1 deletion client/src/components/Input/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function Footer() {
const { data: config } = useGetStartupConfig();
return (
<div className="hidden px-3 pb-1 pt-2 text-center text-xs text-black/50 dark:text-white/50 md:block md:px-4 md:pb-4 md:pt-3">
{config?.appTitle || 'AITok Chat'}
{config?.appTitle || 'AITok Chat'}
</div>
);
}
7 changes: 5 additions & 2 deletions client/src/components/Messages/Message.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export default function Message({
setCurrentEditId,
siblingIdx,
siblingCount,
setSiblingIdx
setSiblingIdx,
hideUser = false
}) {
const { text, searchResult, isCreatedByUser, error, submitting, unfinished } = message;
const isSubmitting = useRecoilValue(store.isSubmitting);
Expand Down Expand Up @@ -75,7 +76,8 @@ export default function Message({
const icon = getIcon({
...conversation,
...message,
model: message?.model || conversation?.model
model: message?.model || conversation?.model,
hideUser
});

if (!isCreatedByUser)
Expand Down Expand Up @@ -242,6 +244,7 @@ export default function Message({
scrollToBottom={scrollToBottom}
currentEditId={currentEditId}
setCurrentEditId={setCurrentEditId}
hideUser={hideUser}
/>
</>
);
Expand Down
5 changes: 4 additions & 1 deletion client/src/components/Messages/MultiMessage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export default function MultiMessage({
scrollToBottom,
currentEditId,
setCurrentEditId,
isSearchView
isSearchView,
hideUser = false
}) {
// const [siblingIdx, setSiblingIdx] = useState(0);

Expand Down Expand Up @@ -52,6 +53,7 @@ export default function MultiMessage({
siblingIdx={1}
siblingCount={1}
setSiblingIdx={null}
hideUser={hideUser}
/>
))
: null}
Expand All @@ -68,6 +70,7 @@ export default function MultiMessage({
siblingIdx={messagesTree.length - siblingIdx - 1}
siblingCount={messagesTree.length}
setSiblingIdx={setSiblingIdxRev}
hideUser={hideUser}
/>
);
}
12 changes: 12 additions & 0 deletions client/src/components/svg/CrossedEyeIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

export default function CrossedEyeIcon() {
return (
<svg stroke="currentColor" width="1em" height="1em" strokeWidth="2" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M5.70711 19.7071L19.7071 5.70711C20.0976 5.31658 20.0976 4.68342 19.7071 4.29289C19.3166 3.90237 18.6834 3.90237 18.2929 4.29289L4.29289 18.2929C3.90237 18.6834 3.90237 19.3166 4.29289 19.7071C4.68342 20.0976 5.31658 20.0976 5.70711 19.7071Z" fill="#000000"/>
<path d="M12 5C13.2011 5 14.394 5.21361 15.5362 5.63535L13.9368 7.23482C13.2953 7.0777 12.6458 7 12 7C9.07319 7 6.06862 8.59614 4.09173 11.9487C4.74631 13.0987 5.52178 14.046 6.37447 14.7971L4.95845 16.2131C3.88666 15.248 2.93477 14.037 2.16029 12.5876C1.94361 12.1821 1.94637 11.6844 2.17003 11.2807C4.45796 7.15186 8.18777 5 12 5Z" fill="#000000"/>
<path d="M12 9C12.056 9 12.1117 9.00154 12.167 9.00457L9.00457 12.167C9.00154 12.1117 9 12.056 9 12C9 10.3431 10.3431 9 12 9Z" fill="#000000"/>
<path d="M14.9954 11.833L11.833 14.9954C11.8883 14.9985 11.944 15 12 15C13.6569 15 15 13.6569 15 12C15 11.944 14.9985 11.8883 14.9954 11.833Z" fill="#000000"/>
<path d="M12 17C11.355 17 10.7061 16.9216 10.0654 16.763L8.46807 18.3604C9.60812 18.7849 10.7998 19 12 19C15.8372 19 19.5882 16.8013 21.8397 12.5876C22.0564 12.1821 22.0536 11.6844 21.83 11.2807C21.0543 9.88089 20.1128 8.7083 19.0587 7.76977L17.6421 9.18635C18.4837 9.91776 19.2525 10.8366 19.9083 11.9487C17.9595 15.3724 14.939 17 12 17Z" fill="#000000"/>
</svg>
);
}
9 changes: 9 additions & 0 deletions client/src/components/svg/EyeIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

export default function EyeIcon() {
return (
<svg stroke="currentColor" width="1em" height="1em" strokeWidth="2" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M6.30147 15.5771C4.77832 14.2684 3.6904 12.7726 3.18002 12C3.6904 11.2274 4.77832 9.73158 6.30147 8.42294C7.87402 7.07185 9.81574 6 12 6C14.1843 6 16.1261 7.07185 17.6986 8.42294C19.2218 9.73158 20.3097 11.2274 20.8201 12C20.3097 12.7726 19.2218 14.2684 17.6986 15.5771C16.1261 16.9282 14.1843 18 12 18C9.81574 18 7.87402 16.9282 6.30147 15.5771ZM12 4C9.14754 4 6.75717 5.39462 4.99812 6.90595C3.23268 8.42276 2.00757 10.1376 1.46387 10.9698C1.05306 11.5985 1.05306 12.4015 1.46387 13.0302C2.00757 13.8624 3.23268 15.5772 4.99812 17.0941C6.75717 18.6054 9.14754 20 12 20C14.8525 20 17.2429 18.6054 19.002 17.0941C20.7674 15.5772 21.9925 13.8624 22.5362 13.0302C22.947 12.4015 22.947 11.5985 22.5362 10.9698C21.9925 10.1376 20.7674 8.42276 19.002 6.90595C17.2429 5.39462 14.8525 4 12 4ZM10 12C10 10.8954 10.8955 10 12 10C13.1046 10 14 10.8954 14 12C14 13.1046 13.1046 14 12 14C10.8955 14 10 13.1046 10 12ZM12 8C9.7909 8 8.00004 9.79086 8.00004 12C8.00004 14.2091 9.7909 16 12 16C14.2092 16 16 14.2091 16 12C16 9.79086 14.2092 8 12 8Z" fill="#000000"/>
</svg>
);
}
12 changes: 12 additions & 0 deletions client/src/components/ui/DuplicateConvoButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

export default function DuplicateConvoButton({ duplicateHandler }) {
return (
<button
onClick={duplicateHandler}
className="absolute bottom-[124px] right-6 z-10 cursor-pointer rounded-full border border-gray-200 bg-gray-50 text-gray-600 dark:border-white/10 dark:bg-white/10 dark:text-gray-200 md:bottom-[120px]"
>
拷贝对话
</button>
);
}
Loading