diff --git a/appinfo/info.xml b/appinfo/info.xml index 24a2ec6a..0d0227a3 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -62,7 +62,7 @@ Known providers: More details on how to set this up in the [admin docs](https://docs.nextcloud.com/server/latest/admin_manual/ai/index.html) ]]> - 2.4.0 + 2.5.0 agpl Julien Veyssier Assistant diff --git a/lib/Db/ChattyLLM/Message.php b/lib/Db/ChattyLLM/Message.php index faad0a0f..d2b9dba4 100644 --- a/lib/Db/ChattyLLM/Message.php +++ b/lib/Db/ChattyLLM/Message.php @@ -23,6 +23,8 @@ * @method \void setTimestamp(int $timestamp) * @method \int getOcpTaskId() * @method \void setOcpTaskId(int $ocpTaskId) + * @method \string getSources() + * @method \void setSources(string $sources) */ class Message extends Entity implements \JsonSerializable { /** @var int */ @@ -35,6 +37,8 @@ class Message extends Entity implements \JsonSerializable { protected $timestamp; /** @var int */ protected $ocpTaskId; + /** @var string */ + protected $sources; public static $columns = [ 'id', @@ -43,6 +47,7 @@ class Message extends Entity implements \JsonSerializable { 'content', 'timestamp', 'ocp_task_id', + 'sources', ]; public static $fields = [ 'id', @@ -51,6 +56,7 @@ class Message extends Entity implements \JsonSerializable { 'content', 'timestamp', 'ocpTaskId', + 'sources', ]; public function __construct() { @@ -59,6 +65,7 @@ public function __construct() { $this->addType('content', Types::STRING); $this->addType('timestamp', Types::INTEGER); $this->addType('ocp_task_id', Types::INTEGER); + $this->addType('sources', Types::STRING); } #[\ReturnTypeWillChange] @@ -70,6 +77,7 @@ public function jsonSerialize() { 'content' => $this->content, 'timestamp' => $this->timestamp, 'ocp_task_id' => $this->ocpTaskId, + 'sources' => $this->sources, ]; } } diff --git a/lib/Listener/BeforeTemplateRenderedListener.php b/lib/Listener/BeforeTemplateRenderedListener.php index 56c2db91..245c5623 100644 --- a/lib/Listener/BeforeTemplateRenderedListener.php +++ b/lib/Listener/BeforeTemplateRenderedListener.php @@ -10,6 +10,7 @@ namespace OCA\Assistant\Listener; use OCA\Assistant\AppInfo\Application; +use OCA\Assistant\Service\AssistantService; use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; @@ -34,6 +35,7 @@ public function __construct( private IAppConfig $appConfig, private IInitialState $initialStateService, private IEventDispatcher $eventDispatcher, + private AssistantService $assistantService, private ?string $userId, ) { } @@ -63,6 +65,7 @@ public function handle(Event $event): void { $this->initialStateService->provideInitialState('last-target-language', $lastTargetLanguage); $indexingComplete = $this->appConfig->getValueInt('context_chat', 'last_indexed_time', 0) !== 0; $this->initialStateService->provideInitialState('contextChatIndexingComplete', $indexingComplete); + $this->initialStateService->provideInitialState('contextAgentToolSources', $this->assistantService->informationSources); } if (class_exists(\OCA\Viewer\Event\LoadViewer::class)) { $this->eventDispatcher->dispatchTyped(new \OCA\Viewer\Event\LoadViewer()); diff --git a/lib/Listener/ChattyLLMTaskListener.php b/lib/Listener/ChattyLLMTaskListener.php index 4cb46b62..0f60a5c3 100644 --- a/lib/Listener/ChattyLLMTaskListener.php +++ b/lib/Listener/ChattyLLMTaskListener.php @@ -61,6 +61,8 @@ public function handle(Event $event): void { $message->setRole('assistant'); $message->setContent(trim($task->getOutput()['output'] ?? '')); $message->setTimestamp(time()); + $sources = json_encode($task->getOutput()['sources'] ?? []); + $message->setSources($sources ? $sources : '[]'); try { $this->messageMapper->insert($message); } catch (\OCP\DB\Exception $e) { diff --git a/lib/Migration/Version020500Date20250425125359.php b/lib/Migration/Version020500Date20250425125359.php new file mode 100644 index 00000000..bf69c802 --- /dev/null +++ b/lib/Migration/Version020500Date20250425125359.php @@ -0,0 +1,43 @@ +hasTable('assistant_chat_msgs')) { + $table = $schema->getTable('assistant_chat_msgs'); + if (!$table->hasColumn('sources')) { + $table->addColumn('sources', Types::TEXT, [ + 'notnull' => true, + 'default' => '[]', + ]); + $schemaChanged = true; + } + } + + return $schemaChanged ? $schema : null; + } +} diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index c9470dfd..4fbceec3 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -64,6 +64,7 @@ * content: string, * timestamp: int, * ocp_task_id: int, + * sources: string, * } * * @psalm-type AssistantChatAgencyMessage = AssistantChatMessage&array{ diff --git a/lib/Service/AssistantService.php b/lib/Service/AssistantService.php index aad0b0f3..233fc701 100644 --- a/lib/Service/AssistantService.php +++ b/lib/Service/AssistantService.php @@ -74,6 +74,8 @@ class AssistantService { TextToTextTopics::ID => 10, ]; + public array $informationSources; + public function __construct( private ITaskProcessingManager $taskProcessingManager, private TaskNotificationMapper $taskNotificationMapper, @@ -86,6 +88,36 @@ public function __construct( private IConfig $config, private IShareManager $shareManager, ) { + $this->informationSources = [ + 'ask_context_chat' => $this->l10n->t('Context Chat'), + 'transcribe_file' => $this->l10n->t('Assistant File Transcription'), + 'generate_document' => $this->l10n->t('Assistant Document Generation'), + 'list_calendars' => $this->l10n->t('Nextcloud Calendar'), + 'schedule_event' => $this->l10n->t('Nextcloud Calendar'), + 'find_free_time_slot_in_calendar' => $this->l10n->t('Nextcloud Calendar'), + 'add_task' => $this->l10n->t('Nextcloud Tasks'), + 'find_details_of_current_user' => $this->l10n->t('Nextcloud User Profile'), + 'list_decks' => $this->l10n->t('Nextcloud Deck'), + 'add_card' => $this->l10n->t('Nextcloud Deck'), + 'get_coordinates_for_address' => $this->l10n->t('OpenStreetMap'), + 'get_current_weather_for_coordinates' => $this->l10n->t('Norwegian Meteorological Institute Weather Forecast'), + 'get_public_transport_route_for_coordinates,' => $this->l10n->t('HERE Public Transport API'), + 'get_osm_route,' => $this->l10n->t('OpenStreetMap'), + 'get_osm_link,' => $this->l10n->t('OpenStreetMap'), + 'get_file_content' => $this->l10n->t('Nextcloud Files'), + 'get_folder_tree' => $this->l10n->t('Nextcloud Files'), + 'create_public_sharing_link' => $this->l10n->t('Nextcloud Files'), + 'send_email' => $this->l10n->t('Nextcloud Mail'), + 'get_mail_account_list' => $this->l10n->t('Nextcloud Mail'), + 'list_projects,' => $this->l10n->t('OpenProject'), + 'list_assignees,' => $this->l10n->t('OpenProject'), + 'create_work_package' => $this->l10n->t('OpenProject'), + 'list_talk_conversations' => $this->l10n->t('Nextcloud Talk'), + 'create_public_conversation' => $this->l10n->t('Nextcloud Talk'), + 'send_message_to_conversation' => $this->l10n->t('Nextcloud Talk'), + 'list_messages_in_conversation' => $this->l10n->t('Nextcloud Talk'), + 'duckduckgo_results_json' => $this->l10n->t('DuckDuckGo Web Search'), + ]; } /** diff --git a/openapi.json b/openapi.json index 6b0d2626..3efbba35 100644 --- a/openapi.json +++ b/openapi.json @@ -49,7 +49,8 @@ "role", "content", "timestamp", - "ocp_task_id" + "ocp_task_id", + "sources" ], "properties": { "id": { @@ -73,6 +74,9 @@ "ocp_task_id": { "type": "integer", "format": "int64" + }, + "sources": { + "type": "string" } } }, diff --git a/src/components/ChattyLLM/ChattyLLMInputForm.vue b/src/components/ChattyLLM/ChattyLLMInputForm.vue index e19c1f97..a392cfea 100644 --- a/src/components/ChattyLLM/ChattyLLMInputForm.vue +++ b/src/components/ChattyLLM/ChattyLLMInputForm.vue @@ -227,7 +227,7 @@ export default { sessionIdToDelete: null, chatContent: '', sessions: null, - // [{ id: number, session_id: number, role: string, content: string, timestamp: number }] + // [{ id: number, session_id: number, role: string, content: string, timestamp: number, sources:string }] messages: [], // null when failed to fetch messagesAxiosController: null, // for request cancellation allMessagesLoaded: false, diff --git a/src/components/ChattyLLM/ConversationBox.vue b/src/components/ChattyLLM/ConversationBox.vue index d8436953..8d17b47c 100644 --- a/src/components/ChattyLLM/ConversationBox.vue +++ b/src/components/ChattyLLM/ConversationBox.vue @@ -28,6 +28,7 @@ :delete-loading="loading.messageDelete && message.id === deleteMessageId" :regenerate-loading="loading.llmGeneration && message.id === regenerateFromId" :new-message-loading="loading.newHumanMessage && idx === (messages.length - 1)" + :information-source-names="informationSourceNames" @regenerate="regenerate(message.id)" @delete="deleteMessage(message.id)" /> @@ -44,6 +45,8 @@ import LoadingPlaceholder from './LoadingPlaceholder.vue' import Message from './Message.vue' import NoSession from './NoSession.vue' +import { loadState } from '@nextcloud/initial-state' + export default { name: 'ConversationBox', @@ -58,7 +61,7 @@ export default { }, props: { - // [{ id: number, session_id: number, role: string, content: string, timestamp: number }] + // [{ id: number, session_id: number, role: string, content: string, timestamp: number, sources: string }] messages: { type: Array, default: null, @@ -82,6 +85,7 @@ export default { return { regenerateFromId: null, deleteMessageId: null, + informationSourceNames: loadState('assistant', 'contextAgentToolSources'), } }, diff --git a/src/components/ChattyLLM/Message.vue b/src/components/ChattyLLM/Message.vue index a268c29d..dd706488 100644 --- a/src/components/ChattyLLM/Message.vue +++ b/src/components/ChattyLLM/Message.vue @@ -30,6 +30,28 @@
{{ message.role === 'human' ? displayName : t('assistant', 'Nextcloud Assistant') }}
+
+ + + + +
@@ -48,8 +70,12 @@ import AssistantIcon from '../icons/AssistantIcon.vue' import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' import NcDateTime from '@nextcloud/vue/dist/Components/NcDateTime.js' import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' +import NcPopover from '@nextcloud/vue/dist/Components/NcPopover.js' +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import { NcRichText } from '@nextcloud/vue/dist/Components/NcRichText.js' +import InformationBox from 'vue-material-design-icons/InformationBox.vue' + import MessageActions from './MessageActions.vue' import { getCurrentUser } from '@nextcloud/auth' @@ -70,12 +96,15 @@ export default { NcDateTime, NcLoadingIcon, NcRichText, + NcPopover, + NcButton, + InformationBox, MessageActions, }, props: { - // { id: number, session_id: number, role: string, content: string, timestamp: number } + // { id: number, session_id: number, role: string, content: string, timestamp: number, sources: string } message: { type: Object, required: true, @@ -96,6 +125,10 @@ export default { type: Boolean, default: false, }, + informationSourceNames: { + type: Array, + default: null, + }, }, data: () => { @@ -111,6 +144,14 @@ export default { } }, + computed: { + parsedSources() { + let parsedSources = JSON.parse(this.message.sources) + parsedSources = parsedSources.map((source) => this.getSourceString(source)) + return [...new Set(parsedSources)] + }, + }, + mounted() { this.fetch() }, @@ -138,6 +179,9 @@ export default { }) } }, + getSourceString(source) { + return this.informationSourceNames[source] ? this.informationSourceNames[source] : source + }, }, } @@ -155,6 +199,7 @@ export default { &__header { display: flex; flex-direction: row; + flex-wrap: wrap; align-items: center; justify-content: space-between; @@ -188,3 +233,16 @@ export default { } } + +