diff --git a/package-lock.json b/package-lock.json
index 2124a7f..1c92b8a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@videodb/chat-vue",
- "version": "0.0.40",
+ "version": "0.0.41",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@videodb/chat-vue",
- "version": "0.0.40",
+ "version": "0.0.41",
"license": "Apache-2.0",
"dependencies": {
"@videodb/player-vue": "~0.0.6",
@@ -17,7 +17,6 @@
"prismjs": "^1.29.0",
"socket.io-client": "^4.7.5",
"swiper": "^11.1.10",
- "uuid": "^10.0.0",
"vue3-popper": "^1.5.0"
},
"devDependencies": {
@@ -4404,17 +4403,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/uuid": {
- "version": "10.0.0",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
"node_modules/video.js": {
"version": "8.19.1",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-8.19.1.tgz",
diff --git a/package.json b/package.json
index c1fd826..02c5e90 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,6 @@
"prismjs": "^1.29.0",
"socket.io-client": "^4.7.5",
"swiper": "^11.1.10",
- "uuid": "^10.0.0",
"vue3-popper": "^1.5.0"
}
}
diff --git a/src/components/canvas-handlers/meeting-recorder/AttachBottom.vue b/src/components/canvas-handlers/meeting-recorder/AttachBottom.vue
new file mode 100644
index 0000000..8e4a96e
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/AttachBottom.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/AttachLeft.vue b/src/components/canvas-handlers/meeting-recorder/AttachLeft.vue
new file mode 100644
index 0000000..4824569
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/AttachLeft.vue
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/AttachTop.vue b/src/components/canvas-handlers/meeting-recorder/AttachTop.vue
new file mode 100644
index 0000000..d48ebcf
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/AttachTop.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/Button.vue b/src/components/canvas-handlers/meeting-recorder/Button.vue
new file mode 100644
index 0000000..f52c5c8
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/Button.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/Cross.vue b/src/components/canvas-handlers/meeting-recorder/Cross.vue
new file mode 100644
index 0000000..bbae0e8
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/Cross.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/Live.vue b/src/components/canvas-handlers/meeting-recorder/Live.vue
new file mode 100644
index 0000000..0a20fc6
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/Live.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/LiveAnalysisModal.vue b/src/components/canvas-handlers/meeting-recorder/LiveAnalysisModal.vue
new file mode 100644
index 0000000..0b936e9
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/LiveAnalysisModal.vue
@@ -0,0 +1,376 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/MeetingAnalysisModal.vue b/src/components/canvas-handlers/meeting-recorder/MeetingAnalysisModal.vue
new file mode 100644
index 0000000..0b59dd8
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/MeetingAnalysisModal.vue
@@ -0,0 +1,592 @@
+
+
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/MeetingRecorderCanvas.vue b/src/components/canvas-handlers/meeting-recorder/MeetingRecorderCanvas.vue
new file mode 100644
index 0000000..958f657
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/MeetingRecorderCanvas.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/Pinned.vue b/src/components/canvas-handlers/meeting-recorder/Pinned.vue
new file mode 100644
index 0000000..011fb88
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/Pinned.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/Robot.vue b/src/components/canvas-handlers/meeting-recorder/Robot.vue
new file mode 100644
index 0000000..cb0a242
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/Robot.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/src/components/canvas-handlers/meeting-recorder/UnPinned.vue b/src/components/canvas-handlers/meeting-recorder/UnPinned.vue
new file mode 100644
index 0000000..3c96580
--- /dev/null
+++ b/src/components/canvas-handlers/meeting-recorder/UnPinned.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/src/components/chat/ChatInput.vue b/src/components/chat/ChatInput.vue
index 5f62847..6dcd8ec 100644
--- a/src/components/chat/ChatInput.vue
+++ b/src/components/chat/ChatInput.vue
@@ -119,7 +119,6 @@
@@ -1072,4 +1196,12 @@ provide("videodb-chat", {
-o-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite;
}
+
+.scrollbar-hide::-webkit-scrollbar {
+ display: none;
+}
+.scrollbar-hide {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
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/useChatInterface.js b/src/components/hooks/useChatInterface.js
index dd328ba..c6b8db0 100644
--- a/src/components/hooks/useChatInterface.js
+++ b/src/components/hooks/useChatInterface.js
@@ -3,21 +3,57 @@ import { ref, reactive } from "vue";
export function useChatInterface() {
const messageHandlers = {};
const chatInput = ref("");
- const chatAttachments = reactive([])
+ const chatAttachments = reactive([]);
+
+ // Right-side canvas state and registry
+ const canvasHandlers = {};
+ const canvasState = reactive({
+ show: false,
+ shrinkChat: false,
+ type: null,
+ content: null,
+ });
const registerMessageHandler = (contentType, handler) => {
messageHandlers[contentType] = handler;
};
+ const registerCanvasHandler = (canvasType, handler) => {
+ canvasHandlers[canvasType] = handler;
+ };
+
const setChatInput = (input) => {
chatInput.value = input;
};
+ const setShrinkChat = (shrink) => {
+ canvasState.shrinkChat = shrink;
+ };
+
+ const openCanvas = (type, content) => {
+ canvasState.show = true;
+ canvasState.type = type;
+ canvasState.content = content || null;
+ };
+
+ const closeCanvas = () => {
+ canvasState.show = false;
+ canvasState.type = null;
+ canvasState.content = null;
+ canvasState.shrinkChat = false;
+ };
+
return {
chatInput,
chatAttachments,
setChatInput,
+ setShrinkChat,
messageHandlers,
registerMessageHandler,
+ canvasHandlers,
+ registerCanvasHandler,
+ canvasState,
+ openCanvas,
+ closeCanvas,
};
}
diff --git a/src/components/hooks/useVideoDBAgent.js b/src/components/hooks/useVideoDBAgent.js
index 4a4603b..2d6d130 100644
--- a/src/components/hooks/useVideoDBAgent.js
+++ b/src/components/hooks/useVideoDBAgent.js
@@ -1,5 +1,4 @@
import io from "socket.io-client";
-import { v4 as uuidv4 } from "uuid";
import { computed, onBeforeMount, reactive, ref, toRefs, watch } from "vue";
const fetchData = async (rootUrl, endpoint) => {
@@ -137,6 +136,94 @@ export function useVideoDBAgent(config) {
return res;
};
+ const saveMeetingContext = async (msgId, context) => {
+ const res = {};
+ try {
+ const response = await fetch(
+ `${httpUrl}/session/message/${msgId}/meeting_context`,
+ {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(context),
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error("Network response was not ok");
+ }
+
+ const data = await response.json();
+ res.status = "success";
+ res.data = data;
+ } catch (error) {
+ res.status = "error";
+ res.error = error;
+ }
+ return res;
+ };
+
+ const fetchMeetingContext = async (uiId) => {
+ const res = {};
+ try {
+ const response = await fetch(
+ `${httpUrl}/session/meeting_context/${uiId}`,
+ );
+ if (response.status === 404) {
+ res.status = "not_found";
+ return res;
+ }
+ if (!response.ok) {
+ throw new Error("Network response was not ok");
+ }
+ const data = await response.json();
+ res.status = "success";
+ res.data = data;
+ } catch (error) {
+ res.status = "error";
+ res.error = error;
+ }
+ return res;
+ };
+
+ 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 refetchCollectionVideos = async () => {
fetchCollectionVideos(session.collectionId).then((res) => {
activeCollectionVideos.value = res.data;
@@ -270,7 +357,7 @@ export function useVideoDBAgent(config) {
const loadSession = (sessionId) => {
let fetchPastMessages = true;
if (!sessionId) {
- sessionId = uuidv4();
+ sessionId = crypto.randomUUID();
fetchPastMessages = false;
}
if (debug) console.log("debug :videodb-chat session loading", sessionId);
@@ -638,5 +725,8 @@ export function useVideoDBAgent(config) {
uploadMedia,
generateImageUrl,
generateAudioUrl,
+ saveMeetingContext,
+ fetchMeetingContext,
+ makeSessionPublic,
};
}
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 @@
+
+
+
+
+
+
+
diff --git a/src/components/icons/Edit.vue b/src/components/icons/Edit.vue
new file mode 100644
index 0000000..798acd2
--- /dev/null
+++ b/src/components/icons/Edit.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
diff --git a/src/components/icons/Share.vue b/src/components/icons/Share.vue
new file mode 100644
index 0000000..627fecc
--- /dev/null
+++ b/src/components/icons/Share.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
diff --git a/src/components/message-handlers/MeetingRecorder.vue b/src/components/message-handlers/MeetingRecorder.vue
new file mode 100644
index 0000000..e5a6ef9
--- /dev/null
+++ b/src/components/message-handlers/MeetingRecorder.vue
@@ -0,0 +1,360 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/modals/ShareModal.vue b/src/components/modals/ShareModal.vue
new file mode 100644
index 0000000..1e098a4
--- /dev/null
+++ b/src/components/modals/ShareModal.vue
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+ Public link created
+
+
+
+
+
+
+ Share the link with everyone to show all the cool things you made
+ {{ ":)" }}
+
+
+
+
+
+
+
Creating public link...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/utils/index.js b/src/components/utils/index.js
index 86507cb..e0238e9 100644
--- a/src/components/utils/index.js
+++ b/src/components/utils/index.js
@@ -1,74 +1,72 @@
-import { v1 } from 'uuid'
-
export function secondsToHHMMSS(val) {
- if (!val) return '00:00:00'
- let time = ''
- time = new Date(val * 1000).toISOString().substring(11, 19)
- if (time.substring(0, 2) === '00') {
- return time.substring(3, time.length)
+ if (!val) return "00:00:00";
+ let time = "";
+ time = new Date(val * 1000).toISOString().substring(11, 19);
+ if (time.substring(0, 2) === "00") {
+ return time.substring(3, time.length);
}
- return time
+ return time;
}
export function randomHsl(num, total) {
- return 'hsla(' + ((num + 1) / (total + 1)) * 360 + ', 100%, 50%, 1)'
+ return "hsla(" + ((num + 1) / (total + 1)) * 360 + ", 100%, 50%, 1)";
}
export function separateBulletPoints(markdownString) {
// Split the markdown string into individual lines
- const lines = markdownString.split('\n')
+ const lines = markdownString.split("\n");
// Remove empty lines and trim leading/trailing whitespace from each line
const cleanedLines = lines
- .filter((line) => line.trim() !== '')
- .map((line) => line.trim())
+ .filter((line) => line.trim() !== "")
+ .map((line) => line.trim());
// Iterate over the cleaned lines and extract the bullet points
- const bulletPoints = []
- let currentBulletPoint = ''
+ const bulletPoints = [];
+ let currentBulletPoint = "";
cleanedLines.forEach((line) => {
- if (line.startsWith('-')) {
+ if (line.startsWith("-")) {
// Add the current bullet point to the array
- if (currentBulletPoint !== '') {
- bulletPoints.push(currentBulletPoint.trim())
+ if (currentBulletPoint !== "") {
+ bulletPoints.push(currentBulletPoint.trim());
}
// Start a new bullet point
- currentBulletPoint = line.substring(1).trim()
+ currentBulletPoint = line.substring(1).trim();
} else {
// Append the line to the current bullet point
- currentBulletPoint += ' ' + line.trim()
+ currentBulletPoint += " " + line.trim();
}
- })
+ });
// Add the last bullet point to the array
- if (currentBulletPoint !== '') {
- bulletPoints.push(currentBulletPoint.trim())
+ if (currentBulletPoint !== "") {
+ bulletPoints.push(currentBulletPoint.trim());
}
- return bulletPoints
+ return bulletPoints;
}
-const NOT_ALLOWED_CHARS = ['\\$', '#', '\\[', '\\]', '\\.', '/'] // Escape special characters with backslashes
+const NOT_ALLOWED_CHARS = ["\\$", "#", "\\[", "\\]", "\\.", "/"]; // Escape special characters with backslashes
export function generateSlug(title) {
- if (title.split(' ').length > 1) {
+ if (title.split(" ").length > 1) {
const nTitle = title
- .split(' ')
+ .split(" ")
.slice(0, 10)
.filter((w) => /^[a-zA-Z0-9]+$/.test(w))
- .join('-')
- title = nTitle
+ .join("-");
+ title = nTitle;
}
for (const NOT_ALLOWED_CHAR of NOT_ALLOWED_CHARS) {
- title = title.replace(new RegExp(NOT_ALLOWED_CHAR, 'g'), '-')
+ title = title.replace(new RegExp(NOT_ALLOWED_CHAR, "g"), "-");
}
- const key = v1().replace(/-/g, '').substring(0, 8)
- const slug = `${title}_${key}`
- return slug
+ const key = crypto.randomUUID().replace(/-/g, "").substring(0, 8);
+ const slug = `${title}_${key}`;
+ return slug;
}
-export default {}
+export default {};