fix(v1.3): close 3 integration gaps from milestone audit
1. Push notifications: call sendPushToAll after streaming completes 2. Mobile offline: add useOfflineQueue + banners to MobileChatView 3. New conversation streaming: call startStream in Path 1 handleSend Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e7df7e5599
commit
d6f5e595d9
3 changed files with 34 additions and 2 deletions
|
|
@ -2,6 +2,7 @@ import { Router } from "express";
|
|||
import type { Db } from "@paperclipai/db";
|
||||
import { assertBoard, assertCompanyAccess } from "./authz.js";
|
||||
import { chatService } from "../services/chat.js";
|
||||
import { sendPushToAll } from "../services/pushService.js";
|
||||
import { issueService } from "../services/issues.js";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
|
|
@ -117,6 +118,14 @@ export function chatRoutes(db: Db): Router {
|
|||
agentId: agentId || undefined,
|
||||
});
|
||||
res.write(`data: ${JSON.stringify({ done: true, messageId: message.id, content: fullContent.trim() })}\n\n`);
|
||||
|
||||
// Fire push notification for offline subscribers (PWA-06)
|
||||
const conversation = await svc.getConversation(req.params.id!);
|
||||
sendPushToAll(db, conversation.companyId, {
|
||||
title: "New agent response",
|
||||
body: fullContent.trim().slice(0, 100),
|
||||
data: { url: `/chat/${conversation.id}` },
|
||||
}).catch(() => {}); // non-blocking
|
||||
}
|
||||
} catch (err) {
|
||||
if (res.writable && !abort.signal.aborted) {
|
||||
|
|
|
|||
|
|
@ -178,8 +178,7 @@ export function ChatPanel() {
|
|||
queryClient.invalidateQueries({ queryKey: ["chat", "messages", newConvo.id] });
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: ["chat"] });
|
||||
// Note: streaming starts on next render when activeConversationId is set
|
||||
// For now, the echo stream will be triggered by the new conversation
|
||||
startStream(content, resolvedAgentId ?? undefined);
|
||||
} else {
|
||||
// Path 2: Active conversation -- post user message then stream
|
||||
const message = await chatApi.postMessage(activeConversationId, { role: "user", content });
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import { useStreamingChat } from "../hooks/useStreamingChat";
|
|||
import { useBrainstormerDefault } from "../hooks/useBrainstormerDefault";
|
||||
import { useChatBookmarks, useToggleBookmark } from "../hooks/useChatBookmarks";
|
||||
import { useChatFileUpload } from "../hooks/useChatFileUpload";
|
||||
import { useOfflineQueue } from "../hooks/useOfflineQueue";
|
||||
import { useOnlineStatus } from "../hooks/useOnlineStatus";
|
||||
import { useChatConversations } from "../hooks/useChatConversations";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { chatApi } from "../api/chat";
|
||||
|
|
@ -20,6 +22,9 @@ import { ChatAgentSelector } from "./ChatAgentSelector";
|
|||
import { ChatConversationList } from "./ChatConversationList";
|
||||
import { ChatStopButton } from "./ChatStopButton";
|
||||
import { PullToRefresh } from "./PullToRefresh";
|
||||
import { OfflineBanner } from "./OfflineBanner";
|
||||
import { InstallPromptBanner } from "./InstallPromptBanner";
|
||||
import { NotificationPermissionPrompt } from "./NotificationPermissionPrompt";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { AgentRole } from "@paperclipai/shared";
|
||||
|
||||
|
|
@ -41,6 +46,8 @@ export function MobileChatView() {
|
|||
const { messages } = useChatMessages(activeConversationId);
|
||||
const { streamingContent, isStreaming, startStream, stop } = useStreamingChat(activeConversationId);
|
||||
const { pendingFiles, addFile, removeFile, clearCompleted, completedFileIds } = useChatFileUpload(activeConversationId);
|
||||
const { enqueue, queuedCount } = useOfflineQueue();
|
||||
const isOnline = useOnlineStatus();
|
||||
const brainstormerDefaultId = useBrainstormerDefault();
|
||||
|
||||
// useChatConversations for refetch (pull-to-refresh)
|
||||
|
|
@ -103,8 +110,20 @@ export function MobileChatView() {
|
|||
[activeConversationId, queryClient, pushToast],
|
||||
);
|
||||
|
||||
const [agentResponseCount, setAgentResponseCount] = useState(0);
|
||||
|
||||
const handleSend = async (content: string) => {
|
||||
if (!selectedCompanyId) return;
|
||||
|
||||
// If offline, enqueue the message and show a toast
|
||||
if (!isOnline) {
|
||||
if (activeConversationId) {
|
||||
await enqueue(activeConversationId, content);
|
||||
pushToast({ title: "Message queued — will send when you're back online", tone: "info" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedAgentId = resolveAgentFromContent(content, agents, effectiveBrainstormerId);
|
||||
const fileIdsToAttach = [...completedFileIds];
|
||||
|
||||
|
|
@ -122,6 +141,7 @@ export function MobileChatView() {
|
|||
queryClient.invalidateQueries({ queryKey: ["chat", "messages", newConvo.id] });
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: ["chat"] });
|
||||
startStream(content, resolvedAgentId ?? undefined);
|
||||
} else {
|
||||
const message = await chatApi.postMessage(activeConversationId, { role: "user", content });
|
||||
if (fileIdsToAttach.length > 0) {
|
||||
|
|
@ -199,6 +219,8 @@ export function MobileChatView() {
|
|||
if (!activeConversationId) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-40 flex h-[100dvh] flex-col bg-background">
|
||||
<OfflineBanner queuedCount={queuedCount} />
|
||||
<InstallPromptBanner />
|
||||
{/* pb-16 accounts for the existing MobileBottomNav (h-16) at the bottom of Layout */}
|
||||
<div className="flex-1 overflow-hidden pb-16">
|
||||
{selectedCompanyId ? (
|
||||
|
|
@ -218,6 +240,8 @@ export function MobileChatView() {
|
|||
// Active conversation view
|
||||
return (
|
||||
<div className="fixed inset-0 z-40 flex h-[100dvh] flex-col bg-background">
|
||||
<OfflineBanner queuedCount={queuedCount} />
|
||||
<NotificationPermissionPrompt agentResponseCount={agentResponseCount} />
|
||||
{/* Header: 48px tall */}
|
||||
<div className="flex h-12 flex-shrink-0 items-center gap-2 border-b border-border px-3">
|
||||
<Button
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue