feat(characters): support character-scoped conversations
This commit is contained in:
@@ -4,8 +4,8 @@ import { Result } from "@/utils/result";
|
||||
|
||||
const getHistoryMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("@/data/repositories/chat_repository", () => ({
|
||||
getChatRepository: () => ({ getHistory: getHistoryMock }),
|
||||
vi.mock("@/data/repositories/chat_repository_loader", () => ({
|
||||
loadChatRepository: async () => ({ getHistory: getHistoryMock }),
|
||||
}));
|
||||
|
||||
import {
|
||||
@@ -31,10 +31,22 @@ describe("fetchSplashLatestMessagePreview", () => {
|
||||
|
||||
const result = await fetchSplashLatestMessagePreview();
|
||||
|
||||
expect(getHistoryMock).toHaveBeenCalledWith(1, 0);
|
||||
expect(getHistoryMock).toHaveBeenCalledWith(
|
||||
"character_elio",
|
||||
1,
|
||||
0,
|
||||
);
|
||||
expect(Result.isOk(result) && result.data).toBe("Latest message");
|
||||
});
|
||||
|
||||
it("requests the selected character preview", async () => {
|
||||
getHistoryMock.mockResolvedValue(Result.ok({ messages: [] }));
|
||||
|
||||
await fetchSplashLatestMessagePreview("character_aria");
|
||||
|
||||
expect(getHistoryMock).toHaveBeenCalledWith("character_aria", 1, 0);
|
||||
});
|
||||
|
||||
it("returns null when history is empty", async () => {
|
||||
getHistoryMock.mockResolvedValue(Result.ok({ messages: [] }));
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Result, type Result as ResultT } from "@/utils/result";
|
||||
import { localChatMediaRowToBlob } from "./chat_media_blob";
|
||||
|
||||
export interface ResolveCachedChatMediaBlobInput {
|
||||
characterId: string;
|
||||
messageId: string;
|
||||
kind: ChatMediaKind;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { loadChatRepository } from "@/data/repositories/chat_repository_loader";
|
||||
import {
|
||||
resolveChatCacheOwnerKey,
|
||||
resolveChatConversationKey,
|
||||
type ChatCacheIdentityResolver,
|
||||
} from "@/data/repositories/chat_cache_identity";
|
||||
import { DEFAULT_CHARACTER_ID } from "@/data/constants/character";
|
||||
import { Result, type Result as ResultT } from "@/utils/result";
|
||||
|
||||
import type { SplashLatestMessageCache } from "./splash_latest_message_cache";
|
||||
@@ -10,21 +11,23 @@ import { getLatestSplashMessagePreview } from "./splash_latest_message_preview";
|
||||
|
||||
export interface LoadSplashLatestMessagePreviewInput {
|
||||
cache: SplashLatestMessageCache;
|
||||
fetchPreview?: () => Promise<ResultT<string | null>>;
|
||||
characterId?: string;
|
||||
fetchPreview?: (characterId: string) => Promise<ResultT<string | null>>;
|
||||
resolveIdentity?: ChatCacheIdentityResolver;
|
||||
}
|
||||
|
||||
export async function resolveSplashLatestMessageCacheIdentity(): Promise<
|
||||
string | null
|
||||
> {
|
||||
const result = await resolveChatCacheOwnerKey();
|
||||
export async function resolveSplashLatestMessageCacheIdentity(
|
||||
characterId = DEFAULT_CHARACTER_ID,
|
||||
): Promise<string | null> {
|
||||
const result = await resolveChatConversationKey(characterId);
|
||||
return Result.isOk(result) ? result.data : null;
|
||||
}
|
||||
|
||||
export async function loadSplashLatestMessagePreview({
|
||||
cache,
|
||||
characterId = DEFAULT_CHARACTER_ID,
|
||||
fetchPreview = fetchSplashLatestMessagePreview,
|
||||
resolveIdentity = resolveChatCacheOwnerKey,
|
||||
resolveIdentity = () => resolveChatConversationKey(characterId),
|
||||
}: LoadSplashLatestMessagePreviewInput): Promise<ResultT<string | null>> {
|
||||
const identityResult = await resolveIdentity();
|
||||
const identity = Result.isOk(identityResult) ? identityResult.data : null;
|
||||
@@ -34,18 +37,18 @@ export async function loadSplashLatestMessagePreview({
|
||||
if (cached) return Result.ok(cached.message);
|
||||
}
|
||||
|
||||
const result = await fetchPreview();
|
||||
const result = await fetchPreview(characterId);
|
||||
if (identity && Result.isOk(result)) {
|
||||
cache.set(identity, result.data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function fetchSplashLatestMessagePreview(): Promise<
|
||||
ResultT<string | null>
|
||||
> {
|
||||
export async function fetchSplashLatestMessagePreview(
|
||||
characterId = DEFAULT_CHARACTER_ID,
|
||||
): Promise<ResultT<string | null>> {
|
||||
const repository = await loadChatRepository();
|
||||
const result = await repository.getHistory(1, 0);
|
||||
const result = await repository.getHistory(characterId, 1, 0);
|
||||
if (Result.isErr(result)) return Result.err(result.error);
|
||||
|
||||
return Result.ok(getLatestSplashMessagePreview(result.data.messages));
|
||||
|
||||
@@ -17,6 +17,7 @@ import { isCacheableRemoteChatMediaUrl } from "./chat_media_url";
|
||||
const log = new Logger("LibChatUseCachedChatMediaUrl");
|
||||
|
||||
export interface UseCachedChatMediaUrlInput {
|
||||
characterId: string;
|
||||
messageId?: string | null;
|
||||
remoteUrl?: string | null;
|
||||
kind: ChatMediaKind;
|
||||
@@ -36,6 +37,7 @@ interface CachedMediaUrlState {
|
||||
}
|
||||
|
||||
export function useCachedChatMediaUrl({
|
||||
characterId,
|
||||
messageId,
|
||||
remoteUrl,
|
||||
kind,
|
||||
@@ -47,7 +49,9 @@ export function useCachedChatMediaUrl({
|
||||
const [failedCacheKey, setFailedCacheKey] = useState<string | null>(null);
|
||||
const fallbackUrl = remoteUrl ?? "";
|
||||
const stateKey =
|
||||
messageId && remoteUrl ? `${messageId}:${kind}:${remoteUrl}` : "";
|
||||
messageId && remoteUrl
|
||||
? `${characterId}:${messageId}:${kind}:${remoteUrl}`
|
||||
: "";
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -80,6 +84,7 @@ export function useCachedChatMediaUrl({
|
||||
|
||||
const resolveCachedMedia = async () => {
|
||||
const cachedResult = await resolveCachedChatMediaBlob({
|
||||
characterId,
|
||||
messageId,
|
||||
kind,
|
||||
});
|
||||
@@ -91,6 +96,7 @@ export function useCachedChatMediaUrl({
|
||||
}
|
||||
|
||||
const cacheResult = await cacheRemoteChatMediaBlob({
|
||||
characterId,
|
||||
messageId,
|
||||
kind,
|
||||
remoteUrl,
|
||||
@@ -114,7 +120,7 @@ export function useCachedChatMediaUrl({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [failedCacheKey, kind, messageId, remoteUrl, stateKey]);
|
||||
}, [characterId, failedCacheKey, kind, messageId, remoteUrl, stateKey]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user