1
0
Fork 0

Show usernames in chat

This commit is contained in:
Pabloader 2026-04-08 09:47:59 +00:00
parent ed83c9a0b8
commit ae0d232331
3 changed files with 51 additions and 15 deletions

View File

@ -2,7 +2,7 @@ import { ContentEditable } from "@common/components/ContentEditable";
import { highlight } from "@common/highlight";
import { useInputState } from "@common/hooks/useInputState";
import clsx from "clsx";
import { Check, ChevronsRight, Edit2, RefreshCw, Sparkles, Trash2, X } from "lucide-preact";
import { Check, ChevronsRight, Edit2, GitFork, RefreshCw, Sparkles, Trash2, X } from "lucide-preact";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import styles from '../assets/chat-sidebar.module.css';
import sidebarStyles from '../assets/sidebar.module.css';
@ -20,6 +20,8 @@ interface RoleHeaderProps {
}
const RoleHeader = ({ message, chatMessages }: RoleHeaderProps) => {
const { currentWorld, userName } = useAppState();
const toolName = useMemo(() => {
if (message.role !== 'tool') return;
for (const m of chatMessages.toReversed()) {
@ -29,12 +31,28 @@ const RoleHeader = ({ message, chatMessages }: RoleHeaderProps) => {
}
}, [message, chatMessages]);
let displayName: string = message.role;
let roleLabel = null;
if (message.role === 'tool' && toolName) {
displayName = toolName;
roleLabel = message.role;
} else if (currentWorld?.chatOnly) {
if (message.role === 'user' && userName) {
displayName = userName;
roleLabel = message.role;
} else if (message.role === 'assistant' && currentWorld.title) {
displayName = currentWorld.title;
roleLabel = message.role;
}
}
return (
<div class={styles.role}>
{message.role}
{toolName && (
{displayName}
{roleLabel && (
<span class={styles.toolBadge}>
{toolName}
{message.role}
</span>
)}
</div>
@ -362,6 +380,16 @@ export const ChatPanel = () => {
});
}, [currentStory, currentWorld, dispatch]);
const handleForkChat = useCallback((messageId: string) => {
if (!currentStory || !currentWorld) return;
dispatch({
type: 'DUPLICATE_STORY',
worldId: currentWorld.id,
id: currentStory.id,
upToMessageId: messageId,
});
}, [currentStory, currentWorld, dispatch]);
const handleStartEdit = useCallback((message: ChatMessage) => {
setEditingMessageId(message.id);
setEditingContent(message.content);
@ -416,6 +444,15 @@ export const ChatPanel = () => {
{!isLoading && canEdit && (
<div class={styles.messageActions}>
{currentWorld?.chatOnly && (
<button
class={styles.iconButton}
onClick={() => handleForkChat(message.id)}
title="Fork chat up to this message"
>
<GitFork size={12} />
</button>
)}
<button
class={styles.iconButton}
onClick={() => handleStartEdit(message)}

View File

@ -135,7 +135,7 @@ const WorldItem = ({
/>
</div>
) : (
<div class={clsx(styles.itemWrapper, isWorldActive && styles.active)}>
<div class={clsx(styles.itemWrapper, isWorldActive && styles.active)} onClick={isExpanded.setTrue}>
<button class={styles.expandButton} onClick={toggleExpand} title={isExpanded.value ? 'Collapse' : 'Expand'}>
{isExpanded.value ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
</button>

View File

@ -137,7 +137,7 @@ type Action =
| { type: 'EDIT_SCRATCHPAD'; worldId: string; id: string; text: string }
| { type: 'DELETE_STORY'; worldId: string; id: string }
| { type: 'SELECT_STORY'; worldId: string; id: string }
| { type: 'DUPLICATE_STORY'; worldId: string; id: string }
| { type: 'DUPLICATE_STORY'; worldId: string; id: string; upToMessageId?: string }
// Story lore
| { type: 'ADD_LORE_ENTRY'; worldId: string; storyId: string | null; entry: LoreEntry }
| { type: 'EDIT_LORE_ENTRY'; worldId: string; storyId: string | null; entryId: string; updates: Partial<LoreEntry> }
@ -278,7 +278,6 @@ function reducer(state: IState, action: Action): IState {
worlds: [...state.worlds, world],
currentWorldId: world.id,
currentStoryId: null,
currentTab: 'menu',
};
}
case 'RENAME_WORLD': {
@ -299,7 +298,6 @@ function reducer(state: IState, action: Action): IState {
...state,
currentWorldId: action.worldId,
currentStoryId: null,
currentTab: 'menu',
};
}
case 'CREATE_STORY': {
@ -320,7 +318,6 @@ function reducer(state: IState, action: Action): IState {
...updateWorld(state, action.worldId, w => ({ ...w, stories: [...w.stories, story] })),
currentWorldId: action.worldId,
currentStoryId: story.id,
currentTab: 'menu',
};
}
case 'RENAME_STORY': {
@ -347,20 +344,24 @@ function reducer(state: IState, action: Action): IState {
};
}
case 'SELECT_STORY': {
const world = state.worlds.find(w => w.id === action.worldId);
return {
...state,
currentWorldId: action.worldId,
currentStoryId: action.id,
currentTab: 'menu',
};
}
case 'DUPLICATE_STORY': {
const world = state.worlds.find(w => w.id === action.worldId);
const original = world?.stories.find(s => s.id === action.id);
if (!original) return state;
let chatMessages: ChatMessage[];
if (action.upToMessageId) {
const idx = original.chatMessages.findIndex(m => m.id === action.upToMessageId);
chatMessages = idx !== -1 ? original.chatMessages.slice(0, idx + 1) : [...original.chatMessages];
} else {
const firstMessage = original.chatMessages[0];
const chatMessages = world?.chatOnly && firstMessage && firstMessage.role === 'assistant' ? [firstMessage] : [];
chatMessages = world?.chatOnly && firstMessage && firstMessage.role === 'assistant' ? [firstMessage] : [];
}
const newStory: Story = {
id: crypto.randomUUID(),
title: `${original.title} (Copy)`,
@ -375,7 +376,6 @@ function reducer(state: IState, action: Action): IState {
return {
...updateWorld(state, action.worldId, w => ({ ...w, stories: [...w.stories, newStory] })),
currentStoryId: newStory.id,
currentTab: 'menu',
};
}
case 'ADD_LORE_ENTRY': {
@ -582,7 +582,6 @@ function reducer(state: IState, action: Action): IState {
worlds: [...state.worlds, world],
currentWorldId: world.id,
currentStoryId: null,
currentTab: 'menu',
};
}
}