Optimize thread counting and add license headers to workflow files (#1949)

# READ CAREFULLY THEN REMOVE

Remove bullet points that are not relevant.

PLEASE REFRAIN FROM USING AI TO WRITE YOUR CODE AND PR DESCRIPTION. IF YOU DO USE AI TO WRITE YOUR CODE PLEASE PROVIDE A DESCRIPTION AND REVIEW IT CAREFULLY. MAKE SURE YOU UNDERSTAND THE CODE YOU ARE SUBMITTING USING AI.

- Pull requests that do not follow these guidelines will be closed without review or comment.
- If you use AI to write your PR description your pr will be close without review or comment.
- If you are unsure about anything, feel free to ask for clarification.

## Description

Please provide a clear description of your changes.

---

## Type of Change

Please delete options that are not relevant.

- [ ] 🐛 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 💥 Breaking change (fix or feature with breaking changes)
- [ ] 📝 Documentation update
- [ ] 🎨 UI/UX improvement
- [ ] 🔒 Security enhancement
- [ ]  Performance improvement

## Areas Affected

Please check all that apply:

- [ ] Email Integration (Gmail, IMAP, etc.)
- [ ] User Interface/Experience
- [ ] Authentication/Authorization
- [ ] Data Storage/Management
- [ ] API Endpoints
- [ ] Documentation
- [ ] Testing Infrastructure
- [ ] Development Workflow
- [ ] Deployment/Infrastructure

## Testing Done

Describe the tests you've done:

- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing performed
- [ ] Cross-browser testing (if UI changes)
- [ ] Mobile responsiveness verified (if UI changes)

## Security Considerations

For changes involving data or authentication:

- [ ] No sensitive data is exposed
- [ ] Authentication checks are in place
- [ ] Input validation is implemented
- [ ] Rate limiting is considered (if applicable)

## Checklist

- [ ] I have read the [CONTRIBUTING](https://github.com/Mail-0/Zero/blob/staging/.github/CONTRIBUTING.md) document
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in complex areas
- [ ] I have updated the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix/feature works
- [ ] All tests pass locally
- [ ] Any dependent changes are merged and published

## Additional Notes

Add any other context about the pull request here.

## Screenshots/Recordings

Add screenshots or recordings here if applicable.

---

_By submitting this pull request, I confirm that my contribution is made under the terms of the project's license._

    
<!-- This is an auto-generated description by cubic. -->
---

## Summary by cubic
Improved thread counting performance by batching label queries and added Apache license headers to workflow files.

- **Refactors**
  - Replaced individual thread count queries with a single batched query for all labels.
  - Added license headers to workflow-related source files.

<!-- End of auto-generated description by cubic. -->



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **New Features**
  * Improved thread counting performance by enabling batch counting of threads across multiple folders or labels.
* **Refactor**
  * Streamlined folder synchronization logic and removed unused or commented-out code for better maintainability.
* **Documentation**
  * Added Apache 2.0 license headers to several files to clarify usage and licensing terms.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Adam
2025-08-07 17:37:29 -07:00
committed by GitHub
parent 18314cd088
commit 3ca38991fe
31 changed files with 241 additions and 229 deletions

View File

@@ -201,13 +201,13 @@ function LoginClientContent({ providers, isProd }: LoginClientProps) {
</button>
<div
className={`overflow-hidden transition-all duration-300 ease-in-out ${expandedProviders[provider.id] ? 'max-h-[500px] opacity-100' : 'max-h-0 opacity-0'}`}
className={`overflow-hidden duration-300 ease-in-out ${expandedProviders[provider.id] ? 'max-h-[500px] opacity-100' : 'max-h-0 opacity-0'}`}
>
<div className="bg-black/3 p-4 font-mono text-sm dark:bg-white/3">
<div className="bg-black/3 dark:bg-white/3 p-4 font-mono text-sm">
{provider.envVarStatus.map((envVar) => (
<div
key={envVar.name}
className={`mb-3 flex items-start transition-all duration-300 ease-in-out last:mb-0 ${expandedProviders[provider.id] ? 'translate-y-0 opacity-100' : 'translate-y-2 opacity-0'}`}
className={`mb-3 flex items-start duration-300 ease-in-out last:mb-0 ${expandedProviders[provider.id] ? 'translate-y-0 opacity-100' : 'translate-y-2 opacity-0'}`}
style={{
transitionDelay: expandedProviders[provider.id]
? `${provider.envVarStatus.indexOf(envVar) * 50}ms`

View File

@@ -380,7 +380,7 @@ export default function OpenPage() {
</div>
{/* Project Stats */}
<div className="mb-8 overflow-hidden rounded-xl border bg-linear-to-b from-white/50 to-white/10 p-6 backdrop-blur-sm dark:border-neutral-700 dark:from-neutral-900/50 dark:to-neutral-900/30">
<div className="bg-linear-to-b mb-8 overflow-hidden rounded-xl border from-white/50 to-white/10 p-6 backdrop-blur-sm dark:border-neutral-700 dark:from-neutral-900/50 dark:to-neutral-900/30">
<div className="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
<div className="space-y-1">
<div className="flex items-center gap-2">
@@ -484,7 +484,7 @@ export default function OpenPage() {
<div className="mt-6 grid gap-4 lg:grid-cols-3">
{/* Repository Growth */}
<Card className="col-span-full border-neutral-100 bg-white/50 p-4 transition-all hover:bg-white/60 lg:col-span-2 dark:border-neutral-800 dark:bg-neutral-900/50 dark:hover:bg-neutral-900/60">
<Card className="col-span-full border-neutral-100 bg-white/50 p-4 hover:bg-white/60 lg:col-span-2 dark:border-neutral-800 dark:bg-neutral-900/50 dark:hover:bg-neutral-900/60">
<h3 className="mb-4 text-sm font-medium text-neutral-600 dark:text-neutral-400">
Repository Growth
</h3>
@@ -576,7 +576,7 @@ export default function OpenPage() {
</Card>
{/* Activity Chart */}
<Card className="col-span-full border-neutral-200 bg-white/50 p-4 transition-all hover:bg-white/60 lg:col-span-1 dark:border-neutral-800 dark:bg-neutral-900/50 dark:hover:bg-neutral-900/60">
<Card className="col-span-full border-neutral-200 bg-white/50 p-4 hover:bg-white/60 lg:col-span-1 dark:border-neutral-800 dark:bg-neutral-900/50 dark:hover:bg-neutral-900/60">
<h3 className="mb-4 text-sm font-medium text-neutral-600 dark:text-neutral-400">
Recent Activity
</h3>
@@ -672,7 +672,7 @@ export default function OpenPage() {
{filteredCoreTeam?.map((member, index) => (
<div
key={member.login}
className="group relative flex items-center gap-4 rounded-xl border bg-white/50 p-4 transition-all hover:-translate-y-1 hover:bg-white hover:shadow-lg dark:border-neutral-800 dark:bg-neutral-900/50 dark:hover:bg-neutral-900 dark:hover:shadow-neutral-900/50"
className="group relative flex items-center gap-4 rounded-xl border bg-white/50 p-4 hover:-translate-y-1 hover:bg-white hover:shadow-lg dark:border-neutral-800 dark:bg-neutral-900/50 dark:hover:bg-neutral-900 dark:hover:shadow-neutral-900/50"
style={{
animationDelay: `${index * 100}ms`,
animation: 'fadeInUp 0.5s ease-out forwards',
@@ -783,7 +783,7 @@ export default function OpenPage() {
key={contributor.login}
href={contributor.html_url}
target="_blank"
className="group relative flex flex-col items-center rounded-xl border bg-white/50 p-4 transition-all hover:-translate-y-1 hover:bg-white hover:shadow-lg dark:border-neutral-800 dark:bg-neutral-900/50 dark:hover:bg-neutral-900 dark:hover:shadow-neutral-900/50"
className="group relative flex flex-col items-center rounded-xl border bg-white/50 p-4 hover:-translate-y-1 hover:bg-white hover:shadow-lg dark:border-neutral-800 dark:bg-neutral-900/50 dark:hover:bg-neutral-900 dark:hover:shadow-neutral-900/50"
style={{
animationDelay: `${index * 50}ms`,
animation: 'fadeInUp 0.5s ease-out forwards',
@@ -908,7 +908,7 @@ export default function OpenPage() {
</div>
<div className="mb-8">
<div className="relative overflow-hidden rounded-xl border bg-linear-to-br from-neutral-50 to-white shadow-sm dark:border-neutral-800 dark:from-neutral-900/80 dark:to-neutral-900/30">
<div className="bg-linear-to-br relative overflow-hidden rounded-xl border from-neutral-50 to-white shadow-sm dark:border-neutral-800 dark:from-neutral-900/80 dark:to-neutral-900/30">
<div className="absolute inset-0 opacity-20 dark:opacity-20"></div>
<div className="relative p-6">
@@ -928,7 +928,7 @@ export default function OpenPage() {
<div className="mt-5 flex flex-wrap gap-3">
<Button
asChild
className="relative overflow-hidden bg-neutral-900 text-white transition-all hover:bg-neutral-800 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100"
className="relative overflow-hidden bg-neutral-900 text-white hover:bg-neutral-800 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100"
>
<a
href={`https://github.com/${REPOSITORY}/blob/main/.github/CONTRIBUTING.md`}

View File

@@ -61,7 +61,7 @@ export default function PrivacyPolicy() {
</h2>
<button
onClick={() => handleCopyLink(sectionId)}
className="text-gray-400 transition-all hover:text-gray-700 dark:text-white/60 dark:hover:text-white/80"
className="text-gray-400 hover:text-gray-700 dark:text-white/60 dark:hover:text-white/80"
aria-label={`Copy link to ${section.title} section`}
>
<Link2

View File

@@ -6,14 +6,12 @@ import { Button } from '@/components/ui/button';
import Footer from '@/components/home/footer';
import { createSectionId } from '@/lib/utils';
import React from 'react';
const LAST_UPDATED = 'February 13, 2025';
export default function TermsOfService() {
const { copiedValue: copiedSection, copyToClipboard } = useCopyToClipboard();
const handleCopyLink = (sectionId: string) => {
const url = `${window.location.origin}${window.location.pathname}#${sectionId}`;
@@ -25,7 +23,7 @@ export default function TermsOfService() {
<Navigation />
<div className="relative z-10 flex grow flex-col">
{/* Back Button */}
<div className="absolute right-4 top-6 md:left-8 md:top-8 md:right-auto">
<div className="absolute right-4 top-6 md:left-8 md:right-auto md:top-8">
<a href="/">
<Button
variant="ghost"
@@ -64,7 +62,7 @@ export default function TermsOfService() {
</h2>
<button
onClick={() => handleCopyLink(sectionId)}
className="text-gray-400 transition-all hover:text-gray-700 dark:text-white/60 dark:hover:text-white/80"
className="text-gray-400 hover:text-gray-700 dark:text-white/60 dark:hover:text-white/80"
aria-label={`Copy link to ${section.title} section`}
>
<Link2

View File

@@ -94,7 +94,7 @@ export default function DeveloperPage() {
<div className="grid grid-cols-1 gap-4 pb-4 sm:gap-6 md:grid-cols-2 lg:grid-cols-3">
{developerResources.map((resource) => (
<Card key={resource.title} className="transition-all hover:shadow-md">
<Card key={resource.title} className="hover:shadow-md">
<CardHeader className="sm:p-6">
<div className="flex items-start gap-4 sm:items-center">
<div className={`shrink-0 rounded-lg ${resource.bgColor} p-2.5`}>

View File

@@ -164,7 +164,7 @@ export default function ConnectionsPage() {
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-primary ml-4 shrink-0"
className="text-muted-foreground hover:text-primary ml-4 shrink-0"
disabled={data.connections.length === 1}
>
<Trash className="h-4 w-4" />
@@ -205,7 +205,7 @@ export default function ConnectionsPage() {
<AddConnectionDialog>
<Button
variant="outline"
className="group relative w-9 overflow-hidden transition-all duration-200 hover:w-full sm:hover:w-[32.5%]"
className="group relative w-9 overflow-hidden duration-200 hover:w-full sm:hover:w-[32.5%]"
>
<Plus className="absolute left-2 h-4 w-4" />
<span className="whitespace-nowrap pl-7 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
@@ -217,7 +217,7 @@ export default function ConnectionsPage() {
<Button
onClick={() => setPricingDialog('true')}
variant="outline"
className="group relative w-9 overflow-hidden transition-all duration-200 hover:w-full sm:hover:w-[32.5%]"
className="group relative w-9 overflow-hidden duration-200 hover:w-full sm:hover:w-[32.5%]"
>
<Plus className="absolute left-2 h-4 w-4" />
<span className="whitespace-nowrap pl-7 opacity-0 transition-opacity duration-200 group-hover:opacity-100">

View File

@@ -316,7 +316,7 @@ export default function Editor({
>
{/* Make sure the command palette doesn't cause a refresh */}
<EditorCommand
className="border-muted bg-background z-50 h-auto max-h-[330px] overflow-y-auto rounded-md border px-1 py-2 shadow-md transition-all"
className="border-muted bg-background z-50 h-auto max-h-[330px] overflow-y-auto rounded-md border px-1 py-2 shadow-md"
onKeyDown={(e) => {
// Prevent form submission on any key that might trigger it
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {

View File

@@ -131,7 +131,7 @@ export function LabelDialog({
<button
key={color.backgroundColor}
type="button"
className={`h-10 w-10 rounded-[4px] border-[0.5px] border-white/10 transition-all ${
className={`h-10 w-10 rounded-[4px] border-[0.5px] border-white/10 ${
formColor?.backgroundColor.toString() === color.backgroundColor &&
formColor.textColor.toString() === color.textColor
? 'scale-110 ring-2 ring-blue-500 ring-offset-1'

View File

@@ -1313,10 +1313,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
</>
)}
</div>
<div
className="flex cursor-pointer flex-col pb-2 transition-all duration-200"
onClick={toggleCollapse}
>
<div className="flex cursor-pointer flex-col pb-2 duration-200" onClick={toggleCollapse}>
<div className="mt-3 flex w-full items-start justify-between gap-4 px-4">
<div className="flex w-full justify-center gap-4">
<BimiAvatar
@@ -1635,16 +1632,11 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
</div>
</div>
<div
className={cn(
'h-0 overflow-hidden transition-all duration-200',
!isCollapsed && 'h-px',
)}
></div>
<div className={cn('h-0 overflow-hidden duration-200', !isCollapsed && 'h-px')}></div>
<div
className={cn(
'grid overflow-hidden transition-all duration-200',
'grid overflow-hidden duration-200',
isCollapsed ? 'grid-rows-[0fr]' : 'grid-rows-[1fr]',
)}
onClick={(e) => e.stopPropagation()}

View File

@@ -223,7 +223,7 @@ const Thread = memo(
data-thread-id={idToUse}
key={idToUse}
className={cn(
'hover:bg-offsetLight dark:hover:bg-primary/5 group relative mx-1 flex cursor-pointer flex-col items-start rounded-lg py-2 text-left text-sm transition-all hover:opacity-100',
'hover:bg-offsetLight dark:hover:bg-primary/5 group relative mx-1 flex cursor-pointer flex-col items-start rounded-lg py-2 text-left text-sm hover:opacity-100',
(isMailSelected || isMailBulkSelected || isKeyboardFocused) &&
'border-border bg-primary/5 opacity-100',
isKeyboardFocused && 'ring-primary/50',
@@ -572,7 +572,7 @@ const Draft = memo(({ message }: { message: { id: string } }) => {
<div
key={message.id}
className={cn(
'group relative mx-[8px] flex cursor-pointer flex-col items-start overflow-clip rounded-[10px] border-transparent py-3 text-left text-sm transition-all',
'group relative mx-[8px] flex cursor-pointer flex-col items-start overflow-clip rounded-[10px] border-transparent py-3 text-left text-sm',
)}
>
<div
@@ -604,7 +604,7 @@ const Draft = memo(({ message }: { message: { id: string } }) => {
<div
key={message.id}
className={cn(
'hover:bg-offsetLight dark:hover:bg-primary/5 group relative mx-[8px] flex cursor-pointer flex-col items-start overflow-clip rounded-[10px] border-transparent py-3 text-left text-sm transition-all hover:opacity-100',
'hover:bg-offsetLight dark:hover:bg-primary/5 group relative mx-[8px] flex cursor-pointer flex-col items-start overflow-clip rounded-[10px] border-transparent py-3 text-left text-sm hover:opacity-100',
)}
>
<div

View File

@@ -526,7 +526,7 @@ export function NotesPanel({ threadId }: NotesPanelProps) {
{isOpen && (
<div
className="animate-in fade-in-20 zoom-in-95 dark:bg-panelDark fixed top-20 z-50 h-[calc(100dvh-5rem)] max-h-[calc(100dvh-5rem)] w-full max-w-screen overflow-hidden rounded-t-lg border border-t bg-[#FAFAFA] shadow-lg duration-100 sm:absolute sm:right-0 sm:top-full sm:mt-2 sm:h-auto sm:max-h-[80vh] sm:w-[350px] sm:max-w-[90vw] sm:rounded-xl sm:border lg:left-[-200px] xl:left-[-300px] dark:border-[#252525]"
className="animate-in fade-in-20 zoom-in-95 dark:bg-panelDark max-w-screen fixed top-20 z-50 h-[calc(100dvh-5rem)] max-h-[calc(100dvh-5rem)] w-full overflow-hidden rounded-t-lg border border-t bg-[#FAFAFA] shadow-lg duration-100 sm:absolute sm:right-0 sm:top-full sm:mt-2 sm:h-auto sm:max-h-[80vh] sm:w-[350px] sm:max-w-[90vw] sm:rounded-xl sm:border lg:left-[-200px] xl:left-[-300px] dark:border-[#252525]"
onClick={handlePanelClick}
>
<div className="sticky top-0 z-10 flex items-center justify-between border-b border-[#E7E7E7] p-3 dark:border-[#252525]">
@@ -706,7 +706,7 @@ export function NotesPanel({ threadId }: NotesPanelProps) {
<button
onClick={() => setSelectedColor(color.value)}
className={cn(
'h-5 w-5 rounded-full transition-all',
'h-5 w-5 rounded-full',
color.value === 'default' ? 'bg-background border' : '',
color.value === 'red' ? 'bg-red-500' : '',
color.value === 'orange' ? 'bg-orange-500' : '',

View File

@@ -1,8 +1,6 @@
import {
Archive,
ArchiveX,
ChevronLeft,
ChevronRight,
Folders,
Lightning,
Mail,
@@ -20,41 +18,37 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { EmptyStateIcon } from '../icons/empty-state-svg';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { useOptimisticThreadState } from '@/components/mail/optimistic-thread-state';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useOptimisticActions } from '@/hooks/use-optimistic-actions';
import { focusedIndexAtom } from '@/hooks/use-mail-navigation';
import { type ThreadDestination } from '@/lib/thread-actions';
import { handleUnsubscribe } from '@/lib/email-utils.client';
import { useThread, useThreads } from '@/hooks/use-threads';
import { useAISidebar } from '@/components/ui/ai-sidebar';
import { EmptyStateIcon } from '../icons/empty-state-svg';
import { ScrollArea } from '@/components/ui/scroll-area';
import type { ParsedMessage, Attachment } from '@/types';
import { useAnimations } from '@/hooks/use-animations';
import { AnimatePresence, motion } from 'motion/react';
import { MailDisplaySkeleton } from './mail-skeleton';
import { useTRPC } from '@/providers/query-provider';
import { useMutation } from '@tanstack/react-query';
import { useIsMobile } from '@/hooks/use-mobile';
import { Button } from '@/components/ui/button';
import { cleanHtml } from '@/lib/email-utils';
import { useParams } from 'react-router';
import ReplyCompose from './reply-composer';
import { NotesPanel } from './note-panel';
import { cn, FOLDERS } from '@/lib/utils';
import { m } from '@/paraglide/messages';
import MailDisplay from './mail-display';
import { useParams } from 'react-router';
import { Inbox } from 'lucide-react';
import { useQueryState } from 'nuqs';
import { format } from 'date-fns';
import { useAtom } from 'jotai';
import { toast } from 'sonner';
import { AnimatePresence, motion } from 'motion/react';
import { useAnimations } from '@/hooks/use-animations';
const formatFileSize = (size: number) => {
const sizeInMB = (size / (1024 * 1024)).toFixed(2);
@@ -85,7 +79,7 @@ export function ThreadDemo({ messages, isMobile }: ThreadDisplayProps) {
>
<div
className={cn(
'bg-offsetLight dark:bg-offsetDark relative flex flex-col overflow-hidden transition-all duration-300',
'bg-offsetLight dark:bg-offsetDark relative flex flex-col overflow-hidden duration-300',
isMobile ? 'h-full' : 'h-full',
!isMobile && !isFullscreen && 'rounded-r-lg',
isFullscreen ? 'fixed inset-0 z-50' : '',
@@ -97,10 +91,7 @@ export function ThreadDemo({ messages, isMobile }: ThreadDisplayProps) {
{[...(messages || [])].reverse().map((message, index) => (
<div
key={message.id}
className={cn(
'transition-all duration-200',
index > 0 && 'border-border border-t',
)}
className={cn('duration-200', index > 0 && 'border-border border-t')}
>
<MailDisplay
demo
@@ -171,9 +162,9 @@ export function ThreadDisplay() {
const [, items] = useThreads();
const [isStarred, setIsStarred] = useState(false);
const [isImportant, setIsImportant] = useState(false);
const [navigationDirection, setNavigationDirection] = useState<'previous' | 'next' | null>(null);
const animationsEnabled = useAnimations();
// Collect all attachments from all messages in the thread
@@ -199,34 +190,6 @@ export function ThreadDisplay() {
// Get optimistic state for this thread
const optimisticState = useOptimisticThreadState(id ?? '');
const handlePrevious = useCallback(() => {
if (!id || !items.length || focusedIndex === null) return;
if (focusedIndex > 0) {
const prevThread = items[focusedIndex - 1];
if (prevThread) {
// Clear draft and reply state when navigating to previous thread
setMode(null);
setActiveReplyId(null);
setDraftId(null);
setThreadId(prevThread.id);
setFocusedIndex(focusedIndex - 1);
if (animationsEnabled) {
setNavigationDirection('previous');
}
}
}
}, [
items,
id,
focusedIndex,
setThreadId,
setFocusedIndex,
setMode,
setActiveReplyId,
setDraftId,
animationsEnabled,
]);
const handleNext = useCallback(() => {
if (!id || !items.length || focusedIndex === null) return setThreadId(null);
if (focusedIndex < items.length - 1) {
@@ -751,7 +714,7 @@ export function ThreadDisplay() {
>
<div
className={cn(
'bg-panelLight dark:bg-panelDark relative flex flex-col overflow-hidden rounded-xl transition-all duration-300',
'bg-panelLight dark:bg-panelDark relative flex flex-col overflow-hidden rounded-xl duration-300',
isMobile ? 'h-full' : 'h-full',
!isMobile && !isFullscreen && 'rounded-r-lg',
isFullscreen ? 'fixed inset-0 z-50' : '',
@@ -831,52 +794,6 @@ export function ThreadDisplay() {
onClick={handleClose}
className="hidden md:flex"
/>
{/* <ThreadSubject subject={emailData.latest?.subject} /> */}
<div className="dark:bg-iconDark/20 relative h-3 w-0.5 rounded-full bg-[#E7E7E7]" />{' '}
<div className="flex items-center gap-1">
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={handlePrevious}
className="inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-md hover:bg-white md:hidden dark:hover:bg-[#313131]"
>
<ChevronLeft className="fill-iconLight dark:fill-iconDark h-3.5 w-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="bottom" className="bg-white dark:bg-[#313131]">
Previous email
</TooltipContent>
</Tooltip>
</TooltipProvider>
<ThreadActionButton
icon={ChevronLeft}
label="Previous email"
onClick={handlePrevious}
className="hidden md:flex"
/>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={handleNext}
className="inline-flex h-7 w-7 items-center justify-center gap-1 overflow-hidden rounded-md hover:bg-white md:hidden dark:hover:bg-[#313131]"
>
<ChevronRight className="fill-iconLight dark:fill-iconDark h-3.5 w-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="bottom" className="bg-white dark:bg-[#313131]">
Next email
</TooltipContent>
</Tooltip>
</TooltipProvider>
<ThreadActionButton
icon={ChevronRight}
label="Next email"
onClick={handleNext}
className="hidden md:flex"
/>
</div>
</div>
<div className="flex items-center gap-2">
<button
@@ -1013,10 +930,15 @@ export function ThreadDisplay() {
{animationsEnabled ? (
<AnimatePresence mode="wait" initial={false}>
<motion.div
key={id}
key={id}
initial={{
opacity: 0,
x: navigationDirection === 'previous' ? -25 : navigationDirection === 'next' ? 25 : 0,
x:
navigationDirection === 'previous'
? -25
: navigationDirection === 'next'
? 25
: 0,
}}
animate={{
opacity: 1,
@@ -1024,10 +946,15 @@ export function ThreadDisplay() {
}}
exit={{
opacity: 0,
x: navigationDirection === 'previous' ? 25 : navigationDirection === 'next' ? -25 : 0,
x:
navigationDirection === 'previous'
? 25
: navigationDirection === 'next'
? -25
: 0,
}}
transition={{
duration: 0.08,
duration: 0.08,
ease: [0.4, 0, 0.2, 1],
}}
onAnimationComplete={handleAnimationComplete}
@@ -1084,19 +1011,16 @@ interface MessageListProps {
isMobile: boolean;
}
const MessageList = ({
messages,
isFullscreen,
totalReplies,
allThreadAttachments,
mode,
const MessageList = ({
messages,
isFullscreen,
totalReplies,
allThreadAttachments,
mode,
activeReplyId,
isMobile
isMobile,
}: MessageListProps) => (
<ScrollArea
className={cn('flex-1', isMobile ? 'h-[calc(100%-1px)]' : 'h-full')}
type="auto"
>
<ScrollArea className={cn('flex-1', isMobile ? 'h-[calc(100%-1px)]' : 'h-full')} type="auto">
<div className="pb-4">
{(messages || []).map((message, index) => {
const isLastMessage = index === messages.length - 1;
@@ -1105,10 +1029,7 @@ const MessageList = ({
return (
<div
key={message.id}
className={cn(
'transition-all duration-200',
index > 0 && 'border-border border-t',
)}
className={cn('duration-200', index > 0 && 'border-border border-t')}
>
<MailDisplay
emailData={message}

View File

@@ -186,7 +186,7 @@ export function Navigation() {
<span className="ml-1 hidden lg:inline">GitHub</span>
</div>
<div className="flex items-center gap-1 text-sm">
<Star className="relative top-px size-4 fill-gray-400 transition-all duration-300 group-hover:fill-yellow-400 group-hover:drop-shadow-[0_0_8px_rgba(250,204,21,0.6)]" />
<Star className="relative top-px size-4 fill-gray-400 duration-300 group-hover:fill-yellow-400 group-hover:drop-shadow-[0_0_8px_rgba(250,204,21,0.6)]" />
<AnimatedNumber value={stars} className="font-medium text-white" />
</div>
</a>

View File

@@ -35,7 +35,7 @@ export function ThemeToggle({ className = '', showLabel = false }: ThemeTogglePr
return (
<button
onClick={handleThemeToggle}
className={`flex items-center rounded-md p-2 text-gray-600 transition-all hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white ${className}`}
className={`flex items-center rounded-md p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white ${className}`}
aria-label="Toggle theme"
>
{theme === 'dark' ? <MoonIcon className="opacity-60" /> : <SunIcon className="opacity-60" />}

View File

@@ -22,7 +22,7 @@ const AccordionTrigger = React.forwardRef<
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
'flex flex-1 items-center justify-between py-4 font-medium hover:underline [&[data-state=open]>svg]:rotate-180',
className,
)}
{...props}
@@ -40,7 +40,7 @@ const AccordionContent = React.forwardRef<
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm transition-all"
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
{...props}
>
<div className={cn('pb-4 pt-0', className)}>{children}</div>

View File

@@ -7,27 +7,31 @@ import {
} from '@/components/ui/dialog';
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader } from '@/components/ui/sidebar';
import { navigationConfig, bottomNavItems } from '@/config/navigation';
import React, { useMemo, useState } from 'react';
import { useSession } from '@/lib/auth-client';
import { useTRPC } from '@/providers/query-provider';
import { useSidebar } from '@/components/ui/sidebar';
import { CreateEmail } from '../create/create-email';
import { useMutation } from '@tanstack/react-query';
import { PencilCompose, X } from '../icons/icons';
import { useBilling } from '@/hooks/use-billing';
import { useIsMobile } from '@/hooks/use-mobile';
import React, { useMemo, useState } from 'react';
import { Button } from '@/components/ui/button';
import { useSession } from '@/lib/auth-client';
import { useAIFullScreen } from './ai-sidebar';
import { useStats } from '@/hooks/use-stats';
import { useLocation } from 'react-router';
import { m } from '@/paraglide/messages';
import { FOLDERS } from '@/lib/utils';
import { Video } from 'lucide-react';
import { NavUser } from './nav-user';
import { NavMain } from './nav-main';
import { useQueryState } from 'nuqs';
import { toast } from 'sonner';
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const { isPro, isLoading } = useBilling();
const trpc = useTRPC();
const { mutateAsync: createMeet } = useMutation(trpc.meet.create.mutationOptions());
const [showUpgrade, setShowUpgrade] = useState(() => {
if (typeof window !== 'undefined') {
return localStorage.getItem('hideUpgradeCard') !== 'true';
@@ -75,6 +79,19 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const showComposeButton = currentSection === 'mail';
const { state } = useSidebar();
const handleCreateMeet = async () => {
try {
const {
data: { id },
} = await createMeet();
navigator.clipboard.writeText(`https://meet.0.email/${id}`);
toast.success('Meeting linked copied to clipboard');
} catch (error) {
console.error(error);
toast.error('Failed to create meeting');
}
};
return (
<div>
{!isFullScreen && (
@@ -89,8 +106,16 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
{session && <NavUser />}
{showComposeButton && (
<div>
<ComposeButton />
<div className="flex gap-1">
<div className="w-[80%]">
<ComposeButton />
</div>
<button
onClick={handleCreateMeet}
className="hover:bg-muted-foreground/10 inline-flex h-8 w-[20%] items-center justify-center gap-1 overflow-hidden rounded-lg border bg-white px-1.5 dark:border-none dark:bg-[#313131]"
>
<Video className="text-muted-foreground h-4 w-4" />
</button>
</div>
)}
</SidebarHeader>

View File

@@ -10,10 +10,7 @@ const InputOTP = React.forwardRef<
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
'flex items-center gap-2 has-disabled:opacity-50',
containerClassName,
)}
containerClassName={cn('flex items-center gap-2 has-disabled:opacity-50', containerClassName)}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
@@ -39,7 +36,7 @@ const InputOTPSlot = React.forwardRef<
<div
ref={ref}
className={cn(
'border-input relative flex h-10 w-10 items-center justify-center border-y border-r text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
'border-input relative flex h-10 w-10 items-center justify-center border-y border-r text-sm first:rounded-l-md first:border-l last:rounded-r-md',
isActive && 'ring-ring ring-offset-background z-10 ring-2',
className,
)}

View File

@@ -13,7 +13,7 @@ const Progress = React.forwardRef<
{...props}
>
<ProgressPrimitive.Indicator
className="bg-primary h-full w-full flex-1 transition-all"
className="bg-primary h-full w-full flex-1"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>

View File

@@ -39,7 +39,7 @@ const Sidebar = React.forwardRef<
return (
<div
className={cn(
'text-sidebar-foreground bg-sidebar dark:bg-sidebar flex h-full w-(--sidebar-width) flex-col',
'text-sidebar-foreground bg-sidebar dark:bg-sidebar w-(--sidebar-width) flex h-full flex-col',
className,
)}
ref={ref}
@@ -86,7 +86,7 @@ const Sidebar = React.forwardRef<
{/* This is what handles the sidebar gap on desktop */}
<div
className={cn(
'relative h-svh w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-in-out',
'w-(--sidebar-width) relative h-svh bg-transparent transition-[width] duration-200 ease-in-out',
'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset'
@@ -96,7 +96,7 @@ const Sidebar = React.forwardRef<
/>
<div
className={cn(
'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-in-out md:flex',
'w-(--sidebar-width) fixed inset-y-0 z-10 hidden h-svh transition-[left,right,width] duration-200 ease-in-out md:flex',
side === 'left'
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
@@ -110,7 +110,7 @@ const Sidebar = React.forwardRef<
>
<div
data-sidebar="sidebar"
className="group-data-[variant=floating]:border-sidebar-border bg-sidebar dark:bg-sidebar flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow transition-all duration-300 ease-in-out overflow-hidden"
className="group-data-[variant=floating]:border-sidebar-border bg-sidebar dark:bg-sidebar flex h-full w-full flex-col overflow-hidden duration-300 ease-in-out group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow"
>
{children}
</div>
@@ -160,7 +160,7 @@ const SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<'bu
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-in-out after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 ease-in-out after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
'group-data-[collapsible=offcanvas]:hover:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
@@ -276,7 +276,7 @@ const SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'
<div
ref={ref}
data-sidebar="group"
className={cn('relative flex w-full min-w-0 flex-col pt-0! md:p-2', className)}
className={cn('pt-0! relative flex w-full min-w-0 flex-col md:p-2', className)}
{...props}
/>
);
@@ -524,7 +524,7 @@ const SidebarMenuSkeleton = React.forwardRef<
>
{showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
<Skeleton
className="h-4 max-w-(--skeleton-width) flex-1"
className="max-w-(--skeleton-width) h-4 flex-1"
data-sidebar="menu-skeleton-text"
style={
{

View File

@@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center whitespace-nowrap rounded-sm px-2 py-px text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm',
'ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center whitespace-nowrap rounded-sm px-2 py-px text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm',
className,
)}
{...props}

View File

@@ -91,6 +91,8 @@ export type ZeroEnv = {
VECTORIZE: VectorizeIndex;
VECTORIZE_MESSAGE: VectorizeIndex;
DEV_PROXY: string;
MEET_AUTH_HEADER: string;
MEET_API_URL: string;
};
const env = _env as ZeroEnv;

View File

@@ -507,17 +507,19 @@ const getCounts = async (connectionId: string): Promise<CountResult[]> => {
*/
export const sendDoState = async (connectionId: string) => {
try {
const registry = await getRegistryClient(connectionId);
const agent = await getZeroSocketAgent(connectionId);
const size = await getDatabaseSize(connectionId);
const counts = await getCounts(connectionId);
const [registry, agent, size, counts] = await Promise.all([
getRegistryClient(connectionId),
getZeroSocketAgent(connectionId),
getDatabaseSize(connectionId),
getCounts(connectionId),
]);
const shards = await listShards(registry);
return await agent.broadcastChatMessage({
return agent.broadcastChatMessage({
type: OutgoingMessageType.Do_State,
isSyncing: false,
syncingFolders: ['inbox'],
storageSize: size,
counts: counts,
counts,
shards: shards.length,
});
} catch (error) {

View File

@@ -143,14 +143,19 @@ export async function countThreads(db: DB): Promise<number> {
return result.count;
}
export async function countThreadsByLabel(db: DB, labelId: string): Promise<number> {
const [result] = await db
.select({ count: count() })
.from(threads)
.innerJoin(threadLabels, eq(threads.id, threadLabels.threadId))
.where(eq(threadLabels.labelId, labelId));
export async function countThreadsByLabels(
db: DB,
labelIds: string[],
): Promise<{ labelId: string; count: number }[]> {
if (labelIds.length === 0) return [];
return result.count;
const results = await db
.select({ labelId: threadLabels.labelId, count: count() })
.from(threadLabels)
.where(inArray(threadLabels.labelId, labelIds))
.groupBy(threadLabels.labelId);
return results;
}
export async function createThreadLabel(

View File

@@ -16,7 +16,7 @@
import {
countThreads,
countThreadsByLabel,
countThreadsByLabels,
deleteSpamThreads,
get,
getThreadLabels,
@@ -323,7 +323,6 @@ export class ZeroDriver extends DurableObject<ZeroEnv> {
transfer = new Transfer(this);
sql: SqlStorage;
private db: DB;
// private foldersInSync: Map<string, boolean> = new Map();
private syncThreadsInProgress: Map<string, boolean> = new Map();
private driver: MailManager | null = null;
private agent: DurableObjectStub<ZeroAgent> | null = null;
@@ -339,7 +338,6 @@ export class ZeroDriver extends DurableObject<ZeroEnv> {
this.name = name;
await this.ctx.blockConcurrencyWhile(async () => {
await this.setupAuth();
// await this.syncFolders();
});
}
@@ -349,22 +347,6 @@ export class ZeroDriver extends DurableObject<ZeroEnv> {
async isSyncing(): Promise<boolean> {
return false;
// try {
// const coordinatorInstance = await this.env.SYNC_THREADS_COORDINATOR_WORKFLOW.get(`${this.name}-inbox-coordinator`);
// const coordinatorStatus = (await coordinatorInstance.status()).status;
// if (['running', 'queued', 'waiting'].includes(coordinatorStatus)) {
// return true;
// }
// } catch {
// }
// try {
// const workflowInstance = await this.env.SYNC_THREADS_WORKFLOW.get(`${this.name}-inbox`);
// const status = (await workflowInstance.status()).status;
// return ['running', 'queued', 'waiting'].includes(status);
// } catch {
// return false;
// }
}
async getAllSubjects() {
@@ -846,13 +828,14 @@ export class ZeroDriver extends DurableObject<ZeroEnv> {
// Additional mail operations
async count() {
const folders = ['inbox', 'sent', 'spam', 'archive', 'trash'];
const counts = await Promise.all(
folders.map(async (folder) => {
const count = await this.getFolderThreadCount(folder.toUpperCase());
return { label: folder, count };
}),
const results = await countThreadsByLabels(
this.db,
folders.map((f) => f.toUpperCase()),
);
return counts;
const resultMap = new Map(
results.map((r: { labelId: string; count: number }) => [r.labelId, r.count]),
);
return folders.map((f) => ({ label: f, count: resultMap.get(f.toUpperCase()) ?? 0 }));
}
private getThreadKey(threadId: string) {
@@ -1002,22 +985,6 @@ export class ZeroDriver extends DurableObject<ZeroEnv> {
return count || 0;
}
async getFolderThreadCount(folder: string) {
const count = await countThreadsByLabel(this.db, folder);
return count || 0;
}
// async sendDoState() {
// const isSyncing = await this.isSyncing();
// return this.agent?.broadcastChatMessage({
// type: OutgoingMessageType.Do_State,
// isSyncing,
// syncingFolders: isSyncing ? ['inbox'] : [],
// storageSize: this.getDatabaseSize(),
// counts: await this.count(),
// });
// }
async inboxRag(query: string) {
if (!this.env.AUTORAG_ID) {
console.warn('[inboxRag] AUTORAG_ID not configured - RAG search disabled');

View File

@@ -1,3 +1,19 @@
/*
* Licensed to Zero Email Inc. under one or more contributor license agreements.
* You may not use this file except in compliance with the Apache License, Version 2.0 (the "License").
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Reuse or distribution of this file requires a license from Zero Email Inc.
*/
import type { IGetThreadResponse } from '../lib/driver/types';
import { workflowFunctions } from './workflow-functions';
import { shouldGenerateDraft } from './index';

View File

@@ -1,3 +1,18 @@
/*
* Licensed to Zero Email Inc. under one or more contributor license agreements.
* You may not use this file except in compliance with the Apache License, Version 2.0 (the "License").
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Reuse or distribution of this file requires a license from Zero Email Inc.
*/
import {
SummarizeMessage,
ReSummarizeThread,

View File

@@ -1,6 +1,8 @@
import { type inferRouterInputs, type inferRouterOutputs } from '@trpc/server';
import { cookiePreferencesRouter } from './routes/cookies';
import { connectionsRouter } from './routes/connections';
import { categoriesRouter } from './routes/categories';
import { templatesRouter } from './routes/templates';
import { shortcutRouter } from './routes/shortcut';
import { settingsRouter } from './routes/settings';
import { getContext } from 'hono/context-storage';
@@ -9,13 +11,12 @@ import { labelsRouter } from './routes/label';
import { notesRouter } from './routes/notes';
import { brainRouter } from './routes/brain';
import { userRouter } from './routes/user';
import { meetRouter } from './routes/meet';
import { mailRouter } from './routes/mail';
import { bimiRouter } from './routes/bimi';
import type { HonoContext } from '../ctx';
import { aiRouter } from './routes/ai';
import { router } from './trpc';
import { categoriesRouter } from './routes/categories';
import { templatesRouter } from './routes/templates';
export const appRouter = router({
ai: aiRouter,
@@ -32,6 +33,7 @@ export const appRouter = router({
settings: settingsRouter,
user: userRouter,
templates: templatesRouter,
meet: meetRouter,
});
export type AppRouter = typeof appRouter;

View File

@@ -0,0 +1,39 @@
import { activeDriverProcedure, router } from '../trpc';
import { TRPCError } from '@trpc/server';
import { env } from '../../env';
type MeetResponse = {
success: boolean;
data: {
created_at: string;
id: string;
is_large: boolean;
live_stream_on_start: boolean;
persist_chat: boolean;
record_on_start: boolean;
status: string;
summarize_on_end: boolean;
updated_at: string;
};
};
export const meetRouter = router({
create: activeDriverProcedure.mutation(async () => {
const AuthHeader = env.MEET_AUTH_HEADER;
const response = await fetch(env.MEET_API_URL + '/meetings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: AuthHeader,
},
});
if (!response.ok) {
console.error(await response.text());
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to create meeting' });
}
const data = await response.json<MeetResponse>();
return data;
}),
});

View File

@@ -1,3 +1,18 @@
/*
* Licensed to Zero Email Inc. under one or more contributor license agreements.
* You may not use this file except in compliance with the Apache License, Version 2.0 (the "License").
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Reuse or distribution of this file requires a license from Zero Email Inc.
*/
import { WorkflowEntrypoint, WorkflowStep } from 'cloudflare:workers';
import { connectionToDriver } from '../lib/server-utils';
import type { WorkflowEvent } from 'cloudflare:workers';

View File

@@ -1,3 +1,18 @@
/*
* Licensed to Zero Email Inc. under one or more contributor license agreements.
* You may not use this file except in compliance with the Apache License, Version 2.0 (the "License").
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Reuse or distribution of this file requires a license from Zero Email Inc.
*/
import { getZeroAgent, connectionToDriver, sendDoState } from '../lib/server-utils';
import { WorkflowEntrypoint, WorkflowStep } from 'cloudflare:workers';
import type { WorkflowEvent } from 'cloudflare:workers';

View File

@@ -172,11 +172,12 @@
"DROP_AGENT_TABLES": "false",
"THREAD_SYNC_MAX_COUNT": "60",
"THREAD_SYNC_LOOP": "false",
"DISABLE_WORKFLOWS": "false",
"DISABLE_WORKFLOWS": "true",
"AUTORAG_ID": "",
"USE_OPENAI": "true",
"CLOUDFLARE_ACCOUNT_ID": "",
"CLOUDFLARE_API_TOKEN": "",
"MEET_AUTH_HEADER": "",
},
"kv_namespaces": [
{