127 lines
4.2 KiB
TypeScript
127 lines
4.2 KiB
TypeScript
import { api } from "@code/backend/convex/_generated/api";
|
|
import type { Id } from "@code/backend/convex/_generated/dataModel";
|
|
import type {
|
|
MobileAssistantMessageView,
|
|
MobileAssistantView,
|
|
} from "@code/ui/components/mobile-product";
|
|
import { useQuery } from "convex/react";
|
|
|
|
import { useOrganizationChatAgent } from "@/hooks/chat/use-chat-agent";
|
|
import { usePersonalOrganization } from "@/hooks/use-personal-organization";
|
|
import { useProjectWorkspace } from "@/hooks/use-project-workspace";
|
|
import { STATUS_COPY } from "@/lib/chat/constants";
|
|
import { getMessageText } from "@/lib/chat/transforms";
|
|
import { buildMobileWorkspaceView } from "@/lib/mobile-workspace/build-mobile-workspace-view";
|
|
|
|
const WORK_UNIT_SCOPE_PATTERN =
|
|
/^<zopu-work-unit id="(?<workUnitId>[^"]+)">[\s\S]*?<message>(?<message>[\s\S]*?)<\/message>\s*<\/zopu-work-unit>$/u;
|
|
|
|
const toAssistantMessages = (
|
|
messages: ReturnType<typeof useOrganizationChatAgent>["messages"],
|
|
selectedWorkUnitId?: string
|
|
): MobileAssistantMessageView[] => {
|
|
const result: MobileAssistantMessageView[] = [];
|
|
for (const message of messages) {
|
|
if (message.role !== "assistant" && message.role !== "user") {
|
|
continue;
|
|
}
|
|
const rawText = getMessageText(message);
|
|
if (!rawText) {
|
|
continue;
|
|
}
|
|
if (!selectedWorkUnitId) {
|
|
result.push({ id: message.id, role: message.role, text: rawText });
|
|
continue;
|
|
}
|
|
if (message.role === "user") {
|
|
const match = WORK_UNIT_SCOPE_PATTERN.exec(rawText);
|
|
if (match?.groups?.workUnitId !== selectedWorkUnitId) {
|
|
result.length = 0;
|
|
continue;
|
|
}
|
|
result.push({
|
|
id: message.id,
|
|
role: message.role,
|
|
text: match.groups.message?.trim() ?? rawText,
|
|
});
|
|
continue;
|
|
}
|
|
if (result.length > 0) {
|
|
result.push({ id: message.id, role: message.role, text: rawText });
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const getStatusTone = (
|
|
status: ReturnType<typeof useOrganizationChatAgent>["status"]
|
|
): MobileAssistantView["statusTone"] => {
|
|
if (status === "error") {
|
|
return "red";
|
|
}
|
|
return status === "connecting" ? "amber" : "green";
|
|
};
|
|
|
|
interface UseMobileProjectWorkspaceOptions {
|
|
readonly selectedWorkUnitId?: string;
|
|
}
|
|
|
|
export const useMobileProjectWorkspace = ({
|
|
selectedWorkUnitId,
|
|
}: UseMobileProjectWorkspaceOptions) => {
|
|
const organization = usePersonalOrganization();
|
|
const projectWorkspace = useProjectWorkspace();
|
|
const chatAgent = useOrganizationChatAgent(organization);
|
|
const signals = useQuery(
|
|
api.signals.list,
|
|
organization.organizationId
|
|
? { organizationId: organization.organizationId }
|
|
: "skip"
|
|
);
|
|
const selectedIssue = projectWorkspace.issues?.find(
|
|
(issue) => String(issue._id) === selectedWorkUnitId
|
|
);
|
|
const sendMessage = (message: string) => {
|
|
if (!selectedIssue) {
|
|
return chatAgent.sendMessage(message);
|
|
}
|
|
return chatAgent.sendMessage(
|
|
`<zopu-work-unit id="${selectedIssue._id}">\n<context>Issue #${selectedIssue.number}: ${selectedIssue.title}</context>\n<message>${message}</message>\n</zopu-work-unit>`
|
|
);
|
|
};
|
|
const assistant: MobileAssistantView = {
|
|
isBusy:
|
|
chatAgent.status === "connecting" ||
|
|
chatAgent.status === "streaming" ||
|
|
chatAgent.status === "submitted",
|
|
messages: toAssistantMessages(chatAgent.messages, selectedWorkUnitId),
|
|
statusLabel: STATUS_COPY[chatAgent.status],
|
|
statusTone: getStatusTone(chatAgent.status),
|
|
};
|
|
|
|
return {
|
|
data: buildMobileWorkspaceView({
|
|
artifacts: projectWorkspace.artifacts,
|
|
assistant,
|
|
events: projectWorkspace.events,
|
|
issues: projectWorkspace.issues,
|
|
organizationLabel: organization.organizationName,
|
|
projects: projectWorkspace.projects,
|
|
selectedProject: projectWorkspace.selectedProject,
|
|
selectedWorkUnitId,
|
|
signals,
|
|
}),
|
|
error:
|
|
organization.error ??
|
|
chatAgent.error ??
|
|
(projectWorkspace.error ? new Error(projectWorkspace.error) : undefined),
|
|
organization,
|
|
projectWorkspace,
|
|
raiseIssue: projectWorkspace.raiseIssue,
|
|
raiseIssueFromSignal: (signalId: string) =>
|
|
projectWorkspace.raiseIssueFromSignal(signalId as Id<"signals">),
|
|
sendMessage,
|
|
startWorkUnit: projectWorkspace.startWorkUnit,
|
|
} as const;
|
|
};
|