Skip to content

Commit fd6207f

Browse files
committed
Merge branch 'develop' into feat-profile-settings-clean
2 parents d3ebdca + a43af86 commit fd6207f

File tree

15 files changed

+1213
-417
lines changed

15 files changed

+1213
-417
lines changed

.github/copilot-instructions.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# GitHub Copilot Instructions
2+
3+
## Development Philosophy & Environment Awareness
4+
5+
### Understanding Your Working Environment
6+
7+
Before implementing solutions, always consider the environment and tools you're working with:
8+
9+
**Browser/DOM Environment:**
10+
11+
- DOM operations are asynchronous and may require timing considerations
12+
- Browser APIs like `scrollTo()` can be unreliable without proper fallbacks
13+
- Always verify that DOM manipulations actually occurred (don't assume success)
14+
- Use multiple approaches: try modern APIs first, then fallback to legacy methods
15+
- Consider render timing - operations may need delays or `requestAnimationFrame`
16+
17+
**React/Component Environment:**
18+
19+
- State updates are asynchronous and may cause re-renders that interfere with DOM operations
20+
- useEffect dependencies can cause infinite loops if not carefully managed
21+
- Refs provide stable references to DOM elements across renders
22+
- Consider component lifecycle when timing DOM operations
23+
24+
**General Environment Principles:**
25+
26+
- **Test assumptions**: Don't assume an API call worked - verify the result
27+
- **Provide fallbacks**: Always have backup approaches for critical functionality
28+
- **Add debugging**: Include logging to understand what's actually happening
29+
- **Consider timing**: Many issues are timing-related, especially with async operations
30+
- **Understand constraints**: Each environment has limitations (browser security, React lifecycle, etc.)
31+
32+
### Problem-Solving Approach
33+
34+
1. **Analyze the environment** - What tools, frameworks, and constraints are you working with?
35+
2. **Understand the flow** - How do data, events, and updates move through the system?
36+
3. **Identify failure points** - Where might things go wrong? What are the edge cases?
37+
4. **Design resilient solutions** - Include error handling, fallbacks, and verification
38+
5. **Add observability** - Include logging/debugging to understand actual behavior
39+
6. **Test incrementally** - Verify each step works before building on it
40+
41+
### Implementation Guidelines
42+
43+
**For DOM/Browser Work:**
44+
45+
- Use feature detection before modern APIs
46+
- Implement progressive enhancement (basic functionality first, enhancements after)
47+
- Add timing delays or RAF when DOM needs to settle
48+
- Verify operations completed successfully
49+
50+
**For React/State Management:**
51+
52+
- Minimize useEffect dependencies to prevent loops
53+
- Use refs for values that shouldn't trigger re-renders
54+
- Consider component lifecycle timing for DOM operations
55+
- Separate concerns: state updates vs DOM manipulation
56+
57+
**For Any Environment:**
58+
59+
- Start with the simplest approach that could work
60+
- Add complexity only when simple approaches fail
61+
- Document why complex solutions are needed
62+
- Make code readable - future developers need to understand the constraints you solved for
63+
64+
This approach applies whether working with databases, APIs, file systems, mobile environments, or any other development context. The key is understanding your tools and environment before implementing solutions.
65+
66+
## Masterbots Monorepo Architecture
67+
68+
### Understanding the System Architecture
69+
70+
The masterbots platform follows modern web application patterns with clear separation of concerns:
71+
72+
**Core Applications:**
73+
74+
- `apps/web/` - Main Next.js chat interface
75+
- `apps/pro-web/` - Pro workspace editor (current focus)
76+
- `apps/hasura/` - GraphQL API and database layer
77+
78+
**Shared Packages:**
79+
80+
- `packages/mb-genql/` - Generated GraphQL client for type-safe API calls
81+
- `packages/mb-lib/` - Shared utilities and helpers
82+
- `packages/mb-types/` - Common TypeScript type definitions
83+
- `packages/mb-env/` - Environment configuration
84+
- `packages/mb-drizzle/` - Database schema and migrations
85+
86+
### State Management Patterns
87+
88+
When working with the masterbots codebase, follow established patterns:
89+
90+
**Provider Architecture:**
91+
92+
- Use hierarchical provider structure for global state
93+
- Custom hooks (`useMBChat`, `useThread`, `useSidebar`, `useModel`) manage domain-specific concerns
94+
- Keep providers focused and compose them hierarchically
95+
96+
**Data Flow:**
97+
98+
- Follow unidirectional data flow: User Input → Component State → Custom Hooks → Server Actions → Database
99+
- Use `hasura.service.ts` for all GraphQL operations
100+
- Leverage IndexedDB for local caching and immediate UI updates
101+
102+
**Component Composition:**
103+
104+
- Build complex components by composing smaller, focused components
105+
- Separate presentation from business logic
106+
- Use custom hooks to encapsulate complex state logic
107+
108+
### AI Integration Environment
109+
110+
The platform integrates multiple AI providers with specific patterns:
111+
112+
**Model Management:**
113+
114+
- Use `getModelClientType()` to determine appropriate AI client
115+
- Route through `ai-main-call.actions` for unified AI API handling
116+
- Support multiple providers (OpenAI, Anthropic, etc.) through consistent interfaces
117+
118+
**Chat System:**
119+
120+
- `useMBChat` orchestrates all chat functionality
121+
- Integrate with AI SDK's `useChat` hook for streaming responses
122+
- Handle file attachments through hybrid storage (IndexedDB + Cloud Storage)
123+
124+
### GraphQL Integration
125+
126+
All data operations follow consistent patterns:
127+
128+
**Service Layer:**
129+
130+
- Use `hasura.service.ts` as the single point of GraphQL interaction
131+
- Generated types from `mb-genql` ensure type safety
132+
- Abstract Hasura-specific details behind service methods
133+
134+
**Error Handling:**
135+
136+
- GraphQL operations can fail at network, parsing, or business logic levels
137+
- Always handle partial success scenarios
138+
- Provide meaningful fallbacks for degraded functionality
139+
140+
### Development Guidelines
141+
142+
**File Organization:**
143+
144+
- Components: `components/routes/[feature]/` for page-specific components
145+
- Shared Components: `components/shared/` for reusable UI elements
146+
- Hooks: `lib/hooks/` for custom React hooks
147+
- Services: `services/` for external API integrations
148+
- Types: `types/` for TypeScript definitions
149+
150+
**External Documentation:**
151+
152+
- [DeepWiki Documentation](https://deepwiki.com/bitcashorg/masterbots) - Comprehensive system overview
153+
- [Hasura GraphQL](https://hasura.io/docs/) - GraphQL API patterns
154+
- [Next.js App Router](https://nextjs.org/docs/app) - Routing and server components
155+
- [AI SDK](https://sdk.vercel.ai/docs) - AI integration patterns
156+
157+
**Working with Pro Workspace:**
158+
159+
- Editor components follow controlled/uncontrolled patterns
160+
- Markdown processing uses dedicated utility functions
161+
- Section management requires careful state synchronization
162+
- Auto-scroll and DOM manipulation need timing considerations (as demonstrated in recent work)
163+
164+
Remember: Each layer of the stack has its own constraints and capabilities. Always consider the full data flow from user interaction to database persistence when implementing features.

apps/pro-web/components/layout/header/crumb-header.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export function DocumentCrumb({
184184
}: {
185185
activeProject: string
186186
activeDocument: string
187-
userDocuments: WorkspaceDocumentMetadata[]
187+
userDocuments: { name: string }[]
188188
activeThread: Thread | null
189189
documentOptions: string[]
190190
threadDocsByName: Map<string, { type?: 'text' | 'image' | 'spreadsheet' }>
@@ -253,7 +253,7 @@ export function DocumentCrumb({
253253
return <FileSpreadsheetIcon className="size-4" />
254254
return null
255255
})()
256-
) : opt !== 'None' && threadDocsByName.size > 0 ? (
256+
) : opt !== 'None' ? (
257257
<span className="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground">
258258
Draft
259259
</span>

apps/pro-web/components/layout/header/header.tsx

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ import { getUserIndexedDBKeys } from '@/lib/hooks/use-chat-attachments'
1919
import { type IndexedDBItem, useIndexedDB } from '@/lib/hooks/use-indexed-db'
2020
import { useSidebar } from '@/lib/hooks/use-sidebar'
2121
import { useThread } from '@/lib/hooks/use-thread'
22-
import { useThreadDocuments } from '@/lib/hooks/use-thread-documents'
2322
import { useWorkspace } from '@/lib/hooks/use-workspace'
23+
import { useWorkspaceDocuments } from '@/lib/hooks/use-workspace-documents'
2424
import { getCanonicalDomain } from '@/lib/url'
2525
import { cn, getRouteColor, getRouteType } from '@/lib/utils'
2626

2727
import type { WorkspaceDocumentMetadata } from '@/types/thread.types'
28-
import { uniq } from 'lodash'
28+
import { pick, uniq } from 'lodash'
2929
import { nanoid } from 'nanoid'
3030
import { useSession } from 'next-auth/react'
3131
import { useTheme } from 'next-themes'
@@ -85,6 +85,7 @@ function HeaderLink({
8585
export function Header() {
8686
const { activeCategory, activeChatbot, setActiveCategory, setActiveChatbot } =
8787
useSidebar()
88+
const workspaceContext = useWorkspace()
8889
const {
8990
activeOrganization,
9091
activeDepartment,
@@ -111,7 +112,7 @@ export function Header() {
111112
addDepartment,
112113
addProject,
113114
addDocument,
114-
} = useWorkspace()
115+
} = workspaceContext
115116
const {
116117
isOpenPopup,
117118
activeThread,
@@ -128,13 +129,10 @@ export function Header() {
128129
() => getUserIndexedDBKeys(session?.user?.id),
129130
[session?.user?.id],
130131
)
131-
const { getItem, getAllItemsRaw } = useIndexedDB(dbKeys) as unknown as {
132-
getItem: (id: IDBValidKey) => Promise<IndexedDBItem>
133-
getAllItemsRaw: () => Promise<IndexedDBItem[]>
134-
}
132+
const { getItem } = useIndexedDB(dbKeys)
135133

136134
// Unified thread documents (from metadata + local IDB where applicable)
137-
const { userDocuments } = useThreadDocuments()
135+
const { userDocuments } = useWorkspaceDocuments(workspaceContext)
138136

139137
// Ensure thread documents metadata is hydrated even if popup is closed
140138
const attemptedDocsRefreshRef = React.useRef<string | null>(null)
@@ -204,34 +202,23 @@ export function Header() {
204202
const { documentOptions, threadDocsByName } = useMemo(() => {
205203
// Helper to dedupe while preserving order
206204
const dedupe = (arr: string[]) => Array.from(new Set(arr))
207-
console.log('userDocuments', userDocuments)
208-
// Prefer documents from unified hook; fall back to thread metadata when empty
209-
const threadDocs = (
210-
userDocuments?.length
211-
? userDocuments
212-
: (activeThread?.metadata
213-
?.documents as Array<WorkspaceDocumentMetadata>) || []
214-
) as Array<WorkspaceDocumentMetadata>
215-
216205
const byName = new Map<string, WorkspaceDocumentMetadata>()
217206

218-
if (threadDocs.length) {
207+
if (userDocuments.length) {
219208
// Filter by current type (unless 'all') and activeProject (if set)
220-
const filtered = threadDocs.filter((d) => {
209+
const filteredDocuments = userDocuments.filter((d) => {
221210
const nameOk = Boolean(d?.name)
222211
const typeOk =
223-
activeDocumentType === 'all' ||
224-
!d?.type ||
225-
d?.type === activeDocumentType
212+
activeDocumentType === 'all' || d?.type === activeDocumentType
226213
return nameOk && typeOk
227214
})
228215

229-
for (const d of filtered) {
230-
const name = (d.name || '') as string
216+
for (const document of filteredDocuments) {
217+
const name = (document.name || '') as string
231218
if (!name) continue
232219
if (!byName.has(name)) {
233220
byName.set(name, {
234-
...d,
221+
...document,
235222
name,
236223
})
237224
}
@@ -296,7 +283,6 @@ export function Header() {
296283
}
297284
}, [
298285
userDocuments,
299-
activeThread?.metadata?.documents,
300286
activeProject,
301287
activeDocumentType,
302288
textDocuments,
@@ -469,12 +455,8 @@ export function Header() {
469455
...userDocuments,
470456
...Object.values(documentList)
471457
.flat()
472-
.map((name) => ({ name }) as unknown as WorkspaceDocumentMetadata),
473-
]).filter((document) =>
474-
activeThread
475-
? document.threadSlug === activeThread.slug || !document.versions
476-
: true,
477-
)
458+
.map((name) => ({ name }) as { name: string }),
459+
])
478460

479461
return (
480462
<header className="sticky top-0 z-50 flex items-center justify-between w-full h-16 px-4 border-b shrink-0 bg-gradient-to-b from-background/10 via-background/50 to-background/80 backdrop-blur-xl">
@@ -618,21 +600,3 @@ export function Header() {
618600
</header>
619601
)
620602
}
621-
622-
// Narrow unknown IndexedDB records that represent workspace documents
623-
function isWorkspaceDocItem(it: unknown): it is {
624-
id?: string
625-
project?: string
626-
name?: string
627-
type?: 'text' | 'image' | 'spreadsheet'
628-
url?: string
629-
content?: string
630-
} {
631-
if (!it || typeof it !== 'object') return false
632-
const o = it as Record<string, unknown>
633-
return (
634-
typeof o.project === 'string' &&
635-
typeof o.name === 'string' &&
636-
(o.type === 'text' || o.type === 'image' || o.type === 'spreadsheet')
637-
)
638-
}

apps/pro-web/components/routes/chat/chat-layout-section.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@ export function ChatLayoutSection({ children }: { children: React.ReactNode }) {
1616
const { activeChatbot, isDashboardOpen, setIsDashboardOpen, allCategories } =
1717
useSidebar()
1818
const pathname = usePathname()
19-
const isPublic = getRouteType(pathname) === 'public'
19+
const isOrg = getRouteType(pathname) === 'org'
2020
const chatClassNames = activeChatbot
2121
? 'max-h-[97vh]'
2222
: 'max-h-[calc(97vh-26px)]'
2323

24-
console.log('isOpenPopup', isOpenPopup)
25-
2624
return (
2725
<>
2826
{isDashboardOpen && (
@@ -34,11 +32,20 @@ export function ChatLayoutSection({ children }: { children: React.ReactNode }) {
3432
/>
3533
</div>
3634
)}
37-
38-
<div className="flex flex-col gap-5 px-4 pt-5 mx-auto w-full max-w-screen-xl h-full md:px-10">
39-
{children}
40-
</div>
41-
{isOpenPopup && <ThreadPopup />}
35+
<section
36+
ref={sectionRef as React.Ref<HTMLDivElement>}
37+
className={cn(
38+
isOrg ? 'max-h-full md:max-h-[calc(97vh-100px)]' : chatClassNames,
39+
'flex h-full group w-full overflow-auto animate-in duration-300 ease-in-out relative',
40+
'lg:w-[calc(100%-250px)] xl:w-[calc(100%-300px)] lg:ml-[250px] xl:ml-[300px]',
41+
'scrollbar',
42+
)}
43+
>
44+
<div className="flex flex-col gap-5 px-4 pt-5 mx-auto w-full max-w-screen-xl h-full md:px-10">
45+
{children}
46+
</div>
47+
{isOpenPopup && <ThreadPopup />}
48+
</section>
4249
</>
4350
)
4451
}

apps/pro-web/components/routes/chat/chat-message-actions.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,7 @@ export function ChatMessageActions({
105105
setDocumentContent(activeProject, docTitle, structuredContent)
106106
setActiveDocument(docTitle)
107107

108-
// Enable workspace mode if not already active
109-
if (!isWorkspaceActive) {
110-
toggleWorkspace()
111-
}
108+
toggleWorkspace(true)
112109

113110
customSonner({
114111
type: 'success',
@@ -131,7 +128,7 @@ export function ChatMessageActions({
131128
return (
132129
<div
133130
className={cn(
134-
'flex items-center justify-end transition-opacity group-hover:opacity-100 md:absolute md:-right-10 md:-top-2 md:opacity-0',
131+
'flex gap-1.5 items-center justify-end transition-opacity group-hover:opacity-100 md:absolute md:-right-10 md:-top-2 md:opacity-0',
135132
className,
136133
)}
137134
key={messageId}

0 commit comments

Comments
 (0)