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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ internal
.turbo

.windsurfrules
CLAUDE.md
CLAUDE.md
.cursor/
16 changes: 16 additions & 0 deletions apps/desktop/src/components/editor-area/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TemplateService } from "@/utils/template-service";
import { commands as analyticsCommands } from "@hypr/plugin-analytics";
import { commands as connectorCommands } from "@hypr/plugin-connector";
import { commands as dbCommands } from "@hypr/plugin-db";
import { events as localLlmEvents } from "@hypr/plugin-local-llm";
import { commands as miscCommands } from "@hypr/plugin-misc";
import { commands as templateCommands, type Grammar } from "@hypr/plugin-template";
import Editor, { type TiptapEditor } from "@hypr/tiptap/editor";
Expand Down Expand Up @@ -361,6 +362,21 @@ export function useEnhanceMutation({
const [isCancelled, setIsCancelled] = useState(false);
const queryClient = useQueryClient();

useEffect(() => {
let unlisten: () => void;
localLlmEvents.llmEvent.listen(({ payload }) => {
if (payload.progress) {
setProgress(payload.progress);
}
}).then((fn) => {
unlisten = fn;
});

return () => {
unlisten();
};
}, []);

// Extract H1 headers at component level (always available)
const extractH1Headers = useCallback((htmlContent: string): string[] => {
if (!htmlContent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export function ChatInput(

const traverseNode = (node: any) => {
if (node.type === "mention" || node.type === "mention-@") {
if (node.attrs) {
if (node.attrs && node.attrs.type !== "selection") {
mentions.push({
id: node.attrs.id || node.attrs["data-id"],
type: node.attrs.type || node.attrs["data-type"] || "note",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type { UIMessage } from "@hypr/utils/ai";
import { useEffect, useRef, useState } from "react";
import { ChatMessage } from "./chat-message";
import { Message } from "./types";
import { UIMessageComponent } from "./ui-message";

interface ChatMessagesViewProps {
messages: Message[];
messages: UIMessage[];
sessionTitle?: string;
hasEnhancedNote?: boolean;
onApplyMarkdown?: (markdownContent: string) => void;
isGenerating?: boolean;
isStreamingText?: boolean;
isSubmitted?: boolean;
isStreaming?: boolean;
isReady?: boolean;
isError?: boolean;
}

function ThinkingIndicator() {
Expand All @@ -30,7 +32,7 @@ function ThinkingIndicator() {
}
`}
</style>
<div style={{ color: "rgb(115 115 115)", fontSize: "0.875rem", padding: "4px 0" }}>
<div style={{ color: "rgb(115 115 115)", fontSize: "0.875rem", padding: "0 0 8px 0" }}>
<span>Thinking</span>
<span className="thinking-dot">.</span>
<span className="thinking-dot">.</span>
Expand All @@ -41,27 +43,45 @@ function ThinkingIndicator() {
}

export function ChatMessagesView(
{ messages, sessionTitle, hasEnhancedNote, onApplyMarkdown, isGenerating, isStreamingText }: ChatMessagesViewProps,
{ messages, sessionTitle, hasEnhancedNote, onApplyMarkdown, isSubmitted, isStreaming, isReady, isError }:
ChatMessagesViewProps,
) {
const messagesEndRef = useRef<HTMLDivElement>(null);
const [showThinking, setShowThinking] = useState(false);
const thinkingTimeoutRef = useRef<NodeJS.Timeout | null>(null);

const shouldShowThinking = () => {
if (!isGenerating) {
return false;
}

if (messages.length === 0) {
// Show thinking when request is submitted but not yet streaming
if (isSubmitted) {
return true;
}

const lastMessage = messages[messages.length - 1];
if (lastMessage.isUser) {
return true;
// Check if we're in transition between parts (text → tool or tool → text)
if (isStreaming && messages.length > 0) {
const lastMessage = messages[messages.length - 1];
if (lastMessage.role === "assistant" && lastMessage.parts) {
const lastPart = lastMessage.parts[lastMessage.parts.length - 1];

// Text part finished but still streaming (tool coming)
if (lastPart?.type === "text" && !(lastPart as any).state) {
return true;
}

// Tool finished but still streaming (more text/tools coming)
if (lastPart?.type?.startsWith("tool-") || lastPart?.type === "dynamic-tool") {
const toolPart = lastPart as any;
if (
toolPart.state === "output-available"
|| toolPart.state === "output-error"
) {
return true;
}
}
}
}

if (!lastMessage.isUser && !isStreamingText) {
// Fallback for other transition states
if (!isReady && !isStreaming && !isError) {
return true;
}

Expand Down Expand Up @@ -89,16 +109,16 @@ export function ChatMessagesView(
clearTimeout(thinkingTimeoutRef.current);
}
};
}, [isGenerating, isStreamingText, messages]);
}, [isSubmitted, isStreaming, isReady, isError, messages]);

useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages, showThinking]);

return (
<div className="flex-1 overflow-y-auto p-4 space-y-4 select-text">
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-6 select-text">
{messages.map((message) => (
<ChatMessage
<UIMessageComponent
key={message.id}
message={message}
sessionTitle={sessionTitle}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
export * from "./chat-history-item";
export * from "./chat-history-view";
export * from "./chat-input";
export * from "./chat-message";
export * from "./chat-messages-view";
export * from "./empty-chat-state";
export * from "./floating-action-buttons";
export { MarkdownCard } from "./markdown-card";
export { MessageContent } from "./message-content";
export * from "./types";
export { UIMessageComponent } from "./ui-message";
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function MarkdownCard(
</style>

{/* Flat card with no shadow */}
<div className="mt-4 mb-4 border border-neutral-200 rounded-lg bg-white overflow-hidden">
<div className="border border-neutral-200 rounded-lg bg-white overflow-hidden">
{/* Grey header section - Made thinner with py-1 */}
<div className="bg-neutral-50 px-4 py-1 border-b border-neutral-200 flex items-center justify-between">
<div className="text-sm text-neutral-600 flex items-center gap-2">
Expand Down
Loading
Loading