Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
]]> </description>
<version>2.4.0</version>
<version>2.5.0</version>
<licence>agpl</licence>
<author>Julien Veyssier</author>
<namespace>Assistant</namespace>
Expand Down
8 changes: 8 additions & 0 deletions lib/Db/ChattyLLM/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -35,6 +37,8 @@ class Message extends Entity implements \JsonSerializable {
protected $timestamp;
/** @var int */
protected $ocpTaskId;
/** @var string */
protected $sources;

public static $columns = [
'id',
Expand All @@ -43,6 +47,7 @@ class Message extends Entity implements \JsonSerializable {
'content',
'timestamp',
'ocp_task_id',
'sources',
];
public static $fields = [
'id',
Expand All @@ -51,6 +56,7 @@ class Message extends Entity implements \JsonSerializable {
'content',
'timestamp',
'ocpTaskId',
'sources',
];

public function __construct() {
Expand All @@ -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]
Expand All @@ -70,6 +77,7 @@ public function jsonSerialize() {
'content' => $this->content,
'timestamp' => $this->timestamp,
'ocp_task_id' => $this->ocpTaskId,
'sources' => $this->sources,
];
}
}
3 changes: 3 additions & 0 deletions lib/Listener/BeforeTemplateRenderedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,6 +35,7 @@ public function __construct(
private IAppConfig $appConfig,
private IInitialState $initialStateService,
private IEventDispatcher $eventDispatcher,
private AssistantService $assistantService,
private ?string $userId,
) {
}
Expand Down Expand Up @@ -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());
Expand Down
1 change: 1 addition & 0 deletions lib/Listener/ChattyLLMTaskListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public function handle(Event $event): void {
$message->setRole('assistant');
$message->setContent(trim($task->getOutput()['output'] ?? ''));
$message->setTimestamp(time());
$message->setSources(json_encode($task->getOutput()['sources'] ?? []));
try {
$this->messageMapper->insert($message);
} catch (\OCP\DB\Exception $e) {
Expand Down
43 changes: 43 additions & 0 deletions lib/Migration/Version020500Date20250425125359.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Assistant\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

class Version020500Date20250425125359 extends SimpleMigrationStep {

/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$schemaChanged = false;

if ($schema->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;
}
}
1 change: 1 addition & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
* content: string,
* timestamp: int,
* ocp_task_id: int,
* sources: string,
* }
*
* @psalm-type AssistantChatAgencyMessage = AssistantChatMessage&array{
Expand Down
32 changes: 32 additions & 0 deletions lib/Service/AssistantService.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class AssistantService {
TextToTextTopics::ID => 10,
];

public array $informationSources;

public function __construct(
private ITaskProcessingManager $taskProcessingManager,
private TaskNotificationMapper $taskNotificationMapper,
Expand All @@ -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'),
];
}

/**
Expand Down
6 changes: 5 additions & 1 deletion openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"role",
"content",
"timestamp",
"ocp_task_id"
"ocp_task_id",
"sources"
],
"properties": {
"id": {
Expand All @@ -73,6 +74,9 @@
"ocp_task_id": {
"type": "integer",
"format": "int64"
},
"sources": {
"type": "string"
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/ChattyLLM/ChattyLLMInputForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@
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,
Expand Down Expand Up @@ -559,7 +559,7 @@
this.loading.initialMessages = false
this.messagesAxiosController = null
} catch (error) {
if (axios.isCancel(error)) {

Check warning on line 562 in src/components/ChattyLLM/ChattyLLMInputForm.vue

View workflow job for this annotation

GitHub Actions / NPM build

Caution: `axios` also has a named export `isCancel`. Check if you meant to write `import {isCancel} from '@nextcloud/axios'` instead

Check warning on line 562 in src/components/ChattyLLM/ChattyLLMInputForm.vue

View workflow job for this annotation

GitHub Actions / NPM build

Caution: `axios` also has a named export `isCancel`. Check if you meant to write `import {isCancel} from '@nextcloud/axios'` instead

Check warning on line 562 in src/components/ChattyLLM/ChattyLLMInputForm.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Caution: `axios` also has a named export `isCancel`. Check if you meant to write `import {isCancel} from '@nextcloud/axios'` instead
console.debug('fetchMessages cancelled')
return
}
Expand Down
6 changes: 5 additions & 1 deletion src/components/ChattyLLM/ConversationBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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)" />
<LoadingPlaceholder v-if="loading.llmGeneration" />
Expand All @@ -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',

Expand All @@ -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,
Expand All @@ -82,6 +85,7 @@ export default {
return {
regenerateFromId: null,
deleteMessageId: null,
informationSourceNames: loadState('assistant', 'contextAgentToolSources'),
}
},

Expand Down
60 changes: 59 additions & 1 deletion src/components/ChattyLLM/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,28 @@
<div class="message__header__role__name">
{{ message.role === 'human' ? displayName : t('assistant', 'Nextcloud Assistant') }}
</div>
<div style="display: flex">
<NcPopover v-if="parsedSources.length">
<template #trigger>
<NcButton
:aria-label="t('assistant', 'Information sources')">
<template #icon>
<InformationBox :size="20" />
</template>
</NcButton>
</template>
<template #default>
<div class="toolinfo_popover_inner">
<h6> Information sources </h6>
<ul>
<li v-for="source in parsedSources" :key="source">
{{ source }}
</li>
</ul>
</div>
</template>
</NcPopover>
</div>
</div>
<NcDateTime class="message__header__timestamp" :timestamp="new Date((message?.timestamp ?? 0) * 1000)" :ignore-seconds="true" />
</div>
Expand All @@ -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'
Expand All @@ -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,
Expand All @@ -96,6 +125,10 @@ export default {
type: Boolean,
default: false,
},
informationSourceNames: {
type: Array,
default: null,
},
},

data: () => {
Expand All @@ -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()
},
Expand Down Expand Up @@ -138,6 +179,9 @@ export default {
})
}
},
getSourceString(source) {
return this.informationSourceNames[source] ? this.informationSourceNames[source] : source
},
},
}
</script>
Expand All @@ -155,6 +199,7 @@ export default {
&__header {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;

Expand Down Expand Up @@ -188,3 +233,16 @@ export default {
}
}
</style>

<style lang="scss">
.toolinfo_popover_inner {
margin: 12px;
h6 {
margin: 2px;
}
ul {
list-style-type: disc;
padding-left: 18px;
}
}
</style>
Loading