mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-27 19:52:49 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea7602b968 |
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|||||||
import type { TurnTiming } from "@/timeline/turn-time";
|
import type { TurnTiming } from "@/timeline/turn-time";
|
||||||
import type { StreamItem } from "@/types/stream";
|
import type { StreamItem } from "@/types/stream";
|
||||||
import {
|
import {
|
||||||
|
collectAssistantTurnContentForStreamRenderStrategy,
|
||||||
orderHeadForStreamRenderStrategy,
|
orderHeadForStreamRenderStrategy,
|
||||||
orderTailForStreamRenderStrategy,
|
orderTailForStreamRenderStrategy,
|
||||||
type StreamStrategy,
|
type StreamStrategy,
|
||||||
@@ -310,6 +311,34 @@ describe("layoutStream", () => {
|
|||||||
expect(footerOwners(layout)).toEqual([assistant.id]);
|
expect(footerOwners(layout)).toEqual([assistant.id]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each(["web", "android"] as const)(
|
||||||
|
"copies every assistant block when a completed turn spans history and live head on %s",
|
||||||
|
(platform) => {
|
||||||
|
const strategy = strategyFor(platform);
|
||||||
|
const layout = layoutFor({
|
||||||
|
platform,
|
||||||
|
tail: [
|
||||||
|
userMessage("u1", 1),
|
||||||
|
assistantMessage("first block", 2, { groupId: "turn-1", index: 0 }),
|
||||||
|
assistantMessage("second block", 3, { groupId: "turn-1", index: 1 }),
|
||||||
|
],
|
||||||
|
head: [assistantMessage("final block", 4, { groupId: "turn-1", index: 2 })],
|
||||||
|
});
|
||||||
|
const footer = layout.auxiliaryTurnFooter;
|
||||||
|
|
||||||
|
expect(footer).not.toBeNull();
|
||||||
|
expect(
|
||||||
|
collectAssistantTurnContentForStreamRenderStrategy({
|
||||||
|
strategy,
|
||||||
|
items: footer!.items,
|
||||||
|
startIndex: footer!.startIndex,
|
||||||
|
precedingItems: footer!.precedingItems,
|
||||||
|
precedingStartIndex: footer!.precedingStartIndex,
|
||||||
|
}),
|
||||||
|
).toBe("first block\n\nsecond block\n\nfinal block");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it.each(["web", "android"] as const)(
|
it.each(["web", "android"] as const)(
|
||||||
"places inline footer after trailing visible tool rows before the next user on %s",
|
"places inline footer after trailing visible tool rows before the next user on %s",
|
||||||
(platform) => {
|
(platform) => {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export interface TurnFooterHost {
|
|||||||
items: StreamItem[];
|
items: StreamItem[];
|
||||||
timing?: TurnTiming;
|
timing?: TurnTiming;
|
||||||
startIndex: number;
|
startIndex: number;
|
||||||
|
precedingItems: StreamItem[] | null;
|
||||||
|
precedingStartIndex: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StreamLayoutItem {
|
export interface StreamLayoutItem {
|
||||||
@@ -66,12 +68,16 @@ function createTurnFooterHost(input: {
|
|||||||
items: StreamItem[];
|
items: StreamItem[];
|
||||||
index: number;
|
index: number;
|
||||||
timingByAssistantId: Map<string, TurnTiming>;
|
timingByAssistantId: Map<string, TurnTiming>;
|
||||||
|
precedingItems?: StreamItem[] | null;
|
||||||
|
precedingStartIndex?: number | null;
|
||||||
}): TurnFooterHost {
|
}): TurnFooterHost {
|
||||||
return {
|
return {
|
||||||
itemId: input.item.id,
|
itemId: input.item.id,
|
||||||
items: input.items,
|
items: input.items,
|
||||||
timing: input.timingByAssistantId.get(input.item.id),
|
timing: input.timingByAssistantId.get(input.item.id),
|
||||||
startIndex: input.index,
|
startIndex: input.index,
|
||||||
|
precedingItems: input.precedingItems ?? null,
|
||||||
|
precedingStartIndex: input.precedingStartIndex ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +147,11 @@ function resolveAuxiliaryTurnFooter(input: StreamLayoutInput): TurnFooterHost |
|
|||||||
items: assistant.items,
|
items: assistant.items,
|
||||||
index: assistant.index,
|
index: assistant.index,
|
||||||
timingByAssistantId: input.timingByAssistantId,
|
timingByAssistantId: input.timingByAssistantId,
|
||||||
|
precedingItems: footerItems === input.liveHead ? input.history : null,
|
||||||
|
precedingStartIndex:
|
||||||
|
footerItems === input.liveHead
|
||||||
|
? input.strategy.getHistoryLiveBoundaryIndex(input.history)
|
||||||
|
: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +185,8 @@ function resolveCompletedFooter(input: {
|
|||||||
items: assistant.items,
|
items: assistant.items,
|
||||||
index: assistant.index,
|
index: assistant.index,
|
||||||
timingByAssistantId: input.timingByAssistantId,
|
timingByAssistantId: input.timingByAssistantId,
|
||||||
|
precedingItems: input.boundaryAboveItems,
|
||||||
|
precedingStartIndex: input.boundaryAboveIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -272,8 +272,25 @@ export function collectAssistantTurnContentForStreamRenderStrategy(params: {
|
|||||||
strategy: StreamStrategy;
|
strategy: StreamStrategy;
|
||||||
items: StreamItem[];
|
items: StreamItem[];
|
||||||
startIndex: number;
|
startIndex: number;
|
||||||
|
precedingItems?: StreamItem[] | null;
|
||||||
|
precedingStartIndex?: number | null;
|
||||||
}): string {
|
}): string {
|
||||||
return params.strategy.collectAssistantTurnContent(params.items, params.startIndex);
|
const content = params.strategy.collectAssistantTurnContent(params.items, params.startIndex);
|
||||||
|
if (!params.precedingItems || params.precedingStartIndex == null) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const precedingContent = params.strategy.collectAssistantTurnContent(
|
||||||
|
params.precedingItems,
|
||||||
|
params.precedingStartIndex,
|
||||||
|
);
|
||||||
|
if (!precedingContent) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
if (!content) {
|
||||||
|
return precedingContent;
|
||||||
|
}
|
||||||
|
return `${precedingContent}\n\n${content}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNearBottomForStreamRenderStrategy(
|
export function isNearBottomForStreamRenderStrategy(
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { View } from "react-native";
|
|||||||
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
||||||
import { MAX_CONTENT_WIDTH } from "@/constants/layout";
|
import { MAX_CONTENT_WIDTH } from "@/constants/layout";
|
||||||
import type { Theme } from "@/styles/theme";
|
import type { Theme } from "@/styles/theme";
|
||||||
import type { TurnTiming } from "@/timeline/turn-time";
|
|
||||||
import type { StreamItem } from "@/types/stream";
|
|
||||||
import {
|
import {
|
||||||
collectAssistantTurnContentForStreamRenderStrategy,
|
collectAssistantTurnContentForStreamRenderStrategy,
|
||||||
type StreamStrategy,
|
type StreamStrategy,
|
||||||
@@ -62,9 +60,7 @@ export const TurnFooter = memo(function TurnFooter({
|
|||||||
return (
|
return (
|
||||||
<CompletedTurnFooterRow
|
<CompletedTurnFooterRow
|
||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
items={host.items}
|
host={host}
|
||||||
timing={host.timing}
|
|
||||||
startIndex={host.startIndex}
|
|
||||||
supportsTimelineCursor={supportsTimelineCursor}
|
supportsTimelineCursor={supportsTimelineCursor}
|
||||||
onForkAssistantTurn={onForkAssistantTurn}
|
onForkAssistantTurn={onForkAssistantTurn}
|
||||||
/>
|
/>
|
||||||
@@ -73,16 +69,12 @@ export const TurnFooter = memo(function TurnFooter({
|
|||||||
|
|
||||||
export const CompletedTurnFooterRow = memo(function CompletedTurnFooterRow({
|
export const CompletedTurnFooterRow = memo(function CompletedTurnFooterRow({
|
||||||
strategy,
|
strategy,
|
||||||
items,
|
host,
|
||||||
timing,
|
|
||||||
startIndex,
|
|
||||||
supportsTimelineCursor,
|
supportsTimelineCursor,
|
||||||
onForkAssistantTurn,
|
onForkAssistantTurn,
|
||||||
}: {
|
}: {
|
||||||
strategy: TurnContentStrategy;
|
strategy: TurnContentStrategy;
|
||||||
items: StreamItem[];
|
host: TurnFooterHost;
|
||||||
timing?: TurnTiming;
|
|
||||||
startIndex: number;
|
|
||||||
supportsTimelineCursor: boolean;
|
supportsTimelineCursor: boolean;
|
||||||
onForkAssistantTurn?: AssistantTurnForkHandler;
|
onForkAssistantTurn?: AssistantTurnForkHandler;
|
||||||
}) {
|
}) {
|
||||||
@@ -90,9 +82,7 @@ export const CompletedTurnFooterRow = memo(function CompletedTurnFooterRow({
|
|||||||
<TurnFooterRow>
|
<TurnFooterRow>
|
||||||
<CompletedTurnFooter
|
<CompletedTurnFooter
|
||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
items={items}
|
host={host}
|
||||||
timing={timing}
|
|
||||||
startIndex={startIndex}
|
|
||||||
supportsTimelineCursor={supportsTimelineCursor}
|
supportsTimelineCursor={supportsTimelineCursor}
|
||||||
onForkAssistantTurn={onForkAssistantTurn}
|
onForkAssistantTurn={onForkAssistantTurn}
|
||||||
/>
|
/>
|
||||||
@@ -133,16 +123,12 @@ function RunningTurnFooter({ inFlightTurnStartedAt }: { inFlightTurnStartedAt: D
|
|||||||
|
|
||||||
function CompletedTurnFooter({
|
function CompletedTurnFooter({
|
||||||
strategy,
|
strategy,
|
||||||
items,
|
host,
|
||||||
timing,
|
|
||||||
startIndex,
|
|
||||||
supportsTimelineCursor,
|
supportsTimelineCursor,
|
||||||
onForkAssistantTurn,
|
onForkAssistantTurn,
|
||||||
}: {
|
}: {
|
||||||
strategy: TurnContentStrategy;
|
strategy: TurnContentStrategy;
|
||||||
items: StreamItem[];
|
host: TurnFooterHost;
|
||||||
timing?: TurnTiming;
|
|
||||||
startIndex: number;
|
|
||||||
supportsTimelineCursor: boolean;
|
supportsTimelineCursor: boolean;
|
||||||
onForkAssistantTurn?: AssistantTurnForkHandler;
|
onForkAssistantTurn?: AssistantTurnForkHandler;
|
||||||
}) {
|
}) {
|
||||||
@@ -150,14 +136,16 @@ function CompletedTurnFooter({
|
|||||||
() =>
|
() =>
|
||||||
collectAssistantTurnContentForStreamRenderStrategy({
|
collectAssistantTurnContentForStreamRenderStrategy({
|
||||||
strategy,
|
strategy,
|
||||||
items,
|
items: host.items,
|
||||||
startIndex,
|
startIndex: host.startIndex,
|
||||||
|
precedingItems: host.precedingItems,
|
||||||
|
precedingStartIndex: host.precedingStartIndex,
|
||||||
}),
|
}),
|
||||||
[strategy, items, startIndex],
|
[strategy, host],
|
||||||
);
|
);
|
||||||
const boundary = resolveAssistantTurnForkBoundary({
|
const boundary = resolveAssistantTurnForkBoundary({
|
||||||
items,
|
items: host.items,
|
||||||
startIndex,
|
startIndex: host.startIndex,
|
||||||
supportsTimelineCursor,
|
supportsTimelineCursor,
|
||||||
});
|
});
|
||||||
const handleFork = useCallback(
|
const handleFork = useCallback(
|
||||||
@@ -173,8 +161,8 @@ function CompletedTurnFooter({
|
|||||||
<View style={stylesheet.turnFooterSlot}>
|
<View style={stylesheet.turnFooterSlot}>
|
||||||
<AssistantTurnFooter
|
<AssistantTurnFooter
|
||||||
getContent={getContent}
|
getContent={getContent}
|
||||||
completedAt={timing?.completedAt}
|
completedAt={host.timing?.completedAt}
|
||||||
durationMs={timing?.durationMs}
|
durationMs={host.timing?.durationMs}
|
||||||
onFork={boundary && onForkAssistantTurn ? handleFork : undefined}
|
onFork={boundary && onForkAssistantTurn ? handleFork : undefined}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -153,9 +153,7 @@ function renderStreamItemWithTurnFooter(input: {
|
|||||||
const footer = footerHost ? (
|
const footer = footerHost ? (
|
||||||
<CompletedTurnFooterRow
|
<CompletedTurnFooterRow
|
||||||
strategy={input.strategy}
|
strategy={input.strategy}
|
||||||
items={footerHost.items}
|
host={footerHost}
|
||||||
timing={footerHost.timing}
|
|
||||||
startIndex={footerHost.startIndex}
|
|
||||||
supportsTimelineCursor={input.supportsTimelineCursor}
|
supportsTimelineCursor={input.supportsTimelineCursor}
|
||||||
onForkAssistantTurn={input.onForkAssistantTurn}
|
onForkAssistantTurn={input.onForkAssistantTurn}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user