Chatbot_phase2_2407
This commit is contained in:
parent
4256143939
commit
4715e1b482
3
.gitignore
vendored
3
.gitignore
vendored
@ -39,3 +39,6 @@ uploads/*
|
||||
# Dedicated Documentation Folder
|
||||
/documents/
|
||||
/minio-seed/
|
||||
|
||||
ai-advisor.md
|
||||
testing-strategy.md
|
||||
@ -149,83 +149,121 @@ export class ChatService {
|
||||
// Explicit Attached Entities Ingestion (Drag-and-Drop AI Workbench)
|
||||
if (hasAttachedEntities) {
|
||||
for (const ent of attachedEntities!) {
|
||||
if (ent.entityKind === 'ASSET') {
|
||||
const dbAsset = await prisma.asset.findUnique({
|
||||
where: { id: ent.id },
|
||||
include: {
|
||||
verticals: true,
|
||||
techStacks: true,
|
||||
complianceStandards: true,
|
||||
if (!ent || !ent.id) continue;
|
||||
try {
|
||||
if (ent.entityKind === 'ASSET') {
|
||||
const dbAsset = await prisma.asset.findUnique({
|
||||
where: { id: ent.id },
|
||||
include: {
|
||||
verticals: true,
|
||||
techStacks: true,
|
||||
complianceStandards: true,
|
||||
}
|
||||
}).catch(() => null);
|
||||
|
||||
if (dbAsset) {
|
||||
citationsMap.set(dbAsset.id, {
|
||||
assetId: dbAsset.id,
|
||||
title: dbAsset.title,
|
||||
location: 'Inspected Catalog Asset',
|
||||
type: dbAsset.type,
|
||||
isRecommended: false,
|
||||
});
|
||||
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED ASSET] Title: "${dbAsset.title}" | Type: "${dbAsset.type}" | Link/URL: "${dbAsset.url}" | Category: "${dbAsset.categoryId || 'General'}" | Subcategory: "${dbAsset.subcategory || '-'}" | Description: "${dbAsset.description || 'N/A'}" | Problem Statement: "${dbAsset.problemStatement || 'N/A'}" | Solution Overview: "${dbAsset.solution || 'N/A'}" | Industry Verticals: "${(dbAsset.verticals || []).map(v => v.name).join(', ')}" | Tech Stack: "${(dbAsset.techStacks || []).map(t => t.name).join(', ')}" | Compliance Standards: "${(dbAsset.complianceStandards || []).map(c => c.name).join(', ')}"\n`
|
||||
);
|
||||
} else {
|
||||
citationsMap.set(ent.id, {
|
||||
assetId: ent.id,
|
||||
title: ent.title,
|
||||
location: 'Inspected Catalog Asset',
|
||||
type: ent.type || 'ASSET',
|
||||
isRecommended: false,
|
||||
});
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED ASSET] Title: "${ent.title}" | Type: "${ent.type || 'Asset'}" | Description: "${ent.description || 'N/A'}" | Problem Statement: "${ent.problemStatement || 'N/A'}" | Solution Overview: "${ent.solution || 'N/A'}"\n`
|
||||
);
|
||||
}
|
||||
});
|
||||
} else if (ent.entityKind === 'SHOWCASE') {
|
||||
const dbShowcase = await prisma.contentShowcase.findUnique({
|
||||
where: { id: ent.id }
|
||||
}).catch(() => null);
|
||||
|
||||
if (dbAsset) {
|
||||
citationsMap.set(dbAsset.id, {
|
||||
assetId: dbAsset.id,
|
||||
title: dbAsset.title,
|
||||
location: 'Inspected Catalog Asset',
|
||||
type: dbAsset.type,
|
||||
isRecommended: false,
|
||||
});
|
||||
if (dbShowcase) {
|
||||
citationsMap.set(dbShowcase.id, {
|
||||
assetId: dbShowcase.id,
|
||||
title: dbShowcase.title,
|
||||
location: 'Featured Content Showcase',
|
||||
type: 'case_study',
|
||||
isRecommended: false,
|
||||
});
|
||||
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED ASSET] Title: "${dbAsset.title}" | Type: "${dbAsset.type}" | Link/URL: "${dbAsset.url}" | Category: "${dbAsset.categoryId || 'General'}" | Subcategory: "${dbAsset.subcategory || '-'}" | Description: "${dbAsset.description || 'N/A'}" | Problem Statement: "${dbAsset.problemStatement || 'N/A'}" | Solution Overview: "${dbAsset.solution || 'N/A'}" | Industry Verticals: "${(dbAsset.verticals || []).map(v => v.name).join(', ')}" | Tech Stack: "${(dbAsset.techStacks || []).map(t => t.name).join(', ')}" | Compliance Standards: "${(dbAsset.complianceStandards || []).map(c => c.name).join(', ')}"\n`
|
||||
);
|
||||
}
|
||||
} else if (ent.entityKind === 'SHOWCASE') {
|
||||
const dbShowcase = await prisma.contentShowcase.findUnique({
|
||||
where: { id: ent.id }
|
||||
});
|
||||
|
||||
if (dbShowcase) {
|
||||
citationsMap.set(dbShowcase.id, {
|
||||
assetId: dbShowcase.id,
|
||||
title: dbShowcase.title,
|
||||
location: 'Featured Content Showcase',
|
||||
type: 'case_study',
|
||||
isRecommended: false,
|
||||
});
|
||||
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED FEATURED REEL] Title: "${dbShowcase.title}" | Video URL: "${dbShowcase.youtubeUrl}" | Description:\n${dbShowcase.description || 'Interactive product reel'}\n`
|
||||
);
|
||||
}
|
||||
} else if (ent.entityKind === 'ECOSYSTEM') {
|
||||
const dbOffering = await prisma.ecosystemOffering.findUnique({
|
||||
where: { id: ent.id }
|
||||
});
|
||||
|
||||
if (dbOffering) {
|
||||
citationsMap.set(dbOffering.id, {
|
||||
assetId: dbOffering.id,
|
||||
title: dbOffering.name,
|
||||
location: 'Ecosystem Offering',
|
||||
type: 'offering',
|
||||
isRecommended: false,
|
||||
});
|
||||
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED ECOSYSTEM OFFERING] Name: "${dbOffering.name}" | Type: "${dbOffering.type}" | Tagline: "${dbOffering.tagline}" | Website URL: "${dbOffering.websiteUrl}" | Description:\n${dbOffering.description}\n`
|
||||
);
|
||||
}
|
||||
} else if (ent.entityKind === 'LEGAL') {
|
||||
const dbLegal = await prisma.legalDocument.findFirst({
|
||||
where: { OR: [{ id: ent.id }, { type: ent.title.includes('NDA') ? 'NDA' : 'MSA' }] }
|
||||
});
|
||||
|
||||
if (dbLegal) {
|
||||
citationsMap.set(dbLegal.id, {
|
||||
assetId: dbLegal.id,
|
||||
title: ent.title,
|
||||
location: 'Legal Agreement',
|
||||
type: 'legal',
|
||||
isRecommended: false,
|
||||
});
|
||||
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED LEGAL AGREEMENT] Title: "${ent.title}" | Version: "${dbLegal.version}" | Content Summary:\n${dbLegal.content.slice(0, 800)}...\n`
|
||||
);
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED FEATURED REEL] Title: "${dbShowcase.title}" | Video URL: "${dbShowcase.youtubeUrl}" | Description:\n${dbShowcase.description || 'Interactive product reel'}\n`
|
||||
);
|
||||
} else {
|
||||
citationsMap.set(ent.id, {
|
||||
assetId: ent.id,
|
||||
title: ent.title,
|
||||
location: 'Featured Content Showcase',
|
||||
type: 'case_study',
|
||||
isRecommended: false,
|
||||
});
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED FEATURED REEL] Title: "${ent.title}" | Video URL: "${ent.url || ''}" | Description:\n${ent.description || 'Interactive product reel'}\n`
|
||||
);
|
||||
}
|
||||
} else if (ent.entityKind === 'ECOSYSTEM') {
|
||||
const dbOffering = await prisma.ecosystemOffering.findUnique({
|
||||
where: { id: ent.id }
|
||||
}).catch(() => null);
|
||||
|
||||
if (dbOffering) {
|
||||
citationsMap.set(dbOffering.id, {
|
||||
assetId: dbOffering.id,
|
||||
title: dbOffering.name,
|
||||
location: 'Ecosystem Offering',
|
||||
type: 'offering',
|
||||
isRecommended: false,
|
||||
});
|
||||
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED ECOSYSTEM OFFERING] Name: "${dbOffering.name}" | Type: "${dbOffering.type}" | Tagline: "${dbOffering.tagline}" | Website URL: "${dbOffering.websiteUrl}" | Key Benefits: ${((dbOffering.benefits as string[]) || []).join('; ')} | Description:\n${dbOffering.description}\n`
|
||||
);
|
||||
} else {
|
||||
citationsMap.set(ent.id, {
|
||||
assetId: ent.id,
|
||||
title: ent.title,
|
||||
location: 'Ecosystem Offering',
|
||||
type: 'offering',
|
||||
isRecommended: false,
|
||||
});
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED ECOSYSTEM OFFERING] Name: "${ent.title}" | Description:\n${ent.description || 'Enterprise Ecosystem Offering'}\n`
|
||||
);
|
||||
}
|
||||
} else if (ent.entityKind === 'LEGAL') {
|
||||
const dbLegal = await prisma.legalDocument.findFirst({
|
||||
where: { OR: [{ id: ent.id }, { type: ent.title.includes('NDA') ? 'NDA' : 'MSA' }] }
|
||||
}).catch(() => null);
|
||||
|
||||
if (dbLegal) {
|
||||
citationsMap.set(dbLegal.id, {
|
||||
assetId: dbLegal.id,
|
||||
title: ent.title,
|
||||
location: 'Legal Agreement',
|
||||
type: 'legal',
|
||||
isRecommended: false,
|
||||
});
|
||||
|
||||
contextLines.push(
|
||||
`[WORKBENCH ATTACHED LEGAL AGREEMENT] Type: "${dbLegal.type}" | Version: "${dbLegal.version}" | Content:\n${dbLegal.content}\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to parse entity payload in RAG pipeline', err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -290,7 +328,7 @@ export class ChatService {
|
||||
});
|
||||
|
||||
contextLines.push(
|
||||
`[Ecosystem Offering] Name: "${eo.name}" | Type: "${eo.type}" | Tagline: "${eo.tagline}" | Website: "${eo.websiteUrl}" | Description:\n${eo.description}\n`
|
||||
`[Ecosystem Offering] Name: "${eo.name}" | Type: "${eo.type}" | Tagline: "${eo.tagline}" | Website URL: "${eo.websiteUrl}" | Key Benefits: ${((eo.benefits as string[]) || []).join('; ')} | Description:\n${eo.description}\n`
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -448,12 +486,19 @@ Exact Admin Console Layout Guide for Administrator (${user.email}):
|
||||
Role & Target Audience:
|
||||
- User: ${user.email} (${isClient ? 'Client / Partner' : 'Portal Administrator'}).
|
||||
|
||||
OUTPUT FORMATTING REQUIREMENTS (CRITICAL FOR IMMACULATE VISUAL STRUCTURE):
|
||||
1. **Multi-Turn Context Awareness**: Maintain full conversational memory. When asked for follow-ups or comparisons of previously mentioned assets, resolve references accurately.
|
||||
2. **Never Output Concatenated Single-Line Tables**: EVERY Markdown table row MUST be separated by a real newline character (\n). Never concatenate table rows like "| Col A | Col B | | :--- | :--- |".
|
||||
3. **Structure Sections Clearly**: Use bold section titles (e.g. "### Summary", "### Key Differences", "### Features & Specifications").
|
||||
4. **Use Bullet Points for Readability**: When detailing lists of features, target users, or tech stacks, use bulleted lists instead of long unformatted paragraphs.
|
||||
5. **Clickable Links**: Include direct clickable Markdown links if URLs or resources exist (e.g., "🔗 **Direct Link**: [Visit Resource](URL)").
|
||||
OUTPUT FORMATTING REQUIREMENTS (CRITICAL FOR QUALITY & SECURITY):
|
||||
1. **Never Output Database IDs or UUIDs**: Database IDs (UUIDs, hashes, etc.) are strictly confidential internal identifiers. You MUST NEVER output any ID or UUID in your response text to the user.
|
||||
2. **Strict Guidelines for Suggested Links**:
|
||||
- **Internal Portal Pages**: If referencing pages inside the portal, you must ONLY use these exact human-accessible relative links:
|
||||
- Client Portal: \`/client/assets\` (Asset Explorer), \`/client/showcase\` (Featured Content), \`/client/ecosystem\` (Explore More), or \`/client/agreements\` (Legal Agreements).
|
||||
- Admin Console: \`/admin/assets\` (Manage Catalog), \`/admin/showcase\` (Showcase Manager), \`/admin/ecosystem\` (Ecosystem Manager), or \`/admin/legal\` (Legal Templates).
|
||||
- **CRITICAL**: Never append database IDs or UUIDs to these paths (e.g., do NOT link to \`/client/assets/123-abc\`). Doing so creates broken pages.
|
||||
- **External Links**: You may suggest an external resource link (e.g. "[Visit Website](URL)") ONLY if the URL starts with \`http\` or \`https\` and is a public web link (not containing \`localhost\`, \`127.0.0.1\`, or \`/uploads/\`).
|
||||
- **Fallback**: If no valid link exists, instruct the user to view it via the citation cards below the chat bubble using the **Quick Preview** button or locate it in the catalog.
|
||||
3. **Multi-Turn Context Awareness**: Maintain full conversational memory. When asked for follow-ups or comparisons of previously mentioned assets, resolve references accurately.
|
||||
4. **Never Output Concatenated Single-Line Tables**: EVERY Markdown table row MUST be separated by a real newline character (\\n). Never concatenate table rows like "| Col A | Col B | | :--- | :--- |".
|
||||
5. **Structure Sections Clearly**: Use bold section titles (e.g. "### Summary", "### Key Differences", "### Features & Specifications").
|
||||
6. **Use Bullet Points for Readability**: When detailing lists of features, target users, or tech stacks, use bulleted lists instead of long unformatted paragraphs.
|
||||
|
||||
${hasAttachedEntities ? `
|
||||
CRITICAL RULES FOR WORKBENCH ATTACHED ENTITIES:
|
||||
@ -462,8 +507,10 @@ CRITICAL RULES FOR WORKBENCH ATTACHED ENTITIES:
|
||||
- Provide a clear, high-impact summary of what these attached items are, their core problem/solution, tech stack, and key features.
|
||||
- Do NOT list, summarize, or invent any unattached showcase reels (like Digital Twin, Smart Basket, etc.) or unrelated catalog assets! Focus 100% EXCLUSIVELY on the attached items.
|
||||
` : `
|
||||
- Answer user questions accurately using the provided Knowledge Context.
|
||||
- Do NOT hallucinate or list random showcase reels or unrelated assets unless explicitly requested by the user.
|
||||
CRITICAL GROUNDING RULES (ZERO HALLUCINATION & STRICT KNOWLEDGE COMPLIANCE):
|
||||
1. **Rely ONLY on Provided Knowledge Context**: Answer user questions 100% EXCLUSIVELY using the provided Knowledge Context. Do NOT use outside general knowledge or make ungrounded assumptions.
|
||||
2. **No Invented Demos or Non-Working Links**: If the context does not explicitly list a live demo URL or document file link for an offering (such as CodeNuk, Cloudtopiaa, or Audittrax Labs), state clearly: "You can explore this offering under the 'Explore More' section (/client/ecosystem) or visit their website at [Website URL]." Do NOT invent dummy demo links, raw file paths, or non-working URLs.
|
||||
3. **If Information is Missing**: If the provided Knowledge Context does not contain the answer, explicitly state: "I don't have detailed information on that in the portal database. Please check the Asset Explorer or contact your account administrator."
|
||||
`}
|
||||
|
||||
Portal Navigation Guidelines:
|
||||
@ -559,10 +606,35 @@ Portal Navigation Guidelines:
|
||||
assistantReply = 'Here is the requested information from your shared catalog:\n\n' + contextBlock;
|
||||
}
|
||||
|
||||
// Strict Citation Filtering: Only include Verified Knowledge Source citations if user explicitly attached entities or asked for asset recommendations
|
||||
const isExplicitSearchQuery = promptLower.includes('find asset') || promptLower.includes('search asset') || promptLower.includes('show asset') || promptLower.includes('recommend asset') || promptLower.includes('showcase video') || promptLower.includes('legal document');
|
||||
// Strict Citation Filtering: Only include Verified Knowledge Source citations if user explicitly attached entities or asked for asset/resource recommendations
|
||||
const isGeneralHelpQuery = promptLower.includes('password') ||
|
||||
promptLower.includes('theme') ||
|
||||
promptLower.includes('appearance') ||
|
||||
promptLower.includes('profile') ||
|
||||
promptLower.includes('how to log') ||
|
||||
promptLower.includes('how to sign') ||
|
||||
promptLower.includes('help');
|
||||
|
||||
const finalCitations = (hasAttachedEntities || isExplicitSearchQuery)
|
||||
const hasAssetKeywords = promptLower.includes('asset') ||
|
||||
promptLower.includes('recommend') ||
|
||||
promptLower.includes('find') ||
|
||||
promptLower.includes('search') ||
|
||||
promptLower.includes('show') ||
|
||||
promptLower.includes('list') ||
|
||||
promptLower.includes('what are') ||
|
||||
promptLower.includes('is there') ||
|
||||
promptLower.includes('are there') ||
|
||||
promptLower.includes('showcase') ||
|
||||
promptLower.includes('agreement') ||
|
||||
promptLower.includes('legal') ||
|
||||
promptLower.includes('document') ||
|
||||
promptLower.includes('video') ||
|
||||
promptLower.includes('reel') ||
|
||||
promptLower.includes('offering') ||
|
||||
promptLower.includes('which are') ||
|
||||
promptLower.includes('tell me more');
|
||||
|
||||
const finalCitations = (hasAttachedEntities || (hasAssetKeywords && !isGeneralHelpQuery))
|
||||
? Array.from(citationsMap.values())
|
||||
: [];
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ dotenv.config();
|
||||
|
||||
import prisma from './db';
|
||||
|
||||
async function seedTaxonomy() {
|
||||
export async function seedFourGroupTaxonomy() {
|
||||
console.log('[Taxonomy-Seed] Starting taxonomy re-seeding with exact user specifications...');
|
||||
|
||||
await prisma.vertical.deleteMany();
|
||||
@ -200,10 +200,13 @@ async function seedTaxonomy() {
|
||||
}
|
||||
|
||||
console.log(`[Taxonomy-Seed] Successfully mapped exact taxonomy relations for ${updatedCount} assets.`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
seedTaxonomy().catch(err => {
|
||||
console.error('[Taxonomy-Seed] Failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
if (require.main === module || (process.argv[1] && process.argv[1].includes('seed-taxonomy'))) {
|
||||
seedFourGroupTaxonomy()
|
||||
.then(() => process.exit(0))
|
||||
.catch(err => {
|
||||
console.error('[Taxonomy-Seed] Failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@ -5,6 +5,9 @@
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tech4Biz Client & Admin Portal</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
600
Channel-Frontend/package-lock.json
generated
600
Channel-Frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -16,27 +16,27 @@ export const PageLayout: React.FC<PageLayoutProps> = ({
|
||||
className = '',
|
||||
}) => {
|
||||
return (
|
||||
<div className={`flex flex-col h-[calc(100vh-140px)] md:h-[calc(100vh-160px)] w-full overflow-hidden ${className}`}>
|
||||
<div className={`flex-1 flex flex-col min-h-0 h-full w-full overflow-hidden ${className}`}>
|
||||
{/* Page Header (Fixed) */}
|
||||
<div className="shrink-0 mb-4">
|
||||
<div className="shrink-0 mb-3 sm:mb-4">
|
||||
{header}
|
||||
</div>
|
||||
|
||||
{/* Toolbar (Fixed) */}
|
||||
{toolbar && (
|
||||
<div className="shrink-0 mb-4">
|
||||
<div className="shrink-0 mb-3 sm:mb-4">
|
||||
{toolbar}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Scrollable Content Area */}
|
||||
<div className="flex-1 min-h-0 overflow-y-auto bg-ink-0 border border-ink-200 rounded-xl shadow-sm relative flex flex-col scrollbar-thin">
|
||||
<div className="flex-1 min-h-0 overflow-y-auto bg-ink-0 border border-ink-200 rounded-2xl shadow-sm relative flex flex-col scrollbar-thin">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{/* Page-level Footer/Pagination (Fixed) */}
|
||||
{footer && (
|
||||
<div className="shrink-0 mt-4">
|
||||
<div className="shrink-0 mt-3 sm:mt-4">
|
||||
{footer}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -66,13 +66,25 @@ export const ChatDrawer: React.FC = () => {
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([
|
||||
{
|
||||
sender: 'ASSISTANT',
|
||||
content: 'Hello! I am your **Tech4Biz AI Advisor Workbench** powered by DeepSeek. **Drag and drop any asset, case study reel, ecosystem offering, or legal agreement** here to inspect, summarize, and receive instant role-tailored explanations!',
|
||||
content: 'Hello! I am your **Tech4Biz AI Advisor Workbench**. **Drag and drop any asset, case study reel, ecosystem offering** here to inspect, summarize, and receive instant role-tailored explanations!',
|
||||
}
|
||||
]);
|
||||
const [activePreviewAsset, setActivePreviewAsset] = useState<Asset | null>(null);
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [hasActiveOverlay, setHasActiveOverlay] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkOverlays = () => {
|
||||
const overlays = document.querySelectorAll('.fixed.inset-0.z-50, .fixed.inset-y-0.right-0');
|
||||
setHasActiveOverlay(overlays.length > 0);
|
||||
};
|
||||
checkOverlays();
|
||||
const interval = setInterval(checkOverlays, 300);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
@ -328,7 +340,7 @@ export const ChatDrawer: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* Draggable Floating Trigger Pill */}
|
||||
{!isOpen && (
|
||||
{!isOpen && !hasActiveOverlay && (
|
||||
<motion.button
|
||||
drag
|
||||
dragConstraints={{ left: -1200, right: 20, top: -800, bottom: 20 }}
|
||||
@ -339,10 +351,10 @@ export const ChatDrawer: React.FC = () => {
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={() => { setIsOpen(true); setIsMinimized(false); }}
|
||||
className="fixed bottom-6 right-6 z-50 flex items-center gap-2.5 px-4 py-3 bg-slate-900 text-white rounded-full shadow-2xl border border-slate-700/80 hover:border-emerald-500/80 transition-all cursor-grab active:cursor-grabbing group select-none"
|
||||
className="fixed bottom-14 right-6 sm:bottom-16 sm:right-8 z-30 flex items-center gap-2.5 px-4 py-3 bg-slate-900 text-white rounded-full shadow-2xl border border-slate-700/80 hover:border-emerald-500/80 transition-all cursor-grab active:cursor-grabbing group select-none"
|
||||
>
|
||||
<div className="p-1.5 rounded-full bg-emerald-500/20 text-emerald-400 border border-emerald-500/40 group-hover:scale-110 transition-transform">
|
||||
<Sparkles className="w-4 h-4" />
|
||||
<Bot className="w-4 h-4" />
|
||||
</div>
|
||||
<span className="text-xs font-extrabold tracking-wide font-sans">
|
||||
AI Advisor
|
||||
@ -381,7 +393,7 @@ export const ChatDrawer: React.FC = () => {
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-bold text-xs text-white">Tech4Biz AI Advisor</h3>
|
||||
<p className="text-[10px] text-slate-400">Powered by DeepSeek LLM Engine</p>
|
||||
<p className="text-[10px] text-slate-400">Enterprise Intelligent Assistant</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -405,11 +417,10 @@ export const ChatDrawer: React.FC = () => {
|
||||
type="button"
|
||||
onClick={handleToggleHistory}
|
||||
title="Past Chat Sessions"
|
||||
className={`p-1.5 rounded-lg border transition-colors cursor-pointer ${
|
||||
showHistory
|
||||
className={`p-1.5 rounded-lg border transition-colors cursor-pointer ${showHistory
|
||||
? 'bg-emerald-500/30 text-emerald-300 border-emerald-500/50'
|
||||
: 'text-slate-400 hover:text-white hover:bg-slate-800 border-slate-800'
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
<History className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
@ -467,11 +478,10 @@ export const ChatDrawer: React.FC = () => {
|
||||
<div
|
||||
key={sess.id}
|
||||
onClick={() => handleLoadSession(sess.id)}
|
||||
className={`p-2 rounded-xl border text-xs cursor-pointer flex items-center justify-between transition-all ${
|
||||
isActive
|
||||
className={`p-2 rounded-xl border text-xs cursor-pointer flex items-center justify-between transition-all ${isActive
|
||||
? 'bg-emerald-500/20 text-emerald-300 border-emerald-500/40'
|
||||
: 'bg-slate-950/70 hover:bg-slate-800 text-slate-300 border-slate-800'
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<MessageSquare className="w-3.5 h-3.5 text-emerald-400 shrink-0" />
|
||||
@ -497,20 +507,18 @@ export const ChatDrawer: React.FC = () => {
|
||||
key={i}
|
||||
className={`flex gap-2.5 ${msg.sender === 'USER' ? 'flex-row-reverse' : 'flex-row'}`}
|
||||
>
|
||||
<div className={`p-1.5 rounded-xl shrink-0 h-fit ${
|
||||
msg.sender === 'USER'
|
||||
<div className={`p-1.5 rounded-xl shrink-0 h-fit ${msg.sender === 'USER'
|
||||
? 'bg-emerald-600 text-white'
|
||||
: 'bg-slate-900 text-emerald-400 border border-slate-700'
|
||||
}`}>
|
||||
}`}>
|
||||
{msg.sender === 'USER' ? <UserIcon className="w-3.5 h-3.5" /> : <Bot className="w-3.5 h-3.5" />}
|
||||
</div>
|
||||
|
||||
<div className={`space-y-2 max-w-[85%] ${msg.sender === 'USER' ? 'text-right' : 'text-left'}`}>
|
||||
<div className={`p-3.5 rounded-2xl leading-relaxed ${
|
||||
msg.sender === 'USER'
|
||||
<div className={`p-3.5 rounded-2xl leading-relaxed ${msg.sender === 'USER'
|
||||
? 'bg-emerald-600 text-white font-medium rounded-tr-none whitespace-pre-wrap'
|
||||
: 'bg-slate-900 text-slate-100 border border-slate-800 rounded-tl-none shadow-md'
|
||||
}`}>
|
||||
}`}>
|
||||
{msg.sender === 'USER' ? (
|
||||
msg.content
|
||||
) : (
|
||||
@ -583,7 +591,7 @@ export const ChatDrawer: React.FC = () => {
|
||||
{loading && (
|
||||
<div className="flex items-center gap-2.5 text-slate-400 text-xs italic p-2">
|
||||
<RefreshCw className="w-3.5 h-3.5 animate-spin text-emerald-400" />
|
||||
Analyzing catalog knowledge with DeepSeek...
|
||||
Analyzing catalog knowledge...
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -670,7 +678,7 @@ export const ChatDrawer: React.FC = () => {
|
||||
asset={activePreviewAsset}
|
||||
isOpen={!!activePreviewAsset}
|
||||
user={user}
|
||||
onDownload={() => {}}
|
||||
onDownload={() => { }}
|
||||
onClose={() => setActivePreviewAsset(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -213,12 +213,37 @@ const handleLine = (line: string, state: ParseState): void => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Preprocesses raw markdown text to split single-line concatenated markdown table rows into proper multi-line Markdown tables.
|
||||
* Preprocesses raw markdown text to split single-line concatenated markdown table rows and merge split table rows across lines.
|
||||
*/
|
||||
const sanitizeMarkdownText = (rawText: string): string => {
|
||||
if (!rawText) return "";
|
||||
|
||||
let formatted = rawText;
|
||||
const lines = rawText.split("\n");
|
||||
const processedLines: string[] = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const trimmed = line.trim();
|
||||
|
||||
// If the line starts with a pipe but doesn't end with one, it is likely split across linebreaks
|
||||
if (trimmed.startsWith("|") && !trimmed.endsWith("|")) {
|
||||
let merged = line;
|
||||
while (i + 1 < lines.length) {
|
||||
const nextLine = lines[i + 1];
|
||||
const nextTrimmed = nextLine.trim();
|
||||
merged += " " + nextTrimmed;
|
||||
i++;
|
||||
if (nextTrimmed.endsWith("|")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
processedLines.push(merged);
|
||||
} else {
|
||||
processedLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
let formatted = processedLines.join("\n");
|
||||
|
||||
// Split inline concatenated table rows like "| Col 1 | Col 2 | | :--- | :--- | | Val 1 | Val 2 |"
|
||||
formatted = formatted.replace(/\|\s*\|\s*:-/g, "|\n| :-");
|
||||
|
||||
@ -86,28 +86,28 @@ export const AssetCard: React.FC<AssetCardProps> = ({
|
||||
|
||||
const isRenderable = (type: string, url: string) => {
|
||||
const isOffice = type.includes('word') || type.includes('presentation') || type.includes('sheet') ||
|
||||
url.toLowerCase().endsWith('.docx') || url.toLowerCase().endsWith('.doc') ||
|
||||
url.toLowerCase().endsWith('.pptx') || url.toLowerCase().endsWith('.ppt') ||
|
||||
url.toLowerCase().endsWith('.xlsx') || url.toLowerCase().endsWith('.xls') ||
|
||||
url.toLowerCase().endsWith('.csv') || type.includes('csv');
|
||||
url.toLowerCase().endsWith('.docx') || url.toLowerCase().endsWith('.doc') ||
|
||||
url.toLowerCase().endsWith('.pptx') || url.toLowerCase().endsWith('.ppt') ||
|
||||
url.toLowerCase().endsWith('.xlsx') || url.toLowerCase().endsWith('.xls') ||
|
||||
url.toLowerCase().endsWith('.csv') || type.includes('csv');
|
||||
|
||||
const isTextOrCode = url.toLowerCase().endsWith('.md') ||
|
||||
url.toLowerCase().endsWith('.txt') ||
|
||||
url.toLowerCase().endsWith('.json') ||
|
||||
url.toLowerCase().endsWith('.js') ||
|
||||
url.toLowerCase().endsWith('.ts') ||
|
||||
url.toLowerCase().endsWith('.tsx') ||
|
||||
url.toLowerCase().endsWith('.jsx') ||
|
||||
url.toLowerCase().endsWith('.py') ||
|
||||
url.toLowerCase().endsWith('.yaml') ||
|
||||
url.toLowerCase().endsWith('.yml') ||
|
||||
url.toLowerCase().endsWith('.css') ||
|
||||
url.toLowerCase().endsWith('.html') ||
|
||||
url.toLowerCase().endsWith('.sh') ||
|
||||
type.includes('text') ||
|
||||
type.includes('markdown') ||
|
||||
type.includes('json') ||
|
||||
type.includes('javascript');
|
||||
url.toLowerCase().endsWith('.txt') ||
|
||||
url.toLowerCase().endsWith('.json') ||
|
||||
url.toLowerCase().endsWith('.js') ||
|
||||
url.toLowerCase().endsWith('.ts') ||
|
||||
url.toLowerCase().endsWith('.tsx') ||
|
||||
url.toLowerCase().endsWith('.jsx') ||
|
||||
url.toLowerCase().endsWith('.py') ||
|
||||
url.toLowerCase().endsWith('.yaml') ||
|
||||
url.toLowerCase().endsWith('.yml') ||
|
||||
url.toLowerCase().endsWith('.css') ||
|
||||
url.toLowerCase().endsWith('.html') ||
|
||||
url.toLowerCase().endsWith('.sh') ||
|
||||
type.includes('text') ||
|
||||
type.includes('markdown') ||
|
||||
type.includes('json') ||
|
||||
type.includes('javascript');
|
||||
|
||||
return type === 'url' || type.includes('pdf') || type.includes('image') || type.includes('png') || type.includes('jpg') || isOffice || isTextOrCode;
|
||||
};
|
||||
@ -188,13 +188,12 @@ export const AssetCard: React.FC<AssetCardProps> = ({
|
||||
}
|
||||
onViewDetails(asset);
|
||||
}}
|
||||
className={`group bg-ink-0 border rounded-xl p-4 transition-[border-color,box-shadow,background-color] duration-300 flex flex-col justify-between cursor-pointer min-h-[410px] ${
|
||||
isExpanded
|
||||
className={`group bg-ink-0 border rounded-xl p-4 transition-[border-color,box-shadow,background-color] duration-300 flex flex-col justify-between cursor-pointer min-h-[410px] ${isExpanded
|
||||
? 'absolute z-20 top-0 left-0 right-0 h-auto shadow-2xl border-ink-300 bg-ink-0'
|
||||
: isRecommended
|
||||
? 'relative w-full h-full border-amber-500/35 bg-gradient-to-br from-amber-500/[0.02] via-ink-0 to-ink-0 hover:border-amber-500 hover:shadow-lg hover:shadow-amber-500/10 hover:-translate-y-0.5'
|
||||
: 'relative w-full h-full border-ink-200 hover:border-ink-300 hover:shadow-md hover:-translate-y-0.5'
|
||||
} ${isSelected ? 'border-ink-900 ring-1 ring-ink-900 bg-ink-50/30' : ''}`}
|
||||
} ${isSelected ? 'border-ink-900 ring-1 ring-ink-900 bg-ink-50/30' : ''}`}
|
||||
>
|
||||
<div className="flex-grow flex flex-col">
|
||||
{/* Visual Thumbnail Area */}
|
||||
@ -207,9 +206,8 @@ export const AssetCard: React.FC<AssetCardProps> = ({
|
||||
checked={isSelected}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) => onToggleSelect(asset.id, e as unknown as React.MouseEvent)}
|
||||
className={`w-3.5 h-3.5 rounded border-ink-300 bg-ink-0 text-ink-900 focus:ring-ink-900/10 cursor-pointer shadow-sm transition-opacity duration-200 ${
|
||||
isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
|
||||
}`}
|
||||
className={`w-3.5 h-3.5 rounded border-ink-300 bg-ink-0 text-ink-900 focus:ring-ink-900/10 cursor-pointer shadow-sm transition-opacity duration-200 ${isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
{isRecommended && (
|
||||
@ -227,7 +225,7 @@ export const AssetCard: React.FC<AssetCardProps> = ({
|
||||
title="Inspect with AI Advisor Workbench"
|
||||
>
|
||||
<Sparkles className="w-3 h-3 text-amber-400 fill-amber-400" />
|
||||
<span>AI Workbench</span>
|
||||
<span>ask AI</span>
|
||||
</button>
|
||||
|
||||
{isRenderable(asset.type, asset.url) && (
|
||||
|
||||
@ -673,7 +673,7 @@ export const UploadAssetModal: React.FC<UploadAssetModalProps> = ({
|
||||
Include in AI Advisor Knowledge Base (RAG Search)
|
||||
</label>
|
||||
<span className="text-[10px] text-ink-500 dark:text-slate-400 block mt-0.5">
|
||||
If checked, DeepSeek AI Advisor indexes this content to answer partner questions with OKF standard citations.
|
||||
If checked, the AI Advisor indexes this content to answer partner questions with OKF standard citations.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
--color-info: #3b82f6;
|
||||
|
||||
/* ── Typography ── */
|
||||
--font-sans: 'Inter Variable', 'Inter', ui-sans-serif, system-ui, sans-serif;
|
||||
--font-sans: 'Poppins', 'Inter', ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
|
||||
|
||||
/* ── Spacing Scale ── */
|
||||
|
||||
@ -597,8 +597,8 @@ export const AssetsPage = () => {
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className={`grid grid-cols-1 ${viewMode === 'compact'
|
||||
? 'md:grid-cols-3 xl:grid-cols-5 gap-3'
|
||||
: 'md:grid-cols-2 xl:grid-cols-4 gap-4'
|
||||
? 'sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-5 gap-3'
|
||||
: 'sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6'
|
||||
} items-start`}
|
||||
>
|
||||
{filteredAssets.map((asset) => (
|
||||
|
||||
@ -120,7 +120,7 @@ export const ClientAgreementsPage: React.FC = () => {
|
||||
title="Inspect NDA with AI Advisor Workbench"
|
||||
>
|
||||
<Sparkles className="w-3 h-3 text-amber-400 fill-amber-400" />
|
||||
<span>AI Workbench</span>
|
||||
<span>ask AI</span>
|
||||
</button>
|
||||
</div>
|
||||
{ndaAcceptance ? (
|
||||
@ -232,7 +232,7 @@ export const ClientAgreementsPage: React.FC = () => {
|
||||
title="Inspect MSA with AI Advisor Workbench"
|
||||
>
|
||||
<Sparkles className="w-3 h-3 text-amber-400 fill-amber-400" />
|
||||
<span>AI Workbench</span>
|
||||
<span>ask AI</span>
|
||||
</button>
|
||||
</div>
|
||||
{msaAcceptance ? (
|
||||
@ -377,7 +377,7 @@ export const ClientAgreementsPage: React.FC = () => {
|
||||
`Verification Hash: ${activeDocData.signatureHash || 'N/A'}\n`,
|
||||
`IP Address: ${activeDocData.ipAddress}\n`,
|
||||
`Signed On: ${new Date(activeDocData.acceptedAt).toLocaleString()}\n`
|
||||
], {type: 'text/plain'});
|
||||
], { type: 'text/plain' });
|
||||
element.href = URL.createObjectURL(file);
|
||||
element.download = `${selectedDoc}_Agreement_${user?.email?.split('@')[0]}.txt`;
|
||||
document.body.appendChild(element);
|
||||
|
||||
@ -90,11 +90,10 @@ export const EcosystemPage: React.FC = () => {
|
||||
<button
|
||||
key={type}
|
||||
onClick={() => setFilter(type)}
|
||||
className={`px-4 py-2 rounded-lg text-xs font-black tracking-wider uppercase transition-all duration-300 whitespace-nowrap cursor-pointer ${
|
||||
filter === type
|
||||
className={`px-4 py-2 rounded-lg text-xs font-black tracking-wider uppercase transition-all duration-300 whitespace-nowrap cursor-pointer ${filter === type
|
||||
? 'bg-ink-0 text-ink-900 shadow-sm border border-ink-200/50'
|
||||
: 'text-ink-500 hover:text-ink-900'
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
{labels[type]}
|
||||
</button>
|
||||
@ -118,7 +117,7 @@ export const EcosystemPage: React.FC = () => {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -15 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
className="grid grid-cols-1 md:grid-cols-2 gap-8"
|
||||
className="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8"
|
||||
>
|
||||
{filteredOfferings.map((offering) => {
|
||||
const IconComponent = iconMap[offering.logoIcon] || Globe;
|
||||
@ -176,14 +175,13 @@ export const EcosystemPage: React.FC = () => {
|
||||
title="Inspect Offering with AI Advisor Workbench"
|
||||
>
|
||||
<Sparkles className="w-3 h-3 text-amber-400 fill-amber-400" />
|
||||
<span>AI Workbench</span>
|
||||
<span>ask AI</span>
|
||||
</button>
|
||||
|
||||
<span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-[9px] font-black tracking-widest uppercase border ${
|
||||
isProduct
|
||||
<span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-[9px] font-black tracking-widest uppercase border ${isProduct
|
||||
? 'bg-blue-500/10 text-blue-600 border-blue-500/20'
|
||||
: 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20'
|
||||
}`}>
|
||||
}`}>
|
||||
<Layers className="w-3 h-3" />
|
||||
{offering.type}
|
||||
</span>
|
||||
@ -210,9 +208,8 @@ export const EcosystemPage: React.FC = () => {
|
||||
<ul className="grid grid-cols-1 gap-2.5 pt-1">
|
||||
{offering.benefits.map((benefit, bIdx) => (
|
||||
<li key={bIdx} className="flex items-start gap-2.5 text-xs font-medium text-ink-600">
|
||||
<span className={`w-1.5 h-1.5 rounded-full shrink-0 mt-1.5 ${
|
||||
isProduct ? 'bg-blue-500' : 'bg-emerald-500'
|
||||
}`} />
|
||||
<span className={`w-1.5 h-1.5 rounded-full shrink-0 mt-1.5 ${isProduct ? 'bg-blue-500' : 'bg-emerald-500'
|
||||
}`} />
|
||||
<span>{benefit}</span>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@ -129,9 +129,8 @@ const VideoDescription: React.FC<VideoDescriptionProps> = ({ text, isExpanded, o
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<p
|
||||
className={`text-[11px] font-medium text-ink-500 leading-relaxed overflow-hidden ${
|
||||
isExpanded ? '' : 'line-clamp-2'
|
||||
}`}
|
||||
className={`text-[11px] font-medium text-ink-500 leading-relaxed overflow-hidden ${isExpanded ? '' : 'line-clamp-2'
|
||||
}`}
|
||||
style={{ whiteSpace: isExpanded ? 'pre-wrap' : 'normal' }}
|
||||
>
|
||||
{text}
|
||||
@ -230,7 +229,7 @@ export const ShowcasePage: React.FC = () => {
|
||||
<p className="text-xs text-ink-500 mt-1 max-w-sm">There are no featured videos available in the showcase right now. Check back later!</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 animate-fadeIn">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 animate-fadeIn">
|
||||
{items.map((item) => {
|
||||
const ytId = extractYouTubeVideoId(item.youtubeUrl);
|
||||
const isIg = item.youtubeUrl.includes('instagram.com');
|
||||
@ -257,11 +256,10 @@ export const ShowcasePage: React.FC = () => {
|
||||
e.dataTransfer.setData('text/plain', item.title);
|
||||
}}
|
||||
transition={{ type: "spring", stiffness: 320, damping: 28 }}
|
||||
className={`group flex flex-col bg-ink-0 border rounded-2xl overflow-hidden transition-[border-color,box-shadow,background-color] duration-300 ${
|
||||
isExpanded
|
||||
className={`group flex flex-col bg-ink-0 border rounded-2xl overflow-hidden transition-[border-color,box-shadow,background-color] duration-300 ${isExpanded
|
||||
? 'absolute z-20 top-0 left-0 right-0 h-auto shadow-2xl border-ink-350 bg-ink-0'
|
||||
: 'relative w-full h-full border-ink-200 hover:border-ink-350 hover:shadow-xl'
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
{/* Video Player / Thumbnail */}
|
||||
<div className="relative aspect-video bg-ink-900 overflow-hidden shrink-0">
|
||||
@ -283,7 +281,7 @@ export const ShowcasePage: React.FC = () => {
|
||||
title="Inspect Reel with AI Advisor Workbench"
|
||||
>
|
||||
<Sparkles className="w-3 h-3 text-amber-400 fill-amber-400" />
|
||||
<span>AI Workbench</span>
|
||||
<span>ask AI</span>
|
||||
</button>
|
||||
{thumbnail ? (
|
||||
<img
|
||||
@ -293,13 +291,12 @@ export const ShowcasePage: React.FC = () => {
|
||||
/>
|
||||
) : (
|
||||
/* Platform Fallback Gradients */
|
||||
<div className={`w-full h-full flex items-center justify-center ${
|
||||
isIg
|
||||
<div className={`w-full h-full flex items-center justify-center ${isIg
|
||||
? 'bg-gradient-to-tr from-yellow-500 via-pink-500 to-purple-600'
|
||||
: isTw
|
||||
? 'bg-ink-950'
|
||||
: 'bg-ink-100'
|
||||
}`}>
|
||||
? 'bg-ink-950'
|
||||
: 'bg-ink-100'
|
||||
}`}>
|
||||
{isIg && (
|
||||
<svg className="w-12 h-12 text-white/90 animate-pulse" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24">
|
||||
<rect x="2" y="2" width="20" height="20" rx="5" ry="5" />
|
||||
@ -390,13 +387,12 @@ export const ShowcasePage: React.FC = () => {
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
transition={{ type: 'spring', damping: 25, stiffness: 250 }}
|
||||
className={`relative bg-ink-900 border border-ink-800 rounded-3xl overflow-hidden shadow-2xl flex flex-col transition-all duration-300 ${
|
||||
lightboxSize === 'cinema'
|
||||
className={`relative bg-ink-900 border border-ink-800 rounded-3xl overflow-hidden shadow-2xl flex flex-col transition-all duration-300 ${lightboxSize === 'cinema'
|
||||
? 'w-[95vw] max-w-7xl md:flex-col h-[85vh] md:h-[90vh]'
|
||||
: lightboxSize === 'theater'
|
||||
? 'w-[95vw] md:w-[85vw] max-w-6xl md:flex-row h-[85vh] md:max-h-[85vh]'
|
||||
: 'w-[95vw] md:w-full max-w-4xl md:flex-row h-[85vh] md:max-h-[80vh]'
|
||||
}`}
|
||||
? 'w-[95vw] md:w-[85vw] max-w-6xl md:flex-row h-[85vh] md:max-h-[85vh]'
|
||||
: 'w-[95vw] md:w-full max-w-4xl md:flex-row h-[85vh] md:max-h-[80vh]'
|
||||
}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Media Container */}
|
||||
@ -422,22 +418,20 @@ export const ShowcasePage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Info Container */}
|
||||
<div className={`p-5 sm:p-6 flex flex-col justify-between border-ink-800 bg-ink-950 overflow-y-auto ${
|
||||
lightboxSize === 'cinema'
|
||||
<div className={`p-5 sm:p-6 flex flex-col justify-between border-ink-800 bg-ink-950 overflow-y-auto ${lightboxSize === 'cinema'
|
||||
? 'w-full border-t h-[40%] md:h-[30%] shrink-0'
|
||||
: 'w-full md:w-80 border-t md:border-t-0 md:border-l h-[45%] md:h-auto shrink-0'
|
||||
}`}>
|
||||
}`}>
|
||||
<div className="space-y-4">
|
||||
{/* Toolbar with Resizer Preset Buttons */}
|
||||
<div className="flex justify-between items-center pb-2 border-b border-ink-800/60">
|
||||
<div className="hidden md:flex bg-ink-900 p-0.5 rounded-lg border border-ink-800 gap-0.5">
|
||||
<button
|
||||
onClick={() => setLightboxSize('compact')}
|
||||
className={`p-1.5 rounded text-[9px] font-black uppercase tracking-wider flex items-center gap-1 transition-all cursor-pointer ${
|
||||
lightboxSize === 'compact'
|
||||
className={`p-1.5 rounded text-[9px] font-black uppercase tracking-wider flex items-center gap-1 transition-all cursor-pointer ${lightboxSize === 'compact'
|
||||
? 'bg-ink-800 text-ink-0'
|
||||
: 'text-ink-500 hover:text-ink-300'
|
||||
}`}
|
||||
}`}
|
||||
title="Compact View"
|
||||
>
|
||||
<Monitor className="w-3 h-3" />
|
||||
@ -445,11 +439,10 @@ export const ShowcasePage: React.FC = () => {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setLightboxSize('theater')}
|
||||
className={`p-1.5 rounded text-[9px] font-black uppercase tracking-wider flex items-center gap-1 transition-all cursor-pointer ${
|
||||
lightboxSize === 'theater'
|
||||
className={`p-1.5 rounded text-[9px] font-black uppercase tracking-wider flex items-center gap-1 transition-all cursor-pointer ${lightboxSize === 'theater'
|
||||
? 'bg-ink-800 text-ink-0'
|
||||
: 'text-ink-500 hover:text-ink-300'
|
||||
}`}
|
||||
}`}
|
||||
title="Theater View"
|
||||
>
|
||||
<Tv className="w-3 h-3" />
|
||||
@ -457,11 +450,10 @@ export const ShowcasePage: React.FC = () => {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setLightboxSize('cinema')}
|
||||
className={`p-1.5 rounded text-[9px] font-black uppercase tracking-wider flex items-center gap-1 transition-all cursor-pointer ${
|
||||
lightboxSize === 'cinema'
|
||||
className={`p-1.5 rounded text-[9px] font-black uppercase tracking-wider flex items-center gap-1 transition-all cursor-pointer ${lightboxSize === 'cinema'
|
||||
? 'bg-ink-800 text-ink-0'
|
||||
: 'text-ink-500 hover:text-ink-300'
|
||||
}`}
|
||||
}`}
|
||||
title="Cinema View"
|
||||
>
|
||||
<Maximize2 className="w-3 h-3" />
|
||||
|
||||
@ -7,7 +7,7 @@ export default defineConfig({
|
||||
plugins: [tailwindcss(), react()],
|
||||
server: {
|
||||
allowedHosts: [
|
||||
"delegator-caregiver-overprice.ngrok-free.dev"
|
||||
"hurricane-reverence-robin.ngrok-free.dev"
|
||||
],
|
||||
cors: true,
|
||||
proxy: {
|
||||
|
||||
@ -34,4 +34,4 @@ fi
|
||||
|
||||
# 3. Start Vite Dev Server
|
||||
info "Starting Vite frontend dev server..."
|
||||
npm run dev -- --host
|
||||
node --max-old-space-size=512 node_modules/vite/bin/vite.js --host
|
||||
|
||||
Loading…
Reference in New Issue
Block a user