diff --git a/src/components/chat/ChatInterface.vue b/src/components/chat/ChatInterface.vue
index c90a130..4722481 100644
--- a/src/components/chat/ChatInterface.vue
+++ b/src/components/chat/ChatInterface.vue
@@ -35,6 +35,8 @@
@create-collection="showCreateCollectionModal = true"
@delete-session="showDeleteSessionDialog"
@delete-collection="promptDeleteCollection"
+ @update-session-name="handleUpdateSessionName"
+ @share-session="handleShareSession"
@agent-click="
if (!chatLoading) {
handleTagAgent($event, false);
@@ -67,6 +69,7 @@
>
@@ -180,6 +183,7 @@
+
+
+
@@ -329,6 +342,8 @@ import ConfirmModal from "../modals/ConfirmModal.vue";
import CreateCollectionModal from "../modals/CreateCollectionModal.vue";
import DeleteCollectionErrorModal from "../modals/DeleteCollectionErrorModal.vue";
import UploadModal from "../modals/UploadModal.vue";
+import ShareModal from "../modals/ShareModal.vue";
+
import Header from "./elements/Header.vue";
import ChatSearchResults from "../message-handlers/ChatSearchResults.vue";
@@ -391,6 +406,14 @@ const props = defineProps({
],
}),
},
+ showHeader: {
+ type: Boolean,
+ default: true,
+ },
+ showChatInput: {
+ type: Boolean,
+ default: true,
+ },
defaultScreenConfig: {
type: Object,
default: () => ({
@@ -466,6 +489,8 @@ const {
deleteVideo,
deleteAudio,
deleteImage,
+ renameSession,
+ makeSessionPublic,
} = useChatHook(props.chatHookConfig);
const {
@@ -531,6 +556,8 @@ const showDeleteImageDialog = ref(false);
const imageToDelete = ref(null);
const showDeleteCollectionErrorModal = ref(false);
const deleteCollectionErrorCode = ref(null);
+const showShareModal = ref(false);
+const sessionToShare = ref(null);
const isSetupComplete = computed(() => {
return (
@@ -725,6 +752,18 @@ const confirmDeleteSession = () => {
sessionToDelete.value = null;
};
+const handleUpdateSessionName = async ({ sessionId: _sessionId, name }) => {
+ try {
+ await renameSession(_sessionId, name);
+ } catch (error) {
+ console.error("Error renaming session:", error?.message || error);
+ }
+};
+const handleShareSession = (session) => {
+ sessionToShare.value = session;
+ showShareModal.value = true;
+};
+
// --- Upload Dialog Handlers ---
const showUploadDialog = ref(false);
const handleUpload = async (uploadData) => {
diff --git a/src/components/chat/elements/Sidebar.vue b/src/components/chat/elements/Sidebar.vue
index 1c21152..d4d930a 100644
--- a/src/components/chat/elements/Sidebar.vue
+++ b/src/components/chat/elements/Sidebar.vue
@@ -95,7 +95,7 @@
closeSidebar();
"
:class="[
- 'vdb-c-ml-24 vdb-c-flex vdb-c-cursor-pointer vdb-c-items-center vdb-c-justify-between vdb-c-truncate vdb-c-rounded-lg vdb-c-p-8 vdb-c-text-sm vdb-c-font-medium vdb-c-text-vdb-darkishgrey',
+ 'vdb-c-ml-24 vdb-c-flex vdb-c-cursor-pointer vdb-c-items-center vdb-c-justify-between vdb-c-truncate vdb-c-rounded-lg vdb-c-p-8 vdb-c-px-16 vdb-c-text-sm vdb-c-font-medium vdb-c-text-vdb-darkishgrey',
{
'vdb-c-bg-[#FFF5EC]':
showSelectedCollection &&
@@ -241,37 +241,125 @@
]"
>
- {{
- session.name ||
- new Date(session.created_at * 1000)
- .toLocaleString("en-US", {
- year: "numeric",
- month: "2-digit",
- day: "2-digit",
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- hour12: false,
- })
- .replace(/\//g, ".")
- .replace(",", " -")
- }}
-
-
-
+
+
+
+
+
+ {{
+ session.name ||
+ session?.metadata?.name ||
+ new Date(session.created_at * 1000)
+ .toLocaleString("en-US", {
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: false,
+ })
+ .replace(/\//g, ".")
+ .replace(",", " -")
+ }}
+
+
+
+
+
@@ -340,6 +428,12 @@ import CollectionIcon from "../../icons/Collection.vue";
import ComposeIcon from "../../icons/Compose.vue";
import DeleteIcon from "../../icons/Delete.vue";
import PlusIcon from "../../icons/Plus.vue";
+import CopyIcon from "../../icons/CopyIcon.vue";
+import CheckIcon from "../../icons/Check.vue";
+import DotVertical from "../../icons/DotVertical.vue";
+import EditIcon from "../../icons/Edit.vue";
+import ShareIcon from "../../icons/Share.vue";
+import Popper from "vue3-popper";
const props = defineProps({
sessions: {
@@ -413,6 +507,10 @@ const hoveredSession = ref(null);
const isMobile = ref(window?.innerWidth < 1024);
const isOpen = ref(false);
const hoveredCollection = ref(null);
+const editingSessionId = ref(null);
+const editingName = ref("");
+const copiedSessionId = ref(null);
+const copyFeedbackTimeout = ref(null);
const visibleSections = computed(() => {
return props.sidebarSections;
@@ -426,6 +524,8 @@ const emit = defineEmits([
"agent-click",
"create-collection",
"delete-collection",
+ "update-session-name",
+ "share-session",
]);
const closeSidebar = () => {
@@ -501,6 +601,50 @@ defineExpose({
triggerExploreAgentsFocusAnimation,
toggleSidebar,
});
+
+const startEditing = (session) => {
+ editingSessionId.value = session.session_id;
+ editingName.value = session.name || "";
+ nextTick(() => {
+ const input = document.getElementById(`edit-input-${session.session_id}`);
+ if (input) {
+ input.focus();
+ input.select();
+ }
+ });
+};
+
+const cancelEditing = () => {
+ editingSessionId.value = null;
+ editingName.value = "";
+};
+
+const saveSessionName = (session) => {
+ if (editingSessionId.value !== session.session_id) return;
+ const trimmed = (editingName.value || "").trim();
+ emit("update-session-name", { sessionId: session.session_id, name: trimmed });
+ cancelEditing();
+};
+
+const copySessionId = async (sessionId) => {
+ try {
+ await navigator.clipboard.writeText(String(sessionId));
+ copiedSessionId.value = sessionId;
+ if (copyFeedbackTimeout.value) {
+ clearTimeout(copyFeedbackTimeout.value);
+ }
+ copyFeedbackTimeout.value = setTimeout(() => {
+ copiedSessionId.value = null;
+ copyFeedbackTimeout.value = null;
+ }, 2000);
+ } catch (error) {
+ console.error("Failed to copy session ID", error);
+ }
+};
+
+const shareSession = (session) => {
+ emit("share-session", session);
+};
diff --git a/src/components/hooks/useVideoDBAgent.js b/src/components/hooks/useVideoDBAgent.js
index 4a4603b..cfb6d73 100644
--- a/src/components/hooks/useVideoDBAgent.js
+++ b/src/components/hooks/useVideoDBAgent.js
@@ -322,6 +322,76 @@ export function useVideoDBAgent(config) {
});
};
+ const renameSession = async (sessionId, name) => {
+ const trimmed = (name || "").trim();
+ if (trimmed.length === 0) {
+ throw new Error("Session name cannot be empty.");
+ }
+ try {
+ const response = await fetch(`${httpUrl}/session/${sessionId}/rename`, {
+ method: "PUT",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ name: trimmed }),
+ });
+
+ const data = await response.json();
+ if (!response.ok) {
+ const message = (data && data.message) || "Failed to rename session.";
+ throw new Error(message);
+ }
+
+ const index = sessions.value.findIndex((s) => s.session_id === sessionId);
+ if (index !== -1) {
+ sessions.value[index] = { ...sessions.value[index], name: trimmed };
+ }
+
+ return data || { success: true };
+ } catch (error) {
+ if (debug)
+ console.error("debug :videodb-chat error renaming session", error);
+ throw error;
+ }
+ };
+
+ const makeSessionPublic = async (sessionId, isPublic = true) => {
+ const res = {};
+ try {
+ const response = await fetch(`${httpUrl}/session/${sessionId}/public`, {
+ method: "PUT",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ is_public: isPublic }),
+ });
+
+ if (!response.ok) {
+ throw new Error("Network response was not ok");
+ }
+
+ const data = await response.json();
+ res.status = "success";
+ res.success = true;
+ res.data = data;
+
+ const idx = sessions.value.findIndex((s) => s.session_id === sessionId);
+ if (idx !== -1) {
+ sessions.value[idx] = {
+ ...sessions.value[idx],
+ is_public: isPublic,
+ };
+ }
+ } catch (error) {
+ res.status = "error";
+ res.success = false;
+ res.error = error.message;
+ }
+ return res;
+ };
+
const updateCollection = async () => {
try {
const res = await fetchCollections();
@@ -544,12 +614,6 @@ export function useVideoDBAgent(config) {
const addMessage = (message) => {
if (debug) console.log("debug :videodb-chat addMessage", message);
if (session.isConnected) {
- if (!sessions.value.some((s) => s.session_id === session.sessionId)) {
- sessions.value.push({
- session_id: session.sessionId,
- created_at: Date.now() / 1000,
- });
- }
const convId = Date.now();
const msgId = convId + 1;
const _message = {
@@ -566,6 +630,36 @@ export function useVideoDBAgent(config) {
...message,
};
+ if (!sessions.value.some((s) => s.session_id === session.sessionId)) {
+ const sessionData = {
+ session_id: session.sessionId,
+ message: _message,
+ created_at: Date.now(new Date()),
+ };
+ fetch(`${httpUrl}/session/${session.sessionId}`, {
+ method: "POST",
+ body: JSON.stringify(sessionData),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((res) => res.json())
+ .then((data) => {
+ sessions.value.push({
+ session_id: data.session_id,
+ created_at: data.created_at,
+ name: data.name,
+ });
+
+ sessions.value = sessions.value.sort(
+ (a, b) => b.created_at - a.created_at,
+ );
+
+ session.sessionId = data.session_id;
+ session.name = data.name;
+ });
+ }
+
conversations[convId] = { [msgId]: _message };
socket.emit("chat", _message);
addClientLoadingMessage(convId);
@@ -638,5 +732,7 @@ export function useVideoDBAgent(config) {
uploadMedia,
generateImageUrl,
generateAudioUrl,
+ makeSessionPublic,
+ renameSession,
};
}
diff --git a/src/components/icons/DotVertical.vue b/src/components/icons/DotVertical.vue
new file mode 100644
index 0000000..c15cd46
--- /dev/null
+++ b/src/components/icons/DotVertical.vue
@@ -0,0 +1,36 @@
+