10 "github.com/charmbracelet/crush/internal/db"
11 "github.com/charmbracelet/crush/internal/pubsub"
12 "github.com/google/uuid"
15type CreateMessageParams struct {
22type Service interface {
23 pubsub.Suscriber[Message]
24 Create(ctx context.Context, sessionID string, params CreateMessageParams) (Message, error)
25 Update(ctx context.Context, message Message) error
26 Get(ctx context.Context, id string) (Message, error)
27 List(ctx context.Context, sessionID string) ([]Message, error)
28 Delete(ctx context.Context, id string) error
29 DeleteSessionMessages(ctx context.Context, sessionID string) error
33 *pubsub.Broker[Message]
37func NewService(q db.Querier) Service {
39 Broker: pubsub.NewBroker[Message](),
44func (s *service) Delete(ctx context.Context, id string) error {
45 message, err := s.Get(ctx, id)
49 err = s.q.DeleteMessage(ctx, message.ID)
53 s.Publish(pubsub.DeletedEvent, message)
57func (s *service) Create(ctx context.Context, sessionID string, params CreateMessageParams) (Message, error) {
58 if params.Role != Assistant {
59 params.Parts = append(params.Parts, Finish{
63 partsJSON, err := marshallParts(params.Parts)
67 dbMessage, err := s.q.CreateMessage(ctx, db.CreateMessageParams{
68 ID: uuid.New().String(),
70 Role: string(params.Role),
71 Parts: string(partsJSON),
72 Model: sql.NullString{String: string(params.Model), Valid: true},
73 Provider: sql.NullString{String: params.Provider, Valid: params.Provider != ""},
78 message, err := s.fromDBItem(dbMessage)
82 s.Publish(pubsub.CreatedEvent, message)
86func (s *service) DeleteSessionMessages(ctx context.Context, sessionID string) error {
87 messages, err := s.List(ctx, sessionID)
91 for _, message := range messages {
92 if message.SessionID == sessionID {
93 err = s.Delete(ctx, message.ID)
102func (s *service) Update(ctx context.Context, message Message) error {
103 parts, err := marshallParts(message.Parts)
107 finishedAt := sql.NullInt64{}
108 if f := message.FinishPart(); f != nil {
109 finishedAt.Int64 = f.Time
110 finishedAt.Valid = true
112 err = s.q.UpdateMessage(ctx, db.UpdateMessageParams{
114 Parts: string(parts),
115 FinishedAt: finishedAt,
120 message.UpdatedAt = time.Now().Unix()
121 s.Publish(pubsub.UpdatedEvent, message)
125func (s *service) Get(ctx context.Context, id string) (Message, error) {
126 dbMessage, err := s.q.GetMessage(ctx, id)
128 return Message{}, err
130 return s.fromDBItem(dbMessage)
133func (s *service) List(ctx context.Context, sessionID string) ([]Message, error) {
134 dbMessages, err := s.q.ListMessagesBySession(ctx, sessionID)
138 messages := make([]Message, len(dbMessages))
139 for i, dbMessage := range dbMessages {
140 messages[i], err = s.fromDBItem(dbMessage)
148func (s *service) fromDBItem(item db.Message) (Message, error) {
149 parts, err := unmarshallParts([]byte(item.Parts))
151 return Message{}, err
155 SessionID: item.SessionID,
156 Role: MessageRole(item.Role),
158 Model: item.Model.String,
159 Provider: item.Provider.String,
160 CreatedAt: item.CreatedAt,
161 UpdatedAt: item.UpdatedAt,
168 reasoningType partType = "reasoning"
169 textType partType = "text"
170 imageURLType partType = "image_url"
171 binaryType partType = "binary"
172 toolCallType partType = "tool_call"
173 toolResultType partType = "tool_result"
174 finishType partType = "finish"
177type partWrapper struct {
178 Type partType `json:"type"`
179 Data ContentPart `json:"data"`
182func marshallParts(parts []ContentPart) ([]byte, error) {
183 wrappedParts := make([]partWrapper, len(parts))
185 for i, part := range parts {
189 case ReasoningContent:
193 case ImageURLContent:
204 return nil, fmt.Errorf("unknown part type: %T", part)
207 wrappedParts[i] = partWrapper{
212 return json.Marshal(wrappedParts)
215func unmarshallParts(data []byte) ([]ContentPart, error) {
216 temp := []json.RawMessage{}
218 if err := json.Unmarshal(data, &temp); err != nil {
222 parts := make([]ContentPart, 0)
224 for _, rawPart := range temp {
226 Type partType `json:"type"`
227 Data json.RawMessage `json:"data"`
230 if err := json.Unmarshal(rawPart, &wrapper); err != nil {
234 switch wrapper.Type {
236 part := ReasoningContent{}
237 if err := json.Unmarshal(wrapper.Data, &part); err != nil {
240 parts = append(parts, part)
242 part := TextContent{}
243 if err := json.Unmarshal(wrapper.Data, &part); err != nil {
246 parts = append(parts, part)
248 part := ImageURLContent{}
249 if err := json.Unmarshal(wrapper.Data, &part); err != nil {
253 part := BinaryContent{}
254 if err := json.Unmarshal(wrapper.Data, &part); err != nil {
257 parts = append(parts, part)
260 if err := json.Unmarshal(wrapper.Data, &part); err != nil {
263 parts = append(parts, part)
266 if err := json.Unmarshal(wrapper.Data, &part); err != nil {
269 parts = append(parts, part)
272 if err := json.Unmarshal(wrapper.Data, &part); err != nil {
275 parts = append(parts, part)
277 return nil, fmt.Errorf("unknown part type: %s", wrapper.Type)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list