feat: Oracle Canvas, Revision History and Canvas Sharing (#33)
Co-authored-by: Sagnik <sagnik7896@gmail.com> Reviewed-on: #33
This commit was merged in pull request #33.
This commit is contained in:
2
app/dist/index.html
vendored
2
app/dist/index.html
vendored
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Velocity WebOS</title>
|
||||
<script type="module" crossorigin src="./assets/index-C2Cn6fx_.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index-BbE_azx6.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CILgAuxv.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
8
app/node_modules/.vite/deps/@radix-ui_react-avatar.js
generated
vendored
8
app/node_modules/.vite/deps/@radix-ui_react-avatar.js
generated
vendored
@@ -1,18 +1,18 @@
|
||||
"use client";
|
||||
import {
|
||||
createSlot
|
||||
} from "./chunk-5HUACAZ7.js";
|
||||
import {
|
||||
useCallbackRef,
|
||||
useLayoutEffect2
|
||||
} from "./chunk-GRXJTWBV.js";
|
||||
import "./chunk-HPBHRBIF.js";
|
||||
import {
|
||||
require_react_dom
|
||||
} from "./chunk-YLZ34CCM.js";
|
||||
import {
|
||||
require_shim
|
||||
} from "./chunk-642Z5WD3.js";
|
||||
import {
|
||||
createSlot
|
||||
} from "./chunk-5HUACAZ7.js";
|
||||
import "./chunk-HPBHRBIF.js";
|
||||
import {
|
||||
require_jsx_runtime
|
||||
} from "./chunk-USXRE7Q2.js";
|
||||
|
||||
6
app/node_modules/.vite/deps/@radix-ui_react-dropdown-menu.js
generated
vendored
6
app/node_modules/.vite/deps/@radix-ui_react-dropdown-menu.js
generated
vendored
@@ -3,13 +3,13 @@ import {
|
||||
useCallbackRef,
|
||||
useLayoutEffect2
|
||||
} from "./chunk-GRXJTWBV.js";
|
||||
import {
|
||||
require_react_dom
|
||||
} from "./chunk-YLZ34CCM.js";
|
||||
import {
|
||||
composeRefs,
|
||||
useComposedRefs
|
||||
} from "./chunk-HPBHRBIF.js";
|
||||
import {
|
||||
require_react_dom
|
||||
} from "./chunk-YLZ34CCM.js";
|
||||
import {
|
||||
require_jsx_runtime
|
||||
} from "./chunk-USXRE7Q2.js";
|
||||
|
||||
6
app/node_modules/.vite/deps/@react-three_drei.js
generated
vendored
6
app/node_modules/.vite/deps/@react-three_drei.js
generated
vendored
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
subscribeWithSelector
|
||||
} from "./chunk-XGWIEMTH.js";
|
||||
import {
|
||||
create
|
||||
} from "./chunk-QJTQF54Q.js";
|
||||
import {
|
||||
subscribeWithSelector
|
||||
} from "./chunk-XGWIEMTH.js";
|
||||
import {
|
||||
Events
|
||||
} from "./chunk-OAEA5FZL.js";
|
||||
|
||||
64
app/node_modules/.vite/deps/_metadata.json
generated
vendored
64
app/node_modules/.vite/deps/_metadata.json
generated
vendored
@@ -7,127 +7,127 @@
|
||||
"react": {
|
||||
"src": "../../react/index.js",
|
||||
"file": "react.js",
|
||||
"fileHash": "44c1ad00",
|
||||
"fileHash": "c178e920",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react-dom": {
|
||||
"src": "../../react-dom/index.js",
|
||||
"file": "react-dom.js",
|
||||
"fileHash": "09fbf9a4",
|
||||
"fileHash": "071b9320",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react/jsx-dev-runtime": {
|
||||
"src": "../../react/jsx-dev-runtime.js",
|
||||
"file": "react_jsx-dev-runtime.js",
|
||||
"fileHash": "ce2da90b",
|
||||
"fileHash": "72ddf78c",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react/jsx-runtime": {
|
||||
"src": "../../react/jsx-runtime.js",
|
||||
"file": "react_jsx-runtime.js",
|
||||
"fileHash": "52be981b",
|
||||
"fileHash": "14b8d385",
|
||||
"needsInterop": true
|
||||
},
|
||||
"@radix-ui/react-avatar": {
|
||||
"src": "../../@radix-ui/react-avatar/dist/index.mjs",
|
||||
"file": "@radix-ui_react-avatar.js",
|
||||
"fileHash": "63b564be",
|
||||
"fileHash": "590b7679",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@radix-ui/react-dropdown-menu": {
|
||||
"src": "../../@radix-ui/react-dropdown-menu/dist/index.mjs",
|
||||
"file": "@radix-ui_react-dropdown-menu.js",
|
||||
"fileHash": "b9686e90",
|
||||
"fileHash": "087b631e",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@radix-ui/react-slot": {
|
||||
"src": "../../@radix-ui/react-slot/dist/index.mjs",
|
||||
"file": "@radix-ui_react-slot.js",
|
||||
"fileHash": "417c3a07",
|
||||
"fileHash": "4e55412b",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@react-three/drei": {
|
||||
"src": "../../@react-three/drei/index.js",
|
||||
"file": "@react-three_drei.js",
|
||||
"fileHash": "b25127e3",
|
||||
"fileHash": "ba800aca",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@react-three/fiber": {
|
||||
"src": "../../@react-three/fiber/dist/react-three-fiber.esm.js",
|
||||
"file": "@react-three_fiber.js",
|
||||
"fileHash": "22a2309e",
|
||||
"fileHash": "12f23541",
|
||||
"needsInterop": false
|
||||
},
|
||||
"class-variance-authority": {
|
||||
"src": "../../class-variance-authority/dist/index.mjs",
|
||||
"file": "class-variance-authority.js",
|
||||
"fileHash": "6e6c6fd0",
|
||||
"fileHash": "0153428f",
|
||||
"needsInterop": false
|
||||
},
|
||||
"clsx": {
|
||||
"src": "../../clsx/dist/clsx.mjs",
|
||||
"file": "clsx.js",
|
||||
"fileHash": "eb68424d",
|
||||
"fileHash": "99f068f1",
|
||||
"needsInterop": false
|
||||
},
|
||||
"framer-motion": {
|
||||
"src": "../../framer-motion/dist/es/index.mjs",
|
||||
"file": "framer-motion.js",
|
||||
"fileHash": "1cbcab3b",
|
||||
"fileHash": "c1fc1ac2",
|
||||
"needsInterop": false
|
||||
},
|
||||
"lucide-react": {
|
||||
"src": "../../lucide-react/dist/esm/lucide-react.js",
|
||||
"file": "lucide-react.js",
|
||||
"fileHash": "6dded310",
|
||||
"fileHash": "4418176c",
|
||||
"needsInterop": false
|
||||
},
|
||||
"react-dom/client": {
|
||||
"src": "../../react-dom/client.js",
|
||||
"file": "react-dom_client.js",
|
||||
"fileHash": "c3a7edc3",
|
||||
"fileHash": "8029f031",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react-router-dom": {
|
||||
"src": "../../react-router-dom/dist/index.mjs",
|
||||
"file": "react-router-dom.js",
|
||||
"fileHash": "e91f778e",
|
||||
"fileHash": "c673e5a0",
|
||||
"needsInterop": false
|
||||
},
|
||||
"recharts": {
|
||||
"src": "../../recharts/es6/index.js",
|
||||
"file": "recharts.js",
|
||||
"fileHash": "d7f9dad1",
|
||||
"fileHash": "41235262",
|
||||
"needsInterop": false
|
||||
},
|
||||
"sonner": {
|
||||
"src": "../../sonner/dist/index.mjs",
|
||||
"file": "sonner.js",
|
||||
"fileHash": "8433c1a9",
|
||||
"fileHash": "c99e6320",
|
||||
"needsInterop": false
|
||||
},
|
||||
"tailwind-merge": {
|
||||
"src": "../../tailwind-merge/dist/bundle-mjs.mjs",
|
||||
"file": "tailwind-merge.js",
|
||||
"fileHash": "772f1bbd",
|
||||
"fileHash": "017ed736",
|
||||
"needsInterop": false
|
||||
},
|
||||
"three": {
|
||||
"src": "../../three/build/three.module.js",
|
||||
"file": "three.js",
|
||||
"fileHash": "490e5c00",
|
||||
"fileHash": "8d6b5e64",
|
||||
"needsInterop": false
|
||||
},
|
||||
"zustand": {
|
||||
"src": "../../zustand/esm/index.mjs",
|
||||
"file": "zustand.js",
|
||||
"fileHash": "315f8e85",
|
||||
"fileHash": "bcef7203",
|
||||
"needsInterop": false
|
||||
},
|
||||
"zustand/middleware": {
|
||||
"src": "../../zustand/esm/middleware.mjs",
|
||||
"file": "zustand_middleware.js",
|
||||
"fileHash": "2563a89b",
|
||||
"fileHash": "1afe1817",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
@@ -135,12 +135,12 @@
|
||||
"hls-Q6LDPZPT": {
|
||||
"file": "hls-Q6LDPZPT.js"
|
||||
},
|
||||
"chunk-XGWIEMTH": {
|
||||
"file": "chunk-XGWIEMTH.js"
|
||||
},
|
||||
"chunk-QJTQF54Q": {
|
||||
"file": "chunk-QJTQF54Q.js"
|
||||
},
|
||||
"chunk-XGWIEMTH": {
|
||||
"file": "chunk-XGWIEMTH.js"
|
||||
},
|
||||
"chunk-OAEA5FZL": {
|
||||
"file": "chunk-OAEA5FZL.js"
|
||||
},
|
||||
@@ -150,15 +150,12 @@
|
||||
"chunk-H4GSM2WL": {
|
||||
"file": "chunk-H4GSM2WL.js"
|
||||
},
|
||||
"chunk-5HUACAZ7": {
|
||||
"file": "chunk-5HUACAZ7.js"
|
||||
"chunk-U7P2NEEE": {
|
||||
"file": "chunk-U7P2NEEE.js"
|
||||
},
|
||||
"chunk-GRXJTWBV": {
|
||||
"file": "chunk-GRXJTWBV.js"
|
||||
},
|
||||
"chunk-HPBHRBIF": {
|
||||
"file": "chunk-HPBHRBIF.js"
|
||||
},
|
||||
"chunk-YLZ34CCM": {
|
||||
"file": "chunk-YLZ34CCM.js"
|
||||
},
|
||||
@@ -177,15 +174,18 @@
|
||||
"chunk-642Z5WD3": {
|
||||
"file": "chunk-642Z5WD3.js"
|
||||
},
|
||||
"chunk-5HUACAZ7": {
|
||||
"file": "chunk-5HUACAZ7.js"
|
||||
},
|
||||
"chunk-HPBHRBIF": {
|
||||
"file": "chunk-HPBHRBIF.js"
|
||||
},
|
||||
"chunk-USXRE7Q2": {
|
||||
"file": "chunk-USXRE7Q2.js"
|
||||
},
|
||||
"chunk-ZNKPWGXJ": {
|
||||
"file": "chunk-ZNKPWGXJ.js"
|
||||
},
|
||||
"chunk-U7P2NEEE": {
|
||||
"file": "chunk-U7P2NEEE.js"
|
||||
},
|
||||
"chunk-G3PMV62Z": {
|
||||
"file": "chunk-G3PMV62Z.js"
|
||||
}
|
||||
|
||||
6
app/node_modules/.vite/deps/recharts.js
generated
vendored
6
app/node_modules/.vite/deps/recharts.js
generated
vendored
@@ -1,15 +1,15 @@
|
||||
import {
|
||||
_extends
|
||||
} from "./chunk-H4GSM2WL.js";
|
||||
import {
|
||||
clsx_default
|
||||
} from "./chunk-U7P2NEEE.js";
|
||||
import {
|
||||
require_react_dom
|
||||
} from "./chunk-YLZ34CCM.js";
|
||||
import {
|
||||
require_react
|
||||
} from "./chunk-ZNKPWGXJ.js";
|
||||
import {
|
||||
clsx_default
|
||||
} from "./chunk-U7P2NEEE.js";
|
||||
import {
|
||||
__commonJS,
|
||||
__export,
|
||||
|
||||
@@ -454,6 +454,7 @@ export default function OraclePage() {
|
||||
page={page}
|
||||
isOpen={shareOpen}
|
||||
onClose={() => setShareOpen(false)}
|
||||
currentUserId={me?.userId ?? null}
|
||||
onShare={handleShare}
|
||||
/>
|
||||
|
||||
|
||||
@@ -39,7 +39,35 @@ function groupBySection(components: CanvasComponent[]): Array<{ sectionId: strin
|
||||
sectionMap.get(sid)!.push(comp);
|
||||
}
|
||||
|
||||
return Array.from(sectionMap.entries()).map(([sectionId, comps]) => ({ sectionId, components: comps }));
|
||||
return Array.from(sectionMap.entries())
|
||||
.map(([sectionId, comps]) => ({ sectionId, components: comps }))
|
||||
.sort((a, b) => {
|
||||
const aPrompt = a.sectionId.startsWith('sec_prompt_generated');
|
||||
const bPrompt = b.sectionId.startsWith('sec_prompt_generated');
|
||||
if (aPrompt && bPrompt) {
|
||||
const aCreated = Math.max(...a.components.map((comp) => Date.parse(comp.provenance.createdAt || '1970-01-01T00:00:00Z')));
|
||||
const bCreated = Math.max(...b.components.map((comp) => Date.parse(comp.provenance.createdAt || '1970-01-01T00:00:00Z')));
|
||||
return bCreated - aCreated;
|
||||
}
|
||||
if (aPrompt !== bPrompt) return aPrompt ? -1 : 1;
|
||||
return Math.min(...a.components.map((comp) => comp.layout.orderIndex)) - Math.min(...b.components.map((comp) => comp.layout.orderIndex));
|
||||
});
|
||||
}
|
||||
|
||||
function getSectionLabel(sectionId: string, sectionComps: CanvasComponent[]): string {
|
||||
if (SECTION_LABELS[sectionId]) return SECTION_LABELS[sectionId];
|
||||
if (sectionId.startsWith('sec_prompt_generated')) {
|
||||
const planning = sectionComps.find((comp) => comp.type === 'textCanvas');
|
||||
const content = planning?.visualizationParameters?.content;
|
||||
if (typeof content === 'string') {
|
||||
const firstLine = content.split('\n')[0]?.trim();
|
||||
if (firstLine?.startsWith('Oracle received:')) {
|
||||
return firstLine.replace('Oracle received:', '').trim();
|
||||
}
|
||||
}
|
||||
return 'Oracle Response';
|
||||
}
|
||||
return sectionId.replace(/^sec_/, '').replace(/_/g, ' ');
|
||||
}
|
||||
|
||||
/** CSS content-visibility wrapper for off-screen components, applying width mode to the flex item */
|
||||
@@ -93,7 +121,7 @@ export function CanvasViewport({
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-1 h-4 rounded-full bg-gradient-to-b from-blue-400 to-cyan-500" />
|
||||
<h2 className="text-xs font-semibold uppercase tracking-widest text-zinc-500">
|
||||
{SECTION_LABELS[sectionId] ?? sectionId.replace(/^sec_/, '').replace(/_/g, ' ')}
|
||||
{getSectionLabel(sectionId, sectionComps)}
|
||||
</h2>
|
||||
<div className="flex-1 h-[1px]" style={{ background: 'rgba(255,255,255,0.05)' }} />
|
||||
<span className="text-[10px] text-zinc-700">{sectionComps.length}</span>
|
||||
|
||||
@@ -10,6 +10,7 @@ interface ShareModalProps {
|
||||
page: CanvasPage | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
currentUserId?: string | null;
|
||||
onShare: (params: {
|
||||
recipientUserId: string;
|
||||
visibility: 'private' | 'team';
|
||||
@@ -40,7 +41,7 @@ function getInitials(member: VelocityActiveUser): string {
|
||||
.join('') || 'U';
|
||||
}
|
||||
|
||||
export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps) {
|
||||
export function ShareModal({ page, isOpen, onClose, currentUserId, onShare }: ShareModalProps) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [teamMembers, setTeamMembers] = useState<VelocityActiveUser[]>([]);
|
||||
const [loadingMembers, setLoadingMembers] = useState(false);
|
||||
@@ -50,6 +51,7 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
const [message, setMessage] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||
const [memberDropOpen, setMemberDropOpen] = useState(false);
|
||||
|
||||
useEffect(() => setMounted(true), []);
|
||||
@@ -57,6 +59,7 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setMemberDropOpen(false);
|
||||
setSubmitError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,6 +86,17 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const availableMembers = useMemo(
|
||||
() => teamMembers.filter((member) => member.user_id !== currentUserId),
|
||||
[teamMembers, currentUserId],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (recipient && recipient.user_id === currentUserId) {
|
||||
setRecipient(null);
|
||||
}
|
||||
}, [recipient, currentUserId]);
|
||||
|
||||
const selectedRecipientLabel = useMemo(
|
||||
() => (recipient ? getDisplayName(recipient) : 'Select verified teammate...'),
|
||||
[recipient],
|
||||
@@ -91,6 +105,7 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
const handleShare = async () => {
|
||||
if (!recipient || !page) return;
|
||||
setSubmitting(true);
|
||||
setSubmitError(null);
|
||||
try {
|
||||
await onShare({
|
||||
recipientUserId: recipient.user_id,
|
||||
@@ -105,8 +120,8 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
setRecipient(null);
|
||||
setMessage('');
|
||||
}, 1800);
|
||||
} catch {
|
||||
// keep modal open and let caller surface the error upstream
|
||||
} catch (error) {
|
||||
setSubmitError(error instanceof Error ? error.message : 'Share failed.');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
@@ -180,6 +195,17 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{submitError && (
|
||||
<div
|
||||
className="rounded-xl px-3 py-2 text-xs text-red-300"
|
||||
style={{
|
||||
background: 'rgba(239,68,68,0.08)',
|
||||
border: '1px solid rgba(239,68,68,0.2)',
|
||||
}}
|
||||
>
|
||||
{submitError}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="text-xs font-medium text-zinc-400 mb-1.5 block">Recipient</label>
|
||||
<div className="relative">
|
||||
@@ -217,10 +243,10 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
{!loadingMembers && membersError && (
|
||||
<div className="px-3 py-3 text-xs text-red-400">{membersError}</div>
|
||||
)}
|
||||
{!loadingMembers && !membersError && teamMembers.length === 0 && (
|
||||
{!loadingMembers && !membersError && availableMembers.length === 0 && (
|
||||
<div className="px-3 py-3 text-xs text-zinc-500">No verified users available.</div>
|
||||
)}
|
||||
{!loadingMembers && !membersError && teamMembers.map((member) => (
|
||||
{!loadingMembers && !membersError && availableMembers.map((member) => (
|
||||
<button
|
||||
key={member.user_id}
|
||||
className="w-full flex items-center gap-3 px-3 py-2.5 hover:bg-white/5 transition-colors text-left"
|
||||
|
||||
Reference in New Issue
Block a user