feat: add Anthropic format conversion for chat runs and improvements (#347)
* fix: improve chat compression and tool display Context Compression Fixes: - Remove duplicate token calculation in compress() - Simplify compress() to only execute compression, not judge - Add buildConversationHistory() to preserve tool calls in LLM context - Remove unused estimateMessagesTokens() and contextLength parameter - Move all judgment logic to chat-run-socket.ts (uses accurate DB tokens) Tool Call Display Improvements: - Add tool execution duration display (format: 1.272s) - Add success/error status icons with circular backgrounds - Replace text error with SVG icon (X in red circle) - Replace old checkmark with polished green checkmark icon - Add i18n key 'chat.executionDuration' for all locales Bug Fixes: - Fix streaming-indicator stuck by adding try-finally in handleEvent - Add debug logging for compression flow diagnosis - Fix template syntax error in MessageList.vue Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(chat): convert conversation history to Anthropic format before sending to Gateway - Add convertToAnthropicFormat() to transform OpenAI format to Anthropic format - Handle DeepSeek reasoning_content in thinking blocks - Properly convert tool_use and tool_result blocks - Add convertFromAnthropicFormat() for parsing SSE responses - Handle stringified Python arrays in resume messages - Record debug history files for troubleshooting (original vs converted) - Fix tool_call_id validation to prevent empty ID errors - Clean internal Hermes fields (call_id, response_item_id) from tool_calls Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(chat): optimize message parsing and add debug logging - Only check for stringified arrays in assistant messages (performance) - Improve parsing error handling: keep original content on parse failure - Add debug logging for upstream events (reasoning/thinking tracking) - Log run.completed event keys for troubleshooting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(chat): add message pagination and reasoning sync improvements **Message Pagination:** - Add getSessionDetailPaginated() for paginated message loading - Query with DESC order then reverse in code for optimal performance - Remove listSessionsPaginated() (not needed) **Reasoning Sync:** - Add bidirectional reasoning merge in syncFromHermes - Memory → DB: preserve streamed reasoning from SSE events - DB → Memory: restore reasoning if Hermes Gateway fixes storage - Send resumed event after sync completes with complete messages - Fix reasoning field inconsistency: use unified 'reasoning' field **Message Parsing:** - Only parse stringified arrays for assistant messages (performance) - Improve parse error handling: keep original content on failure - Add debug logging for upstream reasoning/thinking events **Bug Fixes:** - Fix reasoning content display: now works on both SSE and resume - Ensure reasoning is preserved across page refreshes via sync + resumed event Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: increase default pagination limit for messages to 500 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove auto-resumed event trigger and clean up debug code - Remove automatic resumed event trigger in syncFromHermes to avoid timing issues - Clean up unused imports (fs, join) - Remove debug history file logging code - Fix socket parameter passing in handleAbort, markCompleted, and syncFromHermes - Change usage emit from room broadcast to socket-only emit - Remove console.log debug statement Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: use reasoning field in convertToAnthropicFormat Change convertToAnthropicFormat to read from reasoning field instead of reasoning_content for consistency with database schema and frontend. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: parse stringified array content and improve logs - Parse stringified array format in run.completed to extract thinking/text/tool_use - Send parsed content to frontend via parsed_content/parsed_reasoning/parsed_tool_calls - Frontend updates last assistant message with parsed content - Remove ellipsis from log messages, show full content - Add detailed logging for conversion and parsing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: move finalOutputTrimmed outside else block * fix(chat): handle double-serialized content in resumeSession - Remove outer quotes before parsing stringified array format - Updated changelog for v0.5.2 and v0.5.3 with multilingual support - Fixed message pagination with DESC query + array reverse Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(chat): improve error logging for resume parsing - Add detailed logging for double-serialized content parsing - Log content preview when parsing fails to diagnose issues Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * revert(chat): use simple Python-to-JSON replacement - Revert to simple .replace(/'/g, '"') approach - Parsing failures will keep original content as-is Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -134,10 +134,14 @@ export function startRunViaSocket(
|
||||
// All event handlers share the same cleanup logic
|
||||
const handleEvent = (event: RunEvent) => {
|
||||
if (closed) return
|
||||
onEvent(event)
|
||||
if (event.event === 'run.completed' || event.event === 'run.failed') {
|
||||
cleanup()
|
||||
onDone()
|
||||
try {
|
||||
onEvent(event)
|
||||
} finally {
|
||||
if (event.event === 'run.completed' || event.event === 'run.failed') {
|
||||
console.log('[startRunViaSocket] Run completed/failed, calling cleanup and onDone', event.event)
|
||||
cleanup()
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,8 +119,9 @@ onBeforeUnmount(() => {
|
||||
const thinkingDurationMs = computed<number | null>(() => {
|
||||
const ob = chatStore.getThinkingObservation(props.message.id);
|
||||
if (!ob?.startedAt) return null;
|
||||
const end = ob.endedAt ?? (props.message.isStreaming ? nowTick.value : ob.startedAt);
|
||||
return Math.max(0, end - ob.startedAt);
|
||||
const startedAt = ob.startedAt!; // Non-null assertion after check
|
||||
const end = ob?.endedAt ?? (props.message.isStreaming ? nowTick.value : startedAt);
|
||||
return Math.max(0, end - startedAt);
|
||||
});
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
@@ -762,6 +763,7 @@ const renderedToolResult = computed(() => {
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
line-height: 14px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.tool-details {
|
||||
|
||||
@@ -18,6 +18,14 @@ function formatTokens(n: number): string {
|
||||
return String(n)
|
||||
}
|
||||
|
||||
function formatToolDuration(seconds: number): string {
|
||||
if (seconds < 1) return `${Math.round(seconds * 1000)}ms`
|
||||
if (seconds < 60) return `${Math.round(seconds * 10) / 10}s`
|
||||
const mins = Math.floor(seconds / 60)
|
||||
const secs = Math.round(seconds % 60)
|
||||
return `${mins}m ${secs}s`
|
||||
}
|
||||
|
||||
const displayMessages = computed(() =>
|
||||
chatStore.messages.filter((m) => m.role !== "tool"),
|
||||
);
|
||||
@@ -198,13 +206,52 @@ watch(currentToolCalls, () => {
|
||||
<span v-if="tc.toolPreview" class="tool-call-preview">{{
|
||||
tc.toolPreview
|
||||
}}</span>
|
||||
<span
|
||||
v-if="tc.toolDuration && tc.toolStatus !== 'running'"
|
||||
class="tool-call-duration"
|
||||
:title="$t('chat.executionDuration')"
|
||||
>{{ formatToolDuration(tc.toolDuration) }}</span
|
||||
>
|
||||
<svg
|
||||
v-if="tc.toolStatus === 'done'"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
class="tool-call-success-icon"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" fill="currentColor" fill-opacity="0.15"/>
|
||||
<path
|
||||
d="M8 12L11 15L16 9"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
v-if="tc.toolStatus === 'running'"
|
||||
class="tool-call-spinner"
|
||||
></span>
|
||||
<span v-if="tc.toolStatus === 'error'" class="tool-call-error">{{
|
||||
t("chat.error")
|
||||
}}</span>
|
||||
<svg
|
||||
v-if="tc.toolStatus === 'error'"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
class="tool-call-error-icon"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" fill="currentColor" fill-opacity="0.15"/>
|
||||
<path
|
||||
d="M15 9L9 15M9 9L15 15"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -334,13 +381,30 @@ watch(currentToolCalls, () => {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tool-call-error {
|
||||
font-size: 9px;
|
||||
color: $error;
|
||||
background: rgba($error, 0.08);
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
line-height: 14px;
|
||||
.tool-call-error-icon {
|
||||
color: #ff4d4f;
|
||||
flex-shrink: 0;
|
||||
margin-left: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tool-call-duration {
|
||||
font-size: 10px;
|
||||
color: $text-muted;
|
||||
font-family: $font-code;
|
||||
margin-left: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tool-call-success-icon {
|
||||
color: #52c41a;
|
||||
flex-shrink: 0;
|
||||
margin-left: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
||||
@@ -5,6 +5,29 @@ export interface ChangelogEntry {
|
||||
}
|
||||
|
||||
export const changelog: ChangelogEntry[] = [
|
||||
{
|
||||
version: '0.5.3',
|
||||
date: '2026-04-30',
|
||||
changes: [
|
||||
'changelog.new_0_5_3_1',
|
||||
'changelog.new_0_5_3_2',
|
||||
'changelog.new_0_5_3_3',
|
||||
'changelog.new_0_5_3_4',
|
||||
'changelog.new_0_5_3_5',
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '0.5.2',
|
||||
date: '2026-04-29',
|
||||
changes: [
|
||||
'changelog.new_0_5_2_1',
|
||||
'changelog.new_0_5_2_2',
|
||||
'changelog.new_0_5_2_3',
|
||||
'changelog.new_0_5_2_4',
|
||||
'changelog.new_0_5_2_5',
|
||||
'changelog.new_0_5_2_6',
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '0.5.1',
|
||||
date: '2026-04-29',
|
||||
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
arguments: 'Argumente',
|
||||
result: 'Ergebnis',
|
||||
truncated: '... (abgeschnitten)',
|
||||
thinkingLabel: 'Denkprozess',
|
||||
executionDuration: 'Execution time', thinkingLabel: 'Denkprozess',
|
||||
thinkingInProgress: 'Denkt…',
|
||||
thinkingShow: 'Denkprozess anzeigen',
|
||||
thinkingHide: 'Denkprozess ausblenden',
|
||||
@@ -563,6 +563,17 @@ jobTriggered: 'Job ausgelost',
|
||||
|
||||
// Anderungsprotokoll
|
||||
changelog: {
|
||||
new_0_5_3_1: 'Improve reasoning process display with persistence across page refreshes',
|
||||
new_0_5_3_2: 'Optimize stringified array format parsing to extract thinking/text/tool_calls',
|
||||
new_0_5_3_3: 'Improve log display by removing ellipsis and showing full content',
|
||||
new_0_5_3_4: 'Add detailed logging for format conversion and parsing',
|
||||
new_0_5_3_5: 'Optimize token calculation to accurately include tool results',
|
||||
new_0_5_2_1: 'Convert conversation history to Anthropic format before sending to Gateway',
|
||||
new_0_5_2_2: 'Add bidirectional reasoning sync between memory and database',
|
||||
new_0_5_2_3: 'Add message pagination with DESC query + array reverse for performance',
|
||||
new_0_5_2_4: 'Clean up debug code and unused imports',
|
||||
new_0_5_2_5: 'Remove auto-resumed event trigger to avoid timing issues',
|
||||
new_0_5_2_6: 'Use reasoning field consistently across codebase',
|
||||
new_0_5_1_1: 'Auto-sync Hermes history sessions on first startup',
|
||||
new_0_5_1_2: 'Fix session sync failure with old Hermes versions (backward compatible)',
|
||||
new_0_5_1_3: 'Smart cleanup of exclusive platform credentials on profile clone (Telegram, Discord, Slack, etc.)',
|
||||
|
||||
@@ -154,6 +154,7 @@ export default {
|
||||
arguments: 'Arguments',
|
||||
result: 'Result',
|
||||
truncated: '... (truncated)',
|
||||
executionDuration: 'Execution time',
|
||||
thinkingLabel: 'Thinking',
|
||||
thinkingInProgress: 'Thinking…',
|
||||
thinkingShow: 'Show thinking',
|
||||
@@ -727,6 +728,17 @@ export default {
|
||||
|
||||
// Changelog
|
||||
changelog: {
|
||||
new_0_5_3_1: 'Improve reasoning process display with persistence across page refreshes',
|
||||
new_0_5_3_2: 'Optimize stringified array format parsing to extract thinking/text/tool_calls',
|
||||
new_0_5_3_3: 'Improve log display by removing ellipsis and showing full content',
|
||||
new_0_5_3_4: 'Add detailed logging for format conversion and parsing',
|
||||
new_0_5_3_5: 'Optimize token calculation to accurately include tool results',
|
||||
new_0_5_2_1: 'Convert conversation history to Anthropic format before sending to Gateway',
|
||||
new_0_5_2_2: 'Add bidirectional reasoning sync between memory and database',
|
||||
new_0_5_2_3: 'Add message pagination with DESC query + array reverse for performance',
|
||||
new_0_5_2_4: 'Clean up debug code and unused imports',
|
||||
new_0_5_2_5: 'Remove auto-resumed event trigger to avoid timing issues',
|
||||
new_0_5_2_6: 'Use reasoning field consistently across codebase',
|
||||
new_0_5_1_1: 'Auto-sync Hermes history sessions on first startup',
|
||||
new_0_5_1_2: 'Fix session sync failure with old Hermes versions (backward compatible)',
|
||||
new_0_5_1_3: 'Smart cleanup of exclusive platform credentials on profile clone (Telegram, Discord, Slack, etc.)',
|
||||
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
arguments: 'Argumentos',
|
||||
result: 'Resultado',
|
||||
truncated: '... (truncado)',
|
||||
thinkingLabel: 'Pensamiento',
|
||||
executionDuration: 'Execution time', thinkingLabel: 'Pensamiento',
|
||||
thinkingInProgress: 'Pensando…',
|
||||
thinkingShow: 'Mostrar pensamiento',
|
||||
thinkingHide: 'Ocultar pensamiento',
|
||||
@@ -563,6 +563,17 @@ jobTriggered: 'Job ejecutado',
|
||||
|
||||
// Registro de cambios
|
||||
changelog: {
|
||||
new_0_5_3_1: 'Improve reasoning process display with persistence across page refreshes',
|
||||
new_0_5_3_2: 'Optimize stringified array format parsing to extract thinking/text/tool_calls',
|
||||
new_0_5_3_3: 'Improve log display by removing ellipsis and showing full content',
|
||||
new_0_5_3_4: 'Add detailed logging for format conversion and parsing',
|
||||
new_0_5_3_5: 'Optimize token calculation to accurately include tool results',
|
||||
new_0_5_2_1: 'Convert conversation history to Anthropic format before sending to Gateway',
|
||||
new_0_5_2_2: 'Add bidirectional reasoning sync between memory and database',
|
||||
new_0_5_2_3: 'Add message pagination with DESC query + array reverse for performance',
|
||||
new_0_5_2_4: 'Clean up debug code and unused imports',
|
||||
new_0_5_2_5: 'Remove auto-resumed event trigger to avoid timing issues',
|
||||
new_0_5_2_6: 'Use reasoning field consistently across codebase',
|
||||
new_0_5_1_1: 'Auto-sync Hermes history sessions on first startup',
|
||||
new_0_5_1_2: 'Fix session sync failure with old Hermes versions (backward compatible)',
|
||||
new_0_5_1_3: 'Smart cleanup of exclusive platform credentials on profile clone (Telegram, Discord, Slack, etc.)',
|
||||
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
arguments: 'Arguments',
|
||||
result: 'Resultat',
|
||||
truncated: '... (tronque)',
|
||||
thinkingLabel: 'Raisonnement',
|
||||
executionDuration: 'Execution time', thinkingLabel: 'Raisonnement',
|
||||
thinkingInProgress: 'En réflexion…',
|
||||
thinkingShow: 'Afficher le raisonnement',
|
||||
thinkingHide: 'Masquer le raisonnement',
|
||||
@@ -563,6 +563,17 @@ jobTriggered: 'Job declenche',
|
||||
|
||||
// Journal des modifications
|
||||
changelog: {
|
||||
new_0_5_3_1: 'Improve reasoning process display with persistence across page refreshes',
|
||||
new_0_5_3_2: 'Optimize stringified array format parsing to extract thinking/text/tool_calls',
|
||||
new_0_5_3_3: 'Improve log display by removing ellipsis and showing full content',
|
||||
new_0_5_3_4: 'Add detailed logging for format conversion and parsing',
|
||||
new_0_5_3_5: 'Optimize token calculation to accurately include tool results',
|
||||
new_0_5_2_1: 'Convert conversation history to Anthropic format before sending to Gateway',
|
||||
new_0_5_2_2: 'Add bidirectional reasoning sync between memory and database',
|
||||
new_0_5_2_3: 'Add message pagination with DESC query + array reverse for performance',
|
||||
new_0_5_2_4: 'Clean up debug code and unused imports',
|
||||
new_0_5_2_5: 'Remove auto-resumed event trigger to avoid timing issues',
|
||||
new_0_5_2_6: 'Use reasoning field consistently across codebase',
|
||||
new_0_5_1_1: 'Auto-sync Hermes history sessions on first startup',
|
||||
new_0_5_1_2: 'Fix session sync failure with old Hermes versions (backward compatible)',
|
||||
new_0_5_1_3: 'Smart cleanup of exclusive platform credentials on profile clone (Telegram, Discord, Slack, etc.)',
|
||||
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
arguments: '引数',
|
||||
result: '結果',
|
||||
truncated: '... (省略)',
|
||||
thinkingLabel: '思考過程',
|
||||
executionDuration: 'Execution time', thinkingLabel: '思考過程',
|
||||
thinkingInProgress: '思考中…',
|
||||
thinkingShow: '思考過程を表示',
|
||||
thinkingHide: '思考過程を隠す',
|
||||
@@ -563,6 +563,17 @@ export default {
|
||||
|
||||
// 更新履歴
|
||||
changelog: {
|
||||
new_0_5_3_1: 'Improve reasoning process display with persistence across page refreshes',
|
||||
new_0_5_3_2: 'Optimize stringified array format parsing to extract thinking/text/tool_calls',
|
||||
new_0_5_3_3: 'Improve log display by removing ellipsis and showing full content',
|
||||
new_0_5_3_4: 'Add detailed logging for format conversion and parsing',
|
||||
new_0_5_3_5: 'Optimize token calculation to accurately include tool results',
|
||||
new_0_5_2_1: 'Convert conversation history to Anthropic format before sending to Gateway',
|
||||
new_0_5_2_2: 'Add bidirectional reasoning sync between memory and database',
|
||||
new_0_5_2_3: 'Add message pagination with DESC query + array reverse for performance',
|
||||
new_0_5_2_4: 'Clean up debug code and unused imports',
|
||||
new_0_5_2_5: 'Remove auto-resumed event trigger to avoid timing issues',
|
||||
new_0_5_2_6: 'Use reasoning field consistently across codebase',
|
||||
new_0_5_1_1: 'Auto-sync Hermes history sessions on first startup',
|
||||
new_0_5_1_2: 'Fix session sync failure with old Hermes versions (backward compatible)',
|
||||
new_0_5_1_3: 'Smart cleanup of exclusive platform credentials on profile clone (Telegram, Discord, Slack, etc.)',
|
||||
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
arguments: '인수',
|
||||
result: '결과',
|
||||
truncated: '... (잘림)',
|
||||
thinkingLabel: '사고 과정',
|
||||
executionDuration: 'Execution time', thinkingLabel: '사고 과정',
|
||||
thinkingInProgress: '사고 중…',
|
||||
thinkingShow: '사고 과정 펼치기',
|
||||
thinkingHide: '사고 과정 접기',
|
||||
@@ -563,6 +563,17 @@ export default {
|
||||
|
||||
// 변경 이력
|
||||
changelog: {
|
||||
new_0_5_3_1: 'Improve reasoning process display with persistence across page refreshes',
|
||||
new_0_5_3_2: 'Optimize stringified array format parsing to extract thinking/text/tool_calls',
|
||||
new_0_5_3_3: 'Improve log display by removing ellipsis and showing full content',
|
||||
new_0_5_3_4: 'Add detailed logging for format conversion and parsing',
|
||||
new_0_5_3_5: 'Optimize token calculation to accurately include tool results',
|
||||
new_0_5_2_1: 'Convert conversation history to Anthropic format before sending to Gateway',
|
||||
new_0_5_2_2: 'Add bidirectional reasoning sync between memory and database',
|
||||
new_0_5_2_3: 'Add message pagination with DESC query + array reverse for performance',
|
||||
new_0_5_2_4: 'Clean up debug code and unused imports',
|
||||
new_0_5_2_5: 'Remove auto-resumed event trigger to avoid timing issues',
|
||||
new_0_5_2_6: 'Use reasoning field consistently across codebase',
|
||||
new_0_5_1_1: 'Auto-sync Hermes history sessions on first startup',
|
||||
new_0_5_1_2: 'Fix session sync failure with old Hermes versions (backward compatible)',
|
||||
new_0_5_1_3: 'Smart cleanup of exclusive platform credentials on profile clone (Telegram, Discord, Slack, etc.)',
|
||||
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
arguments: 'Argumentos',
|
||||
result: 'Resultado',
|
||||
truncated: '... (truncado)',
|
||||
thinkingLabel: 'Raciocínio',
|
||||
executionDuration: 'Execution time', thinkingLabel: 'Raciocínio',
|
||||
thinkingInProgress: 'Pensando…',
|
||||
thinkingShow: 'Mostrar raciocínio',
|
||||
thinkingHide: 'Ocultar raciocínio',
|
||||
@@ -563,6 +563,17 @@ jobTriggered: 'Job acionado',
|
||||
|
||||
// Registro de alteracoes
|
||||
changelog: {
|
||||
new_0_5_3_1: 'Improve reasoning process display with persistence across page refreshes',
|
||||
new_0_5_3_2: 'Optimize stringified array format parsing to extract thinking/text/tool_calls',
|
||||
new_0_5_3_3: 'Improve log display by removing ellipsis and showing full content',
|
||||
new_0_5_3_4: 'Add detailed logging for format conversion and parsing',
|
||||
new_0_5_3_5: 'Optimize token calculation to accurately include tool results',
|
||||
new_0_5_2_1: 'Convert conversation history to Anthropic format before sending to Gateway',
|
||||
new_0_5_2_2: 'Add bidirectional reasoning sync between memory and database',
|
||||
new_0_5_2_3: 'Add message pagination with DESC query + array reverse for performance',
|
||||
new_0_5_2_4: 'Clean up debug code and unused imports',
|
||||
new_0_5_2_5: 'Remove auto-resumed event trigger to avoid timing issues',
|
||||
new_0_5_2_6: 'Use reasoning field consistently across codebase',
|
||||
new_0_5_1_1: 'Auto-sync Hermes history sessions on first startup',
|
||||
new_0_5_1_2: 'Fix session sync failure with old Hermes versions (backward compatible)',
|
||||
new_0_5_1_3: 'Smart cleanup of exclusive platform credentials on profile clone (Telegram, Discord, Slack, etc.)',
|
||||
|
||||
@@ -154,6 +154,7 @@ export default {
|
||||
arguments: '参数',
|
||||
result: '结果',
|
||||
truncated: '... (已截断)',
|
||||
executionDuration: '执行时长',
|
||||
thinkingLabel: '思考过程',
|
||||
thinkingInProgress: '思考中…',
|
||||
thinkingShow: '展开思考过程',
|
||||
@@ -729,6 +730,17 @@ export default {
|
||||
|
||||
// 更新日志
|
||||
changelog: {
|
||||
new_0_5_3_1: '改进思考过程显示,支持页面刷新后持久化',
|
||||
new_0_5_3_2: '优化字符串化数组格式解析,自动提取思考/文本/工具调用',
|
||||
new_0_5_3_3: '改进日志显示,移除省略号完整展示日志内容',
|
||||
new_0_5_3_4: '添加详细的格式转换和解析日志记录',
|
||||
new_0_5_3_5: '优化 token 计算,准确包含 tool 结果',
|
||||
new_0_5_2_1: '将对话历史转换为 Anthropic 格式后发送给 Gateway',
|
||||
new_0_5_2_2: '添加内存和数据库之间的双向思考过程同步',
|
||||
new_0_5_2_3: '添加消息分页功能(DESC 查询 + 数组反转,性能优化)',
|
||||
new_0_5_2_4: '清理调试代码和未使用的导入',
|
||||
new_0_5_2_5: '移除自动 resumed 事件触发,避免时序问题',
|
||||
new_0_5_2_6: '统一使用 reasoning 字段',
|
||||
new_0_5_1_1: '首次启动时自动同步 Hermes 历史会话',
|
||||
new_0_5_1_2: '修复旧版本 Hermes 会话同步失败问题(向后兼容)',
|
||||
new_0_5_1_3: 'Profile 克隆时智能清理独占平台凭据(Telegram、Discord、Slack 等)',
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface Message {
|
||||
toolArgs?: string
|
||||
toolResult?: string
|
||||
toolStatus?: 'running' | 'done' | 'error'
|
||||
toolDuration?: number // 工具执行时长(秒)
|
||||
isStreaming?: boolean
|
||||
attachments?: Attachment[]
|
||||
// 思考/推理文本。两条来源:
|
||||
@@ -615,8 +616,10 @@ export const useChatStore = defineStore('chat', () => {
|
||||
|
||||
// Helper to clean up this session's stream state
|
||||
const cleanup = () => {
|
||||
console.log('[sendMessage] cleanup called, deleting stream state for sid:', sid)
|
||||
streamStates.value.delete(sid)
|
||||
serverWorking.value.delete(sid)
|
||||
console.log('[sendMessage] cleanup done, isStreaming now:', isStreaming.value)
|
||||
}
|
||||
|
||||
// Per-run flags used to detect silently-swallowed errors at run.completed.
|
||||
@@ -765,7 +768,13 @@ export const useChatStore = defineStore('chat', () => {
|
||||
)
|
||||
if (toolMsgs.length > 0) {
|
||||
const last = toolMsgs[toolMsgs.length - 1]
|
||||
updateMessage(sid, last.id, { toolStatus: 'done' })
|
||||
// Check if tool errored
|
||||
const hasError = (evt as any).error === true
|
||||
const duration = (evt as any).duration
|
||||
updateMessage(sid, last.id, {
|
||||
toolStatus: hasError ? 'error' : 'done',
|
||||
toolDuration: duration,
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
@@ -790,17 +799,38 @@ export const useChatStore = defineStore('chat', () => {
|
||||
// stream). If we never produced assistant text but the gateway
|
||||
// reports a non-empty output, fall back to rendering it as a
|
||||
// single assistant message so the user actually sees the reply.
|
||||
const finalOutput =
|
||||
typeof evt.output === 'string' ? evt.output : ''
|
||||
const finalOutputTrimmed = finalOutput.trim()
|
||||
if (!runProducedAssistantText && finalOutputTrimmed !== '') {
|
||||
addMessage(sid, {
|
||||
id: uid(),
|
||||
role: 'assistant',
|
||||
content: finalOutput,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
runProducedAssistantText = true
|
||||
|
||||
// Check if backend provided parsed content (from stringified array format)
|
||||
let finalOutputTrimmed = ''
|
||||
if ((evt as any).parsed_content !== undefined) {
|
||||
// Backend has parsed stringified array format, update last assistant message
|
||||
const msgs = getSessionMsgs(sid)
|
||||
const lastAssistant = [...msgs].reverse().find(m => m.role === 'assistant')
|
||||
if (lastAssistant) {
|
||||
updateMessage(sid, lastAssistant.id, {
|
||||
content: (evt as any).parsed_content || '',
|
||||
})
|
||||
if ((evt as any).parsed_reasoning) {
|
||||
updateMessage(sid, lastAssistant.id, {
|
||||
reasoning: (evt as any).parsed_reasoning,
|
||||
})
|
||||
}
|
||||
finalOutputTrimmed = ((evt as any).parsed_content || '').trim()
|
||||
}
|
||||
} else {
|
||||
// Fallback to output field (legacy behavior)
|
||||
const finalOutput =
|
||||
typeof evt.output === 'string' ? evt.output : ''
|
||||
finalOutputTrimmed = finalOutput.trim()
|
||||
if (!runProducedAssistantText && finalOutputTrimmed !== '') {
|
||||
addMessage(sid, {
|
||||
id: uid(),
|
||||
role: 'assistant',
|
||||
content: finalOutput,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
runProducedAssistantText = true
|
||||
}
|
||||
}
|
||||
// Workaround for upstream hermes-agent bug: when the agent
|
||||
// layer silently swallows an error (e.g. invalid API key,
|
||||
@@ -875,6 +905,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
},
|
||||
// onDone
|
||||
() => {
|
||||
console.log('[sendMessage] onDone callback called, cleaning up stream state')
|
||||
const msgs = getSessionMsgs(sid)
|
||||
const last = msgs[msgs.length - 1]
|
||||
if (last?.isStreaming) {
|
||||
@@ -1076,7 +1107,11 @@ export const useChatStore = defineStore('chat', () => {
|
||||
const msgs = getSessionMsgs(sid)
|
||||
const toolMsgs = msgs.filter(m => m.role === 'tool' && m.toolStatus === 'running')
|
||||
if (toolMsgs.length > 0) {
|
||||
updateMessage(sid, toolMsgs[toolMsgs.length - 1].id, { toolStatus: 'done' })
|
||||
const hasError = (evt as any).error === true
|
||||
updateMessage(sid, toolMsgs[toolMsgs.length - 1].id, {
|
||||
toolStatus: hasError ? 'error' : 'done',
|
||||
toolDuration: (evt as any).duration,
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
@@ -1096,15 +1131,35 @@ export const useChatStore = defineStore('chat', () => {
|
||||
target.outputTokens = (evt as any).outputTokens
|
||||
}
|
||||
}
|
||||
const finalOutput = typeof evt.output === 'string' ? evt.output : ''
|
||||
const finalOutputTrimmed = finalOutput.trim()
|
||||
if (!runProducedAssistantText && finalOutputTrimmed !== '') {
|
||||
addMessage(sid, {
|
||||
id: uid(),
|
||||
role: 'assistant',
|
||||
content: finalOutput,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
// Check if backend provided parsed content (from stringified array format)
|
||||
let finalOutputTrimmed = ''
|
||||
if ((evt as any).parsed_content !== undefined) {
|
||||
// Backend has parsed stringified array format, update last assistant message
|
||||
const msgs = getSessionMsgs(sid)
|
||||
const lastAssistant = [...msgs].reverse().find(m => m.role === 'assistant')
|
||||
if (lastAssistant) {
|
||||
updateMessage(sid, lastAssistant.id, {
|
||||
content: (evt as any).parsed_content || '',
|
||||
})
|
||||
if ((evt as any).parsed_reasoning) {
|
||||
updateMessage(sid, lastAssistant.id, {
|
||||
reasoning: (evt as any).parsed_reasoning,
|
||||
})
|
||||
}
|
||||
finalOutputTrimmed = ((evt as any).parsed_content || '').trim()
|
||||
}
|
||||
} else {
|
||||
// Fallback to output field (legacy behavior)
|
||||
const finalOutput = typeof evt.output === 'string' ? evt.output : ''
|
||||
finalOutputTrimmed = finalOutput.trim()
|
||||
if (!runProducedAssistantText && finalOutputTrimmed !== '') {
|
||||
addMessage(sid, {
|
||||
id: uid(),
|
||||
role: 'assistant',
|
||||
content: finalOutput,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
const swallowedError = !runProducedAssistantText && !runHadToolActivity && finalOutputTrimmed === ''
|
||||
if (swallowedError) {
|
||||
|
||||
@@ -261,9 +261,9 @@ onMounted(async () => {
|
||||
|
||||
.log-message {
|
||||
color: $text-secondary;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user