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 { highlight } from "@common/highlight";
import { useInputState } from "@common/hooks/useInputState"; import { useInputState } from "@common/hooks/useInputState";
import clsx from "clsx"; 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 { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import styles from '../assets/chat-sidebar.module.css'; import styles from '../assets/chat-sidebar.module.css';
import sidebarStyles from '../assets/sidebar.module.css'; import sidebarStyles from '../assets/sidebar.module.css';
@ -20,6 +20,8 @@ interface RoleHeaderProps {
} }
const RoleHeader = ({ message, chatMessages }: RoleHeaderProps) => { const RoleHeader = ({ message, chatMessages }: RoleHeaderProps) => {
const { currentWorld, userName } = useAppState();
const toolName = useMemo(() => { const toolName = useMemo(() => {
if (message.role !== 'tool') return; if (message.role !== 'tool') return;
for (const m of chatMessages.toReversed()) { for (const m of chatMessages.toReversed()) {
@ -29,12 +31,28 @@ const RoleHeader = ({ message, chatMessages }: RoleHeaderProps) => {
} }
}, [message, chatMessages]); }, [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 ( return (
<div class={styles.role}> <div class={styles.role}>
{message.role} {displayName}
{toolName && ( {roleLabel && (
<span class={styles.toolBadge}> <span class={styles.toolBadge}>
{toolName} {message.role}
</span> </span>
)} )}
</div> </div>
@ -362,6 +380,16 @@ export const ChatPanel = () => {
}); });
}, [currentStory, currentWorld, dispatch]); }, [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) => { const handleStartEdit = useCallback((message: ChatMessage) => {
setEditingMessageId(message.id); setEditingMessageId(message.id);
setEditingContent(message.content); setEditingContent(message.content);
@ -416,6 +444,15 @@ export const ChatPanel = () => {
{!isLoading && canEdit && ( {!isLoading && canEdit && (
<div class={styles.messageActions}> <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 <button
class={styles.iconButton} class={styles.iconButton}
onClick={() => handleStartEdit(message)} onClick={() => handleStartEdit(message)}

View File

@ -135,7 +135,7 @@ const WorldItem = ({
/> />
</div> </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'}> <button class={styles.expandButton} onClick={toggleExpand} title={isExpanded.value ? 'Collapse' : 'Expand'}>
{isExpanded.value ? <ChevronDown size={12} /> : <ChevronRight size={12} />} {isExpanded.value ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
</button> </button>

View File

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