[FEAT] Rename new threads on thread creation (#1607)

* make thread name 'Thread' when new thread is created

* implement auto generated thread titles

* implement truncated prompt as thread name

* move rename of thread into workspaceThread function

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
Sean Hatfield 2024-06-07 14:06:47 -07:00 committed by GitHub
parent 13da9cb396
commit 3c98d15c6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 72 additions and 13 deletions

View File

@ -27,7 +27,6 @@ export default function ThreadItem({
const { slug } = useParams(); const { slug } = useParams();
const optionsContainer = useRef(null); const optionsContainer = useRef(null);
const [showOptions, setShowOptions] = useState(false); const [showOptions, setShowOptions] = useState(false);
const [name, setName] = useState(thread.name);
const linkTo = !thread.slug const linkTo = !thread.slug
? paths.workspace.chat(slug) ? paths.workspace.chat(slug)
: paths.workspace.thread(slug, thread.slug); : paths.workspace.thread(slug, thread.slug);
@ -97,7 +96,7 @@ export default function ThreadItem({
isActive ? "font-medium text-white" : "text-slate-400" isActive ? "font-medium text-white" : "text-slate-400"
}`} }`}
> >
{truncate(name, 25)} {truncate(thread.name, 25)}
</p> </p>
</a> </a>
)} )}
@ -133,7 +132,6 @@ export default function ThreadItem({
workspace={workspace} workspace={workspace}
thread={thread} thread={thread}
onRemove={onRemove} onRemove={onRemove}
onRename={setName}
close={() => setShowOptions(false)} close={() => setShowOptions(false)}
/> />
)} )}
@ -144,14 +142,7 @@ export default function ThreadItem({
); );
} }
function OptionsMenu({ function OptionsMenu({ containerRef, workspace, thread, onRemove, close }) {
containerRef,
workspace,
thread,
onRename,
onRemove,
close,
}) {
const menuRef = useRef(null); const menuRef = useRef(null);
// Ref menu options // Ref menu options
@ -208,7 +199,7 @@ function OptionsMenu({
return; return;
} }
onRename(name); thread.name = name;
close(); close();
}; };

View File

@ -12,6 +12,26 @@ export default function ThreadContainer({ workspace }) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [ctrlPressed, setCtrlPressed] = useState(false); const [ctrlPressed, setCtrlPressed] = useState(false);
useEffect(() => {
const chatHandler = (event) => {
const { threadSlug, newName } = event.detail;
setThreads((prevThreads) =>
prevThreads.map((thread) => {
if (thread.slug === threadSlug) {
return { ...thread, name: newName };
}
return thread;
})
);
};
window.addEventListener("renameThread", chatHandler);
return () => {
window.removeEventListener("renameThread", chatHandler);
};
}, []);
useEffect(() => { useEffect(() => {
async function fetchThreads() { async function fetchThreads() {
if (!workspace.slug) return; if (!workspace.slug) return;

View File

@ -12,6 +12,7 @@ import handleSocketResponse, {
AGENT_SESSION_END, AGENT_SESSION_END,
AGENT_SESSION_START, AGENT_SESSION_START,
} from "@/utils/chat/agent"; } from "@/utils/chat/agent";
import truncate from "truncate";
export default function ChatContainer({ workspace, knownHistory = [] }) { export default function ChatContainer({ workspace, knownHistory = [] }) {
const { threadSlug = null } = useParams(); const { threadSlug = null } = useParams();
@ -39,6 +40,18 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
event.preventDefault(); event.preventDefault();
if (!message || message === "") return false; if (!message || message === "") return false;
// If first message and it is a thread
// and message is not blank/whitespace,
// then send event to rename the thread
if (threadSlug && chatHistory.length === 0 && message.trim().length > 0) {
const truncatedName = truncate(message, 22);
window.dispatchEvent(
new CustomEvent("renameThread", {
detail: { threadSlug, newName: truncatedName },
})
);
}
const prevChatHistory = [ const prevChatHistory = [
...chatHistory, ...chatHistory,
{ content: message, role: "user" }, { content: message, role: "user" },

View File

@ -15,6 +15,8 @@ const {
validWorkspaceSlug, validWorkspaceSlug,
} = require("../utils/middleware/validWorkspace"); } = require("../utils/middleware/validWorkspace");
const { writeResponseChunk } = require("../utils/helpers/chat/responses"); const { writeResponseChunk } = require("../utils/helpers/chat/responses");
const { WorkspaceThread } = require("../models/workspaceThread");
const truncate = require("truncate");
function chatEndpoints(app) { function chatEndpoints(app) {
if (!app) return; if (!app) return;
@ -196,6 +198,14 @@ function chatEndpoints(app) {
user, user,
thread thread
); );
await WorkspaceThread.autoRenameThread({
thread,
workspace,
user,
newName: truncate(message, 22),
});
await Telemetry.sendTelemetry("sent_chat", { await Telemetry.sendTelemetry("sent_chat", {
multiUserMode: multiUserMode(response), multiUserMode: multiUserMode(response),
LLMSelection: process.env.LLM_PROVIDER || "openai", LLMSelection: process.env.LLM_PROVIDER || "openai",

View File

@ -8,7 +8,7 @@ const WorkspaceThread = {
try { try {
const thread = await prisma.workspace_threads.create({ const thread = await prisma.workspace_threads.create({
data: { data: {
name: "New thread", name: "Thread",
slug: uuidv4(), slug: uuidv4(),
user_id: userId ? Number(userId) : null, user_id: userId ? Number(userId) : null,
workspace_id: workspace.id, workspace_id: workspace.id,
@ -84,6 +84,25 @@ const WorkspaceThread = {
return []; return [];
} }
}, },
// Will fire on first message (included or not) for a thread and rename the thread with the newName prop.
autoRenameThread: async function ({
workspace = null,
thread = null,
user = null,
newName = null,
}) {
if (!workspace || !thread || !newName) return false;
const { WorkspaceChats } = require("./workspaceChats");
const chatCount = await WorkspaceChats.count({
workspaceId: workspace.id,
user_id: user?.id || null,
thread_id: thread.id,
});
if (chatCount !== 1) return false;
await this.update(thread, { name: newName });
return true;
},
}; };
module.exports = { WorkspaceThread }; module.exports = { WorkspaceThread };

View File

@ -75,6 +75,7 @@
"sqlite3": "^5.1.6", "sqlite3": "^5.1.6",
"swagger-autogen": "^2.23.5", "swagger-autogen": "^2.23.5",
"swagger-ui-express": "^5.0.0", "swagger-ui-express": "^5.0.0",
"truncate": "^3.0.0",
"url-pattern": "^1.0.3", "url-pattern": "^1.0.3",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"uuid-apikey": "^1.5.3", "uuid-apikey": "^1.5.3",

View File

@ -6211,6 +6211,11 @@ triple-beam@^1.3.0:
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984"
integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==
truncate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/truncate/-/truncate-3.0.0.tgz#7dbe19e2f72c614e36b79bab00fbfbeb1cbaf078"
integrity sha512-C+0Xojw7wZPl6MDq5UjMTuxZvBPK04mtdFet7k+GSZPINcvLZFCXg+15kWIL4wAqDB7CksIsKiRLbQ1wa7rKdw==
tslib@^2.2.0, tslib@^2.4.0, tslib@^2.5.3, tslib@^2.6.2: tslib@^2.2.0, tslib@^2.4.0, tslib@^2.5.3, tslib@^2.6.2:
version "2.6.2" version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"