merge upstream and keep full message timestamps
This commit is contained in:
commit
f2be124c77
9 changed files with 44 additions and 26 deletions
|
|
@ -524,6 +524,7 @@ func (s *JSONLStore) AddMessage(
|
||||||
return s.addMsg(sessionKey, providers.Message{
|
return s.addMsg(sessionKey, providers.Message{
|
||||||
Role: role,
|
Role: role,
|
||||||
Content: content,
|
Content: content,
|
||||||
|
Timestamp: time.Now().Format(time.RFC3339Nano),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -539,6 +540,10 @@ func (s *JSONLStore) addMsg(sessionKey string, msg providers.Message) error {
|
||||||
l.Lock()
|
l.Lock()
|
||||||
defer l.Unlock()
|
defer l.Unlock()
|
||||||
|
|
||||||
|
if strings.TrimSpace(msg.Timestamp) == "" {
|
||||||
|
msg.Timestamp = time.Now().Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
|
||||||
// Append the message as a single JSON line.
|
// Append the message as a single JSON line.
|
||||||
line, err := json.Marshal(msg)
|
line, err := json.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,15 @@ func TestAddMessage_BasicRoundtrip(t *testing.T) {
|
||||||
if history[0].Role != "user" || history[0].Content != "hello" {
|
if history[0].Role != "user" || history[0].Content != "hello" {
|
||||||
t.Errorf("msg[0] = %+v", history[0])
|
t.Errorf("msg[0] = %+v", history[0])
|
||||||
}
|
}
|
||||||
|
if history[0].Timestamp == "" {
|
||||||
|
t.Errorf("msg[0].Timestamp is empty")
|
||||||
|
}
|
||||||
if history[1].Role != "assistant" || history[1].Content != "hi there" {
|
if history[1].Role != "assistant" || history[1].Content != "hi there" {
|
||||||
t.Errorf("msg[1] = %+v", history[1])
|
t.Errorf("msg[1] = %+v", history[1])
|
||||||
}
|
}
|
||||||
|
if history[1].Timestamp == "" {
|
||||||
|
t.Errorf("msg[1].Timestamp is empty")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddMessage_AutoCreatesSession(t *testing.T) {
|
func TestAddMessage_AutoCreatesSession(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ type Attachment struct {
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
Timestamp string `json:"timestamp,omitempty"`
|
||||||
Media []string `json:"media,omitempty"`
|
Media []string `json:"media,omitempty"`
|
||||||
Attachments []Attachment `json:"attachments,omitempty"`
|
Attachments []Attachment `json:"attachments,omitempty"`
|
||||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,10 @@ func (sm *SessionManager) AddFullMessage(sessionKey string, msg providers.Messag
|
||||||
sm.mu.Lock()
|
sm.mu.Lock()
|
||||||
defer sm.mu.Unlock()
|
defer sm.mu.Unlock()
|
||||||
|
|
||||||
|
if strings.TrimSpace(msg.Timestamp) == "" {
|
||||||
|
msg.Timestamp = time.Now().Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
|
||||||
session, ok := sm.sessions[sessionKey]
|
session, ok := sm.sessions[sessionKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
session = &Session{
|
session = &Session{
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ type sessionListItem struct {
|
||||||
type sessionChatMessage struct {
|
type sessionChatMessage struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
Timestamp string `json:"timestamp,omitempty"`
|
||||||
Media []string `json:"media,omitempty"`
|
Media []string `json:"media,omitempty"`
|
||||||
Attachments []sessionChatAttachment `json:"attachments,omitempty"`
|
Attachments []sessionChatAttachment `json:"attachments,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
@ -477,6 +478,7 @@ func visibleSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLen
|
||||||
|
|
||||||
for _, msg := range messages {
|
for _, msg := range messages {
|
||||||
attachments := sessionAttachments(msg)
|
attachments := sessionAttachments(msg)
|
||||||
|
timestamp := strings.TrimSpace(msg.Timestamp)
|
||||||
|
|
||||||
switch msg.Role {
|
switch msg.Role {
|
||||||
case "tool":
|
case "tool":
|
||||||
|
|
@ -486,6 +488,7 @@ func visibleSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLen
|
||||||
chatMsg := sessionChatMessage{
|
chatMsg := sessionChatMessage{
|
||||||
Role: "user",
|
Role: "user",
|
||||||
Content: msg.Content,
|
Content: msg.Content,
|
||||||
|
Timestamp: timestamp,
|
||||||
Media: append([]string(nil), msg.Media...),
|
Media: append([]string(nil), msg.Media...),
|
||||||
Attachments: attachments,
|
Attachments: attachments,
|
||||||
}
|
}
|
||||||
|
|
@ -500,12 +503,12 @@ func visibleSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLen
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
toolSummaryMessages := visibleAssistantToolSummaryMessages(msg.ToolCalls, toolFeedbackMaxArgsLength)
|
toolSummaryMessages := visibleAssistantToolSummaryMessages(msg.ToolCalls, timestamp, toolFeedbackMaxArgsLength)
|
||||||
if len(toolSummaryMessages) > 0 {
|
if len(toolSummaryMessages) > 0 {
|
||||||
transcript = append(transcript, toolSummaryMessages...)
|
transcript = append(transcript, toolSummaryMessages...)
|
||||||
}
|
}
|
||||||
|
|
||||||
visibleToolMessages := visibleAssistantToolMessages(msg.ToolCalls)
|
visibleToolMessages := visibleAssistantToolMessages(msg.ToolCalls, timestamp)
|
||||||
if len(visibleToolMessages) > 0 {
|
if len(visibleToolMessages) > 0 {
|
||||||
transcript = append(transcript, visibleToolMessages...)
|
transcript = append(transcript, visibleToolMessages...)
|
||||||
}
|
}
|
||||||
|
|
@ -534,6 +537,7 @@ func visibleSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLen
|
||||||
chatMsg := sessionChatMessage{
|
chatMsg := sessionChatMessage{
|
||||||
Role: "assistant",
|
Role: "assistant",
|
||||||
Content: content,
|
Content: content,
|
||||||
|
Timestamp: timestamp,
|
||||||
Media: append([]string(nil), msg.Media...),
|
Media: append([]string(nil), msg.Media...),
|
||||||
Attachments: attachments,
|
Attachments: attachments,
|
||||||
}
|
}
|
||||||
|
|
@ -686,6 +690,7 @@ func assistantMessageInternalOnly(msg providers.Message) bool {
|
||||||
|
|
||||||
func visibleAssistantToolSummaryMessages(
|
func visibleAssistantToolSummaryMessages(
|
||||||
toolCalls []providers.ToolCall,
|
toolCalls []providers.ToolCall,
|
||||||
|
timestamp string,
|
||||||
toolFeedbackMaxArgsLength int,
|
toolFeedbackMaxArgsLength int,
|
||||||
) []sessionChatMessage {
|
) []sessionChatMessage {
|
||||||
if len(toolCalls) == 0 {
|
if len(toolCalls) == 0 {
|
||||||
|
|
@ -716,6 +721,7 @@ func visibleAssistantToolSummaryMessages(
|
||||||
name,
|
name,
|
||||||
visibleAssistantToolSummaryText(tc, toolFeedbackMaxArgsLength),
|
visibleAssistantToolSummaryText(tc, toolFeedbackMaxArgsLength),
|
||||||
),
|
),
|
||||||
|
Timestamp: timestamp,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -745,7 +751,7 @@ func visibleAssistantToolSummaryText(
|
||||||
return utils.Truncate(strings.TrimSpace(argsJSON), toolFeedbackMaxArgsLength)
|
return utils.Truncate(strings.TrimSpace(argsJSON), toolFeedbackMaxArgsLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
func visibleAssistantToolMessages(toolCalls []providers.ToolCall) []sessionChatMessage {
|
func visibleAssistantToolMessages(toolCalls []providers.ToolCall, timestamp string) []sessionChatMessage {
|
||||||
if len(toolCalls) == 0 {
|
if len(toolCalls) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -763,6 +769,7 @@ func visibleAssistantToolMessages(toolCalls []providers.ToolCall) []sessionChatM
|
||||||
messages = append(messages, sessionChatMessage{
|
messages = append(messages, sessionChatMessage{
|
||||||
Role: "assistant",
|
Role: "assistant",
|
||||||
Content: content,
|
Content: content,
|
||||||
|
Timestamp: timestamp,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,8 +166,8 @@ func TestHandleGetSession_JSONLStorage(t *testing.T) {
|
||||||
|
|
||||||
sessionKey := legacyPicoSessionPrefix + "detail-jsonl"
|
sessionKey := legacyPicoSessionPrefix + "detail-jsonl"
|
||||||
for _, msg := range []providers.Message{
|
for _, msg := range []providers.Message{
|
||||||
{Role: "user", Content: "first"},
|
{Role: "user", Content: "first", Timestamp: "2026-04-23T11:05:00+08:00"},
|
||||||
{Role: "assistant", Content: "second"},
|
{Role: "assistant", Content: "second", Timestamp: "2026-04-23T11:06:00+08:00"},
|
||||||
{Role: "tool", Content: "ignored"},
|
{Role: "tool", Content: "ignored"},
|
||||||
} {
|
} {
|
||||||
if err := store.AddFullMessage(nil, sessionKey, msg); err != nil {
|
if err := store.AddFullMessage(nil, sessionKey, msg); err != nil {
|
||||||
|
|
@ -196,6 +196,7 @@ func TestHandleGetSession_JSONLStorage(t *testing.T) {
|
||||||
Messages []struct {
|
Messages []struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
} `json:"messages"`
|
} `json:"messages"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||||
|
|
@ -213,9 +214,15 @@ func TestHandleGetSession_JSONLStorage(t *testing.T) {
|
||||||
if resp.Messages[0].Role != "user" || resp.Messages[0].Content != "first" {
|
if resp.Messages[0].Role != "user" || resp.Messages[0].Content != "first" {
|
||||||
t.Fatalf("first message = %#v, want user/first", resp.Messages[0])
|
t.Fatalf("first message = %#v, want user/first", resp.Messages[0])
|
||||||
}
|
}
|
||||||
|
if resp.Messages[0].Timestamp != "2026-04-23T11:05:00+08:00" {
|
||||||
|
t.Fatalf("resp.Messages[0].Timestamp = %q, want %q", resp.Messages[0].Timestamp, "2026-04-23T11:05:00+08:00")
|
||||||
|
}
|
||||||
if resp.Messages[1].Role != "assistant" || resp.Messages[1].Content != "second" {
|
if resp.Messages[1].Role != "assistant" || resp.Messages[1].Content != "second" {
|
||||||
t.Fatalf("second message = %#v, want assistant/second", resp.Messages[1])
|
t.Fatalf("second message = %#v, want assistant/second", resp.Messages[1])
|
||||||
}
|
}
|
||||||
|
if resp.Messages[1].Timestamp != "2026-04-23T11:06:00+08:00" {
|
||||||
|
t.Fatalf("resp.Messages[1].Timestamp = %q, want %q", resp.Messages[1].Timestamp, "2026-04-23T11:06:00+08:00")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleGetSession_HidesHandledToolAttachmentsBackedByMediaRefs(t *testing.T) {
|
func TestHandleGetSession_HidesHandledToolAttachmentsBackedByMediaRefs(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export interface SessionDetail {
|
||||||
messages: {
|
messages: {
|
||||||
role: "user" | "assistant"
|
role: "user" | "assistant"
|
||||||
content: string
|
content: string
|
||||||
|
timestamp?: string
|
||||||
media?: string[]
|
media?: string[]
|
||||||
attachments?: {
|
attachments?: {
|
||||||
type?: "image" | "audio" | "video" | "file"
|
type?: "image" | "audio" | "video" | "file"
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export async function loadSessionMessages(
|
||||||
media: message.media,
|
media: message.media,
|
||||||
attachments: message.attachments,
|
attachments: message.attachments,
|
||||||
}),
|
}),
|
||||||
timestamp: fallbackTime,
|
timestamp: message.timestamp?.trim() || fallbackTime,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,20 +38,7 @@ export function formatMessageTime(dateRaw: number | string | Date): string {
|
||||||
if (!date.isValid()) {
|
if (!date.isValid()) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
const now = dayjs()
|
return date.format("YYYY-MM-DD HH:mm")
|
||||||
|
|
||||||
const isToday = date.isSame(now, "day")
|
|
||||||
const isThisYear = date.isSame(now, "year")
|
|
||||||
|
|
||||||
if (isToday) {
|
|
||||||
return date.format("LT")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isThisYear) {
|
|
||||||
return date.format("MMM D LT")
|
|
||||||
}
|
|
||||||
|
|
||||||
return date.format("ll LT")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePicoChat() {
|
export function usePicoChat() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue