Persist completed run elapsed time
This commit is contained in:
+58
-24
@@ -3799,34 +3799,68 @@ def _annotate_last_assistant_elapsed(
|
|||||||
session_id: str,
|
session_id: str,
|
||||||
elapsed_ms: int,
|
elapsed_ms: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
path = _session_json_path(directory, session_id)
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(path.read_text(encoding='utf-8'))
|
stored = load_agent_session(session_id, directory=directory)
|
||||||
except (OSError, json.JSONDecodeError):
|
except (
|
||||||
|
FileNotFoundError,
|
||||||
|
OSError,
|
||||||
|
json.JSONDecodeError,
|
||||||
|
KeyError,
|
||||||
|
TypeError,
|
||||||
|
ValueError,
|
||||||
|
):
|
||||||
return
|
return
|
||||||
messages = data.get('messages')
|
|
||||||
display_messages = data.get('display_messages')
|
messages, messages_changed = _annotate_last_assistant_in_messages(
|
||||||
if not isinstance(display_messages, list):
|
stored.messages,
|
||||||
display_messages = messages
|
elapsed_ms,
|
||||||
if not isinstance(messages, list) and not isinstance(display_messages, list):
|
)
|
||||||
|
display_messages: tuple[dict[str, Any], ...] = ()
|
||||||
|
display_changed = False
|
||||||
|
if stored.display_messages:
|
||||||
|
display_messages, display_changed = _annotate_last_assistant_in_messages(
|
||||||
|
stored.display_messages,
|
||||||
|
elapsed_ms,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not messages_changed and not display_changed:
|
||||||
return
|
return
|
||||||
for message_list in (messages, display_messages):
|
|
||||||
if not isinstance(message_list, list):
|
save_agent_session(
|
||||||
|
replace(
|
||||||
|
stored,
|
||||||
|
messages=messages if messages_changed else stored.messages,
|
||||||
|
display_messages=(
|
||||||
|
display_messages
|
||||||
|
if display_changed
|
||||||
|
else stored.display_messages
|
||||||
|
),
|
||||||
|
),
|
||||||
|
directory=directory,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _annotate_last_assistant_in_messages(
|
||||||
|
messages: tuple[dict[str, Any], ...] | tuple[Any, ...],
|
||||||
|
elapsed_ms: int,
|
||||||
|
) -> tuple[tuple[dict[str, Any], ...], bool]:
|
||||||
|
updated = [dict(message) for message in messages if isinstance(message, dict)]
|
||||||
|
for index in range(len(updated) - 1, -1, -1):
|
||||||
|
message = updated[index]
|
||||||
|
if message.get('role') != 'assistant':
|
||||||
continue
|
continue
|
||||||
for message in reversed(message_list):
|
metadata = message.get('metadata')
|
||||||
if not isinstance(message, dict) or message.get('role') != 'assistant':
|
if not isinstance(metadata, dict):
|
||||||
continue
|
metadata = {}
|
||||||
metadata = message.get('metadata')
|
else:
|
||||||
if not isinstance(metadata, dict):
|
metadata = dict(metadata)
|
||||||
metadata = {}
|
if metadata.get('elapsed_ms') == elapsed_ms:
|
||||||
message['metadata'] = metadata
|
return tuple(updated), False
|
||||||
metadata['elapsed_ms'] = elapsed_ms
|
metadata['elapsed_ms'] = elapsed_ms
|
||||||
break
|
message['metadata'] = metadata
|
||||||
try:
|
updated[index] = message
|
||||||
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding='utf-8')
|
return tuple(updated), True
|
||||||
except OSError:
|
return tuple(updated), False
|
||||||
return
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def _read_session_title(directory: Path, session_id: str | None) -> str | None:
|
def _read_session_title(directory: Path, session_id: str | None) -> str | None:
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export function ThreadListSidebar({
|
|||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton size="lg">
|
<SidebarMenuButton size="lg">
|
||||||
<div className="aui-sidebar-header-icon-wrapper flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
<div className="aui-sidebar-header-icon-wrapper flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-accent text-sidebar-foreground">
|
||||||
<BotIcon className="aui-sidebar-header-icon size-4" />
|
<BotIcon className="aui-sidebar-header-icon size-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="aui-sidebar-header-heading mr-6 flex flex-col gap-0.5 leading-none">
|
<div className="aui-sidebar-header-heading mr-6 flex flex-col gap-0.5 leading-none">
|
||||||
@@ -647,7 +647,7 @@ function AccountMenu({
|
|||||||
{compact ? (
|
{compact ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex size-8 items-center justify-center rounded-full bg-sidebar-primary text-sidebar-primary-foreground transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
className="flex size-8 items-center justify-center rounded-full bg-sidebar-accent text-sidebar-foreground transition-colors hover:bg-sidebar-accent/80 hover:text-sidebar-accent-foreground"
|
||||||
aria-label="账号与统计"
|
aria-label="账号与统计"
|
||||||
title={account.username}
|
title={account.username}
|
||||||
>
|
>
|
||||||
@@ -660,7 +660,7 @@ function AccountMenu({
|
|||||||
type="button"
|
type="button"
|
||||||
className="flex w-full items-center gap-2 rounded-lg px-2 py-2 text-left transition-colors hover:bg-sidebar-accent"
|
className="flex w-full items-center gap-2 rounded-lg px-2 py-2 text-left transition-colors hover:bg-sidebar-accent"
|
||||||
>
|
>
|
||||||
<div className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
<div className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-sidebar-accent text-sidebar-foreground">
|
||||||
<UserIcon className="size-4" />
|
<UserIcon className="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user