diff --git a/backend/src/main/java/com/talkforgeai/backend/assistant/controller/AssistantSpringController.java b/backend/src/main/java/com/talkforgeai/backend/assistant/controller/AssistantSpringController.java index 84350f3d..9be24b55 100644 --- a/backend/src/main/java/com/talkforgeai/backend/assistant/controller/AssistantSpringController.java +++ b/backend/src/main/java/com/talkforgeai/backend/assistant/controller/AssistantSpringController.java @@ -125,19 +125,20 @@ public GenerateImageResponse generateImage(@RequestBody GenerateImageRequest gen return assistantService.generateImage(generateImageRequest.prompt()); } - @GetMapping("/threads") - public List listThreads() { - return assistantService.retrieveThreads(); + @GetMapping("/threads/{assistantId}") + public List listThreads(@PathVariable("assistantId") String assistantId) { + return assistantService.retrieveThreads(assistantId); } - @PostMapping("/threads") - public ThreadDto createThread() { - return assistantService.createThread(); + @PostMapping("/threads/{assistantId}") + public ThreadDto createThread(@PathVariable("assistantId") String assistantId) { + return assistantService.createThread(assistantId); } - @GetMapping("/threads/{threadId}") - public ThreadDto retrieveThread(@PathVariable("threadId") String threadId) { - return assistantService.retrieveThread(threadId); + @GetMapping("/threads/{assistantId}/{threadId}") + public ThreadDto retrieveThread(@PathVariable("assistantId") String assistantId, + @PathVariable("threadId") String threadId) { + return assistantService.retrieveThread(assistantId, threadId); } @GetMapping("/threads/{threadId}/messages") @@ -162,7 +163,7 @@ public ParsedMessageDto postProcessMessage(@PathVariable("threadId") String thre } - @GetMapping("/threads/{threadId}/{filename}") + @GetMapping("/threads/images/{threadId}/{filename}") public ResponseEntity getImage(@PathVariable String threadId, @PathVariable String filename) { diff --git a/backend/src/main/java/com/talkforgeai/backend/assistant/domain/AssistantEntity.java b/backend/src/main/java/com/talkforgeai/backend/assistant/domain/AssistantEntity.java index 3a185f05..b5248691 100644 --- a/backend/src/main/java/com/talkforgeai/backend/assistant/domain/AssistantEntity.java +++ b/backend/src/main/java/com/talkforgeai/backend/assistant/domain/AssistantEntity.java @@ -70,6 +70,9 @@ public class AssistantEntity { @OneToMany(mappedBy = "assistant", fetch = FetchType.LAZY) private List memoryDocuments = new ArrayList<>(); + @OneToMany(mappedBy = "assistant", fetch = FetchType.LAZY) + private List threads = new ArrayList<>(); + @Column(name = "memory", length = 20, nullable = false) private String memory; @@ -165,5 +168,12 @@ public void setMemoryDocuments(List memoryDocuments) { this.memoryDocuments = memoryDocuments; } + public List getThreads() { + return threads; + } + + public void setThreads(List threads) { + this.threads = threads; + } } diff --git a/backend/src/main/java/com/talkforgeai/backend/assistant/domain/ThreadEntity.java b/backend/src/main/java/com/talkforgeai/backend/assistant/domain/ThreadEntity.java index f5d9b6bd..ae4b35fd 100644 --- a/backend/src/main/java/com/talkforgeai/backend/assistant/domain/ThreadEntity.java +++ b/backend/src/main/java/com/talkforgeai/backend/assistant/domain/ThreadEntity.java @@ -19,6 +19,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import java.util.Date; @@ -36,6 +37,9 @@ public class ThreadEntity { @Column(name = "created_at", nullable = false) private Date createdAt; + @ManyToOne + private AssistantEntity assistant; + // Standard getters and setters public String getId() { return id; @@ -60,4 +64,12 @@ public Date getCreatedAt() { public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; } + + public AssistantEntity getAssistant() { + return assistant; + } + + public void setAssistant(AssistantEntity assistant) { + this.assistant = assistant; + } } \ No newline at end of file diff --git a/backend/src/main/java/com/talkforgeai/backend/assistant/dto/ThreadDto.java b/backend/src/main/java/com/talkforgeai/backend/assistant/dto/ThreadDto.java index 29e94edd..03d1783e 100644 --- a/backend/src/main/java/com/talkforgeai/backend/assistant/dto/ThreadDto.java +++ b/backend/src/main/java/com/talkforgeai/backend/assistant/dto/ThreadDto.java @@ -20,6 +20,7 @@ public record ThreadDto(String id, String title, - Date createdAt) { + Date createdAt, + AssistantDto assistant) { } diff --git a/backend/src/main/java/com/talkforgeai/backend/assistant/repository/ThreadRepository.java b/backend/src/main/java/com/talkforgeai/backend/assistant/repository/ThreadRepository.java index 41c4ac04..a7e5ca37 100644 --- a/backend/src/main/java/com/talkforgeai/backend/assistant/repository/ThreadRepository.java +++ b/backend/src/main/java/com/talkforgeai/backend/assistant/repository/ThreadRepository.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Jean Schmitz. + * Copyright (c) 2023-2024 Jean Schmitz. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,17 @@ package com.talkforgeai.backend.assistant.repository; import com.talkforgeai.backend.assistant.domain.ThreadEntity; +import java.util.List; +import java.util.Optional; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface ThreadRepository extends JpaRepository { + + List findAllByAssistantId(String assistantId, Sort sortOrder); + + Optional findByIdAndAssistantId(String id, String assistantId); + } diff --git a/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantMapper.java b/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantMapper.java index abdfd4bf..a4a4b747 100644 --- a/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantMapper.java +++ b/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantMapper.java @@ -102,7 +102,7 @@ public Map mapProperties(Map pro public ThreadDto toDto(ThreadEntity threadEntity) { return new ThreadDto(threadEntity.getId(), threadEntity.getTitle(), - threadEntity.getCreatedAt()); + threadEntity.getCreatedAt(), toDto(threadEntity.getAssistant())); } public ThreadEntity toEntity(ThreadDto threadDto) { diff --git a/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantSpringService.java b/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantSpringService.java index aa06e08a..39d2d0cb 100644 --- a/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantSpringService.java +++ b/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantSpringService.java @@ -228,9 +228,11 @@ public boolean doesAssistantExistByName(String assistantName) { } @Transactional - public ThreadDto createThread() { + public ThreadDto createThread(String assistantId) { ThreadEntity threadEntity = new ThreadEntity(); threadEntity.setId(UniqueIdUtil.generateThreadId()); + threadEntity.setAssistant(assistantRepository.findById(assistantId) + .orElseThrow(() -> new AssistentException("Assistant not found"))); threadEntity.setTitle(""); threadEntity.setCreatedAt(new Date()); threadRepository.save(threadEntity); @@ -238,8 +240,10 @@ public ThreadDto createThread() { return assistantMapper.toDto(threadEntity); } - public List retrieveThreads() { - return this.threadRepository.findAll(Sort.by(Sort.Direction.DESC, "createdAt")).stream() + public List retrieveThreads(String assistantId) { + return this.threadRepository.findAllByAssistantId(assistantId, + Sort.by(Sort.Direction.DESC, "createdAt")) + .stream() .map(assistantMapper::toDto) .toList(); } @@ -533,10 +537,12 @@ public ThreadTitleDto generateThreadTitle(String threadId, } } - public ThreadDto retrieveThread(String threadId) { - return threadRepository.findById(threadId) - .map(assistantMapper::toDto) - .orElseThrow(() -> new AssistentException("Thread not found")); + public ThreadDto retrieveThread(String assistantId, String threadId) { + ThreadEntity threadEntity = threadRepository.findByIdAndAssistantId(threadId, assistantId) + .orElseThrow(() -> new AssistentException( + "Thread " + threadId + " owned by assisant " + assistantId + " not found")); + + return assistantMapper.toDto(threadEntity); } @Transactional diff --git a/backend/src/main/java/com/talkforgeai/backend/transformers/ImageDownloadTransformer.java b/backend/src/main/java/com/talkforgeai/backend/transformers/ImageDownloadTransformer.java index 1662f546..ad2f369f 100644 --- a/backend/src/main/java/com/talkforgeai/backend/transformers/ImageDownloadTransformer.java +++ b/backend/src/main/java/com/talkforgeai/backend/transformers/ImageDownloadTransformer.java @@ -118,6 +118,6 @@ private String downloadImage(String imageUrl, String threadId, Path chatDirector throw ex; } - return "/api/v1/threads/" + threadId + "/" + fileName; + return "/api/v1/threads/images/" + threadId + "/" + fileName; } } diff --git a/backend/src/main/resources/db/migration/V1__Initial_Setup.sql b/backend/src/main/resources/db/migration/V1__Initial_Setup.sql index cdd59dd7..2775a8e3 100644 --- a/backend/src/main/resources/db/migration/V1__Initial_Setup.sql +++ b/backend/src/main/resources/db/migration/V1__Initial_Setup.sql @@ -37,11 +37,15 @@ create table assistant_properties create table thread ( - id varchar(50) not null primary key, - title varchar(50), - created_at timestamp + id varchar(50) not null primary key, + title varchar(50), + created_at timestamp, + assistant_id varchar(50) not null, + foreign key (assistant_id) references assistant (id) ); +create index idx_thread_assistant_id on thread (assistant_id); + create table message ( id varchar(50) not null primary key, diff --git a/frontend/src/components/App.vue b/frontend/src/components/App.vue index fbb9103b..def3b398 100644 --- a/frontend/src/components/App.vue +++ b/frontend/src/components/App.vue @@ -19,8 +19,9 @@ - +
diff --git a/frontend/src/components/chat/ChatView.vue b/frontend/src/components/chat/ChatView.vue index 9d3e91a0..13501ea4 100644 --- a/frontend/src/components/chat/ChatView.vue +++ b/frontend/src/components/chat/ChatView.vue @@ -163,6 +163,8 @@ export default defineComponent({ }, }, mounted() { + this.chatStore.clearThreadsList(); + this.chatStore.newThread(); this.fetchData(); }, }); diff --git a/frontend/src/composable/use-assistants.ts b/frontend/src/composable/use-assistants.ts index 968dfa4c..bfda8576 100644 --- a/frontend/src/composable/use-assistants.ts +++ b/frontend/src/composable/use-assistants.ts @@ -80,7 +80,7 @@ export function useAssistants() { chatStore.removeStatus(); isReading = false; } else { - console.log('Chunk value: ', chunkValue); + //console.log('Chunk value: ', chunkValue); partial += chunkValue; const parts = partial.split('\n'); partial = parts.pop() ?? ''; @@ -169,7 +169,7 @@ export function useAssistants() { const processData = ( data: string, event: string, debouncedUpdateCallback: () => void): string => { - console.log('PROCESS event=' + event + ': ', data); + //console.log('PROCESS event=' + event + ': ', data); let result = ''; switch (event) { @@ -191,12 +191,12 @@ export function useAssistants() { }; function processRunStartedEvent(data: string) { - console.log('## processRunStartedEvent', data); + //console.log('## processRunStartedEvent', data); chatStore.runId = data; } const processDeltaEvent = (data: string) => { - console.log('## processDeltaEvent', data); + //console.log('## processDeltaEvent', data); const lastMessage = chatStore.getLastMessage(); if (data.length > 0) { @@ -240,7 +240,7 @@ export function useAssistants() { const retrieveThreads = async () => { const result = await axios.get( - `/api/v1/threads`, + `/api/v1/threads/${chatStore.selectedAssistant.id}`, { params: {}, }, @@ -251,13 +251,13 @@ export function useAssistants() { const retrieveThread = async (threadId: string) => { const result = await axios.get( - `/api/v1/threads/${threadId}`, + `/api/v1/threads/${chatStore.selectedAssistant.id}/${threadId}`, ); return result.data; }; const createThread = async () => { - const result = await axios.post(`/api/v1/threads`); + const result = await axios.post(`/api/v1/threads/${chatStore.selectedAssistant.id}`); return result.data; }; diff --git a/frontend/src/store/chat-store.ts b/frontend/src/store/chat-store.ts index 482049b1..5de1c067 100644 --- a/frontend/src/store/chat-store.ts +++ b/frontend/src/store/chat-store.ts @@ -60,10 +60,16 @@ export const useChatStore = defineStore('chat', { }, }, actions: { + /** + * Clear the threads list. + */ + clearThreadsList() { + this.threads = []; + }, /** * Create a new thread. */ - async newThread() { + newThread() { this.threadId = ''; this.threadMessages = []; this.parsedMessages = {};