Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ import { AnonymousSessionHandler, SidebarCoordinator } from "@/components/layout
import { PdfEngineWrapper } from "@/components/pdf/PdfEngineWrapper";
import WorkspaceSettingsModal from "@/components/workspace/WorkspaceSettingsModal";
import ShareWorkspaceDialog from "@/components/workspace/ShareWorkspaceDialog";
import { WorkspaceInstructionModal } from "@/components/onboarding/WorkspaceInstructionModal";
import { RealtimeProvider } from "@/contexts/RealtimeContext";
import { toast } from "sonner";
import { useWorkspaceInstructionModal } from "@/hooks/workspace/use-workspace-instruction-modal";

import { InviteGuard } from "@/components/workspace/InviteGuard";
import { useReactiveNavigation } from "@/hooks/ui/use-reactive-navigation";
Expand Down Expand Up @@ -107,6 +109,14 @@ function DashboardContent({
// Workspace settings/share modals (lifted so header can open them)
const [showWorkspaceSettings, setShowWorkspaceSettings] = useState(false);
const [showWorkspaceShare, setShowWorkspaceShare] = useState(false);
const [assistantThreadRunning, setAssistantThreadRunning] = useState<boolean | null>(null);

const instructionModal = useWorkspaceInstructionModal({
workspaceId: currentWorkspaceId,
userId: session?.user?.id ?? null,
assistantIsRunning: assistantThreadRunning,
analytics: posthog ?? null,
});

// Show sign-in prompt after 25 events for anonymous users
useEffect(() => {
Expand Down Expand Up @@ -415,6 +425,7 @@ function DashboardContent({
onWorkspaceSizeChange={setWorkspacePanelSize}
onSingleSelect={handleCreateInstantNote}
onMultiSelect={handleCreateCardFromSelections}
onAssistantThreadRunningChange={setAssistantThreadRunning}
panels={panels}
workspaceSection={
<WorkspaceSection
Expand Down Expand Up @@ -455,6 +466,17 @@ function DashboardContent({
modalManager={modalManagerElement}
maximizedItemId={maximizedItemId}
/>
<WorkspaceInstructionModal
mode={instructionModal.mode ?? "first-open"}
open={instructionModal.open}
canClose={instructionModal.canClose}
showFallback={instructionModal.showFallback}
isGenerating={instructionModal.isGenerating}
onRequestClose={instructionModal.close}
onFallbackContinue={instructionModal.continueFromFallback}
onUserInteracted={instructionModal.markInteracted}
useStaticFallback={true}
/>
Comment on lines +469 to +479

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "WorkspaceInstructionModal.tsx" -o -name "WorkspaceInstructionModal.ts"

Repository: ThinkEx-OSS/thinkex

Length of output: 120


🏁 Script executed:

wc -l ./src/components/onboarding/WorkspaceInstructionModal.tsx

Repository: ThinkEx-OSS/thinkex

Length of output: 124


🏁 Script executed:

cat -n ./src/components/onboarding/WorkspaceInstructionModal.tsx

Repository: ThinkEx-OSS/thinkex

Length of output: 21193


Remove unused useStaticFallback and mediaSrc props from WorkspaceInstructionModal.

Both props are declared in the interface (lines 18–19) but never destructured or referenced in the component body. Either remove them from the interface and call site, or implement their intended functionality.

🤖 Prompt for AI Agents
In `@src/app/dashboard/page.tsx` around lines 469 - 479, The
WorkspaceInstructionModal props include unused useStaticFallback and mediaSrc;
remove them from the component props interface/type and from the call site in
page.tsx (delete useStaticFallback={true} and any mediaSrc usage) and also
remove any unused destructuring of these names inside the
WorkspaceInstructionModal implementation (or alternatively implement their
behavior if intended), ensuring the component props type and all JSX invocations
only include actually used props such as mode, open, canClose, showFallback,
isGenerating, onRequestClose, onFallbackContinue, and onUserInteracted.


<WorkspaceSettingsModal
workspace={currentWorkspace}
Expand Down
15 changes: 15 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1449,4 +1449,19 @@ body:has(.card-detail-modal) .workspace-grid-container {
to {
opacity: 1;
}
}

/* Liquid glass shimmer sweep */
@keyframes glass-shimmer {
0% {
transform: translateX(-100%);
}

50% {
transform: translateX(100%);
}

100% {
transform: translateX(100%);
}
}
26 changes: 23 additions & 3 deletions src/components/assistant-ui/AssistantPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { DevToolsModal } from "@assistant-ui/react-devtools";
import { useAui } from "@assistant-ui/react";
import { useAui, useAuiState } from "@assistant-ui/react";
import { Thread } from "./thread";
import { useWorkspaceState } from "@/hooks/workspace/use-workspace-state";
import { useWorkspaceContextProvider } from "@/hooks/ai/use-workspace-context-provider";
Expand All @@ -22,6 +22,7 @@ interface AssistantPanelProps {
onSingleSelect?: (text: string, range?: Range) => void | Promise<void>;
onMultiSelect?: (selections: Array<{ text: string; id: string; range?: Range }>) => void | Promise<void>;
onReady?: () => void;
onThreadRunningChange?: (isRunning: boolean) => void;
}

export function AssistantPanel({
Expand All @@ -31,7 +32,8 @@ export function AssistantPanel({
setIsChatMaximized,
onSingleSelect,
onMultiSelect,
onReady
onReady,
onThreadRunningChange,
}: AssistantPanelProps) {
// Don't render if no workspaceId
if (!workspaceId) {
Expand All @@ -49,6 +51,7 @@ export function AssistantPanel({
onSingleSelect={onSingleSelect}
onMultiSelect={onMultiSelect}
onReady={onReady}
onThreadRunningChange={onThreadRunningChange}
/>
</>
);
Expand All @@ -61,7 +64,8 @@ function WorkspaceContextWrapper({
setIsChatMaximized,
onSingleSelect,
onMultiSelect,
onReady
onReady,
onThreadRunningChange,
}: {
workspaceId?: string | null;
setIsChatExpanded?: (expanded: boolean) => void;
Expand All @@ -70,6 +74,7 @@ function WorkspaceContextWrapper({
onSingleSelect?: (text: string, range?: Range) => void | Promise<void>;
onMultiSelect?: (selections: Array<{ text: string; id: string; range?: Range }>) => void | Promise<void>;
onReady?: () => void;
onThreadRunningChange?: (isRunning: boolean) => void;
}) {
// Fetch current workspace state (includes loading state)
const { state, isLoading } = useWorkspaceState(workspaceId || null);
Expand All @@ -96,6 +101,7 @@ function WorkspaceContextWrapper({
onReady={onReady}
state={state}
isLoading={isLoading}
onThreadRunningChange={onThreadRunningChange}
/>
</>
);
Expand Down Expand Up @@ -249,6 +255,7 @@ function WorkspaceContextWrapperContent({
onReady,
state,
isLoading,
onThreadRunningChange,
}: {
workspaceId?: string | null;
setIsChatExpanded?: (expanded: boolean) => void;
Expand All @@ -259,6 +266,7 @@ function WorkspaceContextWrapperContent({
onReady?: () => void;
state: ReturnType<typeof useWorkspaceState>["state"];
isLoading: boolean;
onThreadRunningChange?: (isRunning: boolean) => void;
}) {
// Notify parent when content is ready
useEffect(() => {
Expand Down Expand Up @@ -308,6 +316,8 @@ function WorkspaceContextWrapperContent({
)}
data-tour="chat-panel"
>
<ThreadRunningObserver onRunningChange={onThreadRunningChange} />

{/* Chat Header */}
<AppChatHeader
onCollapse={() => setIsChatExpanded?.(false)}
Expand All @@ -329,3 +339,13 @@ function WorkspaceContextWrapperContent({
</div>
);
}

function ThreadRunningObserver({ onRunningChange }: { onRunningChange?: (isRunning: boolean) => void }) {
const isRunning = useAuiState(({ thread }) => (thread as any)?.isRunning ?? false);

useEffect(() => {
onRunningChange?.(isRunning);
}, [isRunning, onRunningChange]);

return null;
}
Comment on lines +343 to +351

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the actual file to understand the context
cat -n src/components/assistant-ui/AssistantPanel.tsx | head -360 | tail -30

Repository: ThinkEx-OSS/thinkex

Length of output: 1146


🏁 Script executed:

# Check what's imported from `@assistant-ui/react` at the top of the file
head -30 src/components/assistant-ui/AssistantPanel.tsx

Repository: ThinkEx-OSS/thinkex

Length of output: 1478


🏁 Script executed:

# Look for any type definitions or existing patterns in the codebase for accessing thread state
rg "useAuiState" --type tsx -A 2 -B 2

Repository: ThinkEx-OSS/thinkex

Length of output: 90


🌐 Web query:

@assistant-ui/react useAuiState hook API documentation thread state

💡 Result:

useAuiState (from @assistant-ui/react) is the unified “read state with subscriptions” hook:

  • Signature: useAuiState(selector)
  • What it does: Runs your selector(state) and re-renders only when the selected value changes (Zustand-style selectors). Avoid returning newly-created objects from selectors to prevent re-render loops. [1]
  • What state is: A scope-aware AssistantState object; depending on where your component is rendered you can access scopes like thread, message, composer, part, attachment, etc. (child scopes can also read parent scopes like thread). [1][2]

Thread state (what you can select)

From useAuiState(({ thread }) => ...), the thread is a ThreadState with commonly-used fields including:

  • thread.isRunning (active stream/run in progress)
  • thread.isLoading (loading history)
  • thread.messages (messages in the current branch)
  • plus threadId, metadata, capabilities, suggestions, etc. [3]

There are also convenience conditions you’ll commonly see in UI like thread.isEmpty and thread.isRunning (e.g., via AssistantIf). [2]

Example selectors

import { useAuiState } from "@assistant-ui/react";

const isRunning = useAuiState(({ thread }) => thread.isRunning);
const messageCount = useAuiState(({ thread }) => thread.messages.length);

[1][3]

Sources: Context API guide (covers useAuiState and scoping) [1], AssistantIf API (shows thread.isEmpty / thread.isRunning) [2], ThreadRuntime API (documents ThreadState fields like isRunning, messages, etc.) [3].


Remove unnecessary as any type assertion — thread.isRunning is properly typed.

The @assistant-ui/react library fully types thread.isRunning as a boolean field in ThreadState. Cast to any is unnecessary and hides type safety. Replace with:

const isRunning = useAuiState(({ thread }) => thread.isRunning);

If a fallback to false is needed for initialization, keep only:

const isRunning = useAuiState(({ thread }) => thread.isRunning ?? false);
🤖 Prompt for AI Agents
In `@src/components/assistant-ui/AssistantPanel.tsx` around lines 343 - 351, In
ThreadRunningObserver, remove the unnecessary and unsafe "as any" cast when
reading thread.isRunning; update the selector passed to useAuiState to directly
access thread.isRunning (or thread.isRunning ?? false if you need a fallback) so
type safety from ThreadState is preserved; refer to the ThreadRunningObserver
function and the useAuiState selector to locate and change the line that
currently contains "(thread as any)?.isRunning ?? false".

4 changes: 4 additions & 0 deletions src/components/home/HomeContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { FolderPlus, Github } from "lucide-react";
import { useCreateWorkspace } from "@/hooks/workspace/use-create-workspace";
import { useSession } from "@/lib/auth-client";
import { markNewWorkspaceInstruction } from "@/lib/workspace/instruction-modal";
import {
HoverCard,
HoverCardContent,
Expand All @@ -29,6 +31,7 @@ export const useSectionVisibility = () => useContext(SectionVisibilityContext);

export function HomeContent() {
const router = useRouter();
const { data: session } = useSession();
const [scrollY, setScrollY] = useState(0);
const [searchQuery, setSearchQuery] = useState("");
const [heroVisible, setHeroVisible] = useState(true);
Expand Down Expand Up @@ -92,6 +95,7 @@ export function HomeContent() {
},
{
onSuccess: ({ workspace }) => {
markNewWorkspaceInstruction(session?.user?.id, workspace.id);
router.push(`/workspace/${workspace.slug}`);
},
onError: (err) => {
Expand Down
5 changes: 4 additions & 1 deletion src/components/layout/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface DashboardLayoutProps {
// Text selection handlers
onSingleSelect?: (text: string) => void | Promise<void>;
onMultiSelect?: (selections: Array<{ text: string; id: string }>) => void | Promise<void>;
onAssistantThreadRunningChange?: (isRunning: boolean) => void;

// Component slots
workspaceSection: React.ReactNode;
Expand Down Expand Up @@ -60,6 +61,7 @@ export function DashboardLayout({
onWorkspaceSizeChange,
onSingleSelect,
onMultiSelect,
onAssistantThreadRunningChange,
workspaceSection,
panels,
modalManager,
Expand Down Expand Up @@ -107,6 +109,7 @@ export function DashboardLayout({
setIsChatMaximized={setIsChatMaximized}
onSingleSelect={onSingleSelect}
onMultiSelect={onMultiSelect}
onThreadRunningChange={onAssistantThreadRunningChange}
/>
</div>
</AssistantDropzone>
Expand Down Expand Up @@ -188,6 +191,7 @@ export function DashboardLayout({
setIsChatMaximized={setIsChatMaximized}
onSingleSelect={onSingleSelect}
onMultiSelect={onMultiSelect}
onThreadRunningChange={onAssistantThreadRunningChange}
/>
</AssistantDropzone>
</ResizablePanel>
Expand All @@ -208,4 +212,3 @@ export function DashboardLayout({

return content;
}

Loading