Slice 1 polish: archive dormant code, merge work-os into primitives, add futures
- Archive dormant apps (daemon, desktop, native, tui) and superseded packages/server into repos/ for reference - Archive 21 dead web components (chat page shell, work-os cluster, strays) into repos/web/; keep reused message-rendering primitives in components/chat - Merge @code/work-os into @code/primitives; work.ts absorbed via ./work subpath and barrel export; update all four importers - Add docs/futures.md tracking audio/video (blocked at Flue + provider), web layout cleanup, and message rendering quality - Repoint dev:zopu script to @code/agents (the live Flue server) - Note repos/ reference-code convention in AGENTS.md
This commit is contained in:
40
repos/web/components/chat/chat-composer.tsx
Normal file
40
repos/web/components/chat/chat-composer.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { MobileChatComposer } from "@code/ui/components/mobile-chat";
|
||||
import { LoaderCircle } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { useChatComposer } from "@/hooks/chat/use-chat-composer";
|
||||
import type { ChatComposerProps } from "@/lib/chat/types";
|
||||
|
||||
export const ChatComposer = ({ error, onSend, status }: ChatComposerProps) => {
|
||||
const busy =
|
||||
status === "connecting" || status === "submitted" || status === "streaming";
|
||||
const composer = useChatComposer({ busy, onSend });
|
||||
|
||||
let statusMessage: ReactNode;
|
||||
if (busy) {
|
||||
statusMessage = (
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
{status === "connecting" ? "Preparing Zopu" : "Zopu is responding"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MobileChatComposer
|
||||
busy={busy}
|
||||
canSend={composer.canSend}
|
||||
errorMessage={
|
||||
error ? error.message || "Message failed to send" : undefined
|
||||
}
|
||||
onSubmit={composer.handleSubmit}
|
||||
statusMessage={statusMessage}
|
||||
textareaProps={{
|
||||
...composer.inputProps,
|
||||
"aria-describedby": error ? "composer-error" : undefined,
|
||||
"aria-invalid": error ? true : undefined,
|
||||
disabled: busy,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
56
repos/web/components/chat/chat-conversation.tsx
Normal file
56
repos/web/components/chat/chat-conversation.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { ConversationEmptyState } from "@code/ui/components/ai-elements/conversation";
|
||||
import { Button } from "@code/ui/components/button";
|
||||
import { MobileChatMark } from "@code/ui/components/mobile-chat";
|
||||
|
||||
import { SUGGESTIONS } from "@/lib/chat/constants";
|
||||
import type { ChatConversationProps } from "@/lib/chat/types";
|
||||
|
||||
import { ChatMessage } from "./chat-message";
|
||||
import { ChatThinkingResponse } from "./chat-thinking-response";
|
||||
|
||||
export const ChatConversation = ({
|
||||
historyReady,
|
||||
messages,
|
||||
onSuggestion,
|
||||
status,
|
||||
}: ChatConversationProps) => {
|
||||
if (historyReady && messages.length === 0) {
|
||||
return (
|
||||
<ConversationEmptyState className="min-h-full items-start justify-end px-0 pt-14 pb-5 text-left">
|
||||
<MobileChatMark className="size-10 rounded-[12px] [&_svg]:size-5" />
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-[22px] font-semibold tracking-[-0.035em]">
|
||||
What can I help with?
|
||||
</h2>
|
||||
<p className="max-w-[320px] text-[14px] leading-5 text-muted-foreground">
|
||||
Think, write, plan, or explore an idea with Zopu.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3 grid w-full gap-2">
|
||||
{SUGGESTIONS.map(([title, prompt]) => (
|
||||
<Button
|
||||
key={title}
|
||||
className="group h-auto min-h-14 w-full flex-col items-start justify-center gap-0.5 rounded-[16px] border-[#e7e7e4] bg-[#f7f7f5] px-4 py-3 text-left shadow-none transition-colors active:bg-[#eeeeeb]"
|
||||
onClick={() => onSuggestion(prompt)}
|
||||
variant="outline"
|
||||
>
|
||||
<span className="font-medium text-foreground">{title}</span>
|
||||
<span className="text-xs leading-4 font-normal text-muted-foreground whitespace-normal">
|
||||
{prompt}
|
||||
</span>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</ConversationEmptyState>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-5 pb-5">
|
||||
{messages.map((message) => (
|
||||
<ChatMessage key={message.id} message={message} />
|
||||
))}
|
||||
{status === "submitted" && <ChatThinkingResponse />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
11
repos/web/components/chat/chat-header.tsx
Normal file
11
repos/web/components/chat/chat-header.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { MobileChatHeader } from "@code/ui/components/mobile-chat";
|
||||
|
||||
import { STATUS_COPY } from "@/lib/chat/constants";
|
||||
import type { ChatHeaderProps } from "@/lib/chat/types";
|
||||
|
||||
export const ChatHeader = ({ status }: ChatHeaderProps) => (
|
||||
<MobileChatHeader
|
||||
active={status !== "connecting" && status !== "error"}
|
||||
statusLabel={STATUS_COPY[status]}
|
||||
/>
|
||||
);
|
||||
45
repos/web/components/chat/chat-page.tsx
Normal file
45
repos/web/components/chat/chat-page.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
Conversation,
|
||||
ConversationContent,
|
||||
ConversationScrollButton,
|
||||
} from "@code/ui/components/ai-elements/conversation";
|
||||
|
||||
import { useChatAgent } from "@/hooks/chat/use-chat-agent";
|
||||
|
||||
import { ChatComposer } from "./chat-composer";
|
||||
import { ChatConversation } from "./chat-conversation";
|
||||
import { ChatHeader } from "./chat-header";
|
||||
|
||||
export const ChatPage = () => {
|
||||
const agent = useChatAgent();
|
||||
const handleSendMessage = (message: string) => agent.sendMessage(message);
|
||||
|
||||
return (
|
||||
<section className="chat-surface flex h-full min-h-0 flex-col bg-[#fefefe]">
|
||||
<ChatHeader status={agent.status} />
|
||||
|
||||
<main aria-label="Conversation" className="min-h-0 flex-1">
|
||||
<Conversation className="ai-conversation-mobile h-full">
|
||||
<ConversationContent className="min-h-full w-full gap-0 px-4 pt-4 pb-6">
|
||||
<ChatConversation
|
||||
historyReady={agent.historyReady}
|
||||
messages={agent.messages}
|
||||
onSuggestion={(suggestion) => void handleSendMessage(suggestion)}
|
||||
status={agent.status}
|
||||
/>
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton
|
||||
aria-label="Scroll to latest message"
|
||||
className="bottom-3 size-9 rounded-full border border-[#dededb] bg-[#fefefe]/95 shadow-sm backdrop-blur"
|
||||
/>
|
||||
</Conversation>
|
||||
</main>
|
||||
|
||||
<ChatComposer
|
||||
error={agent.error}
|
||||
onSend={handleSendMessage}
|
||||
status={agent.status}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user