Skip to content
Open
Show file tree
Hide file tree
Changes from all 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>3.5.0-dev.2</version>
<version>3.5.0-dev.3</version>
<licence>agpl</licence>
<author>Julien Veyssier</author>
<namespace>Assistant</namespace>
Expand Down
10 changes: 9 additions & 1 deletion lib/Db/ChattyLLM/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
* @method \string|null getSources()
* @method \void setSources(?string $sources)
* @method \string|null getAttachments()
* @method \void setAttachments(?string $attachments)
* @method \void setAttachments(?string $reasoning)
* @method \string|null getReasoning()
* @method \void setReasoning(?string $reasoning)
*/
class Message extends Entity implements \JsonSerializable {
public const ROLE_HUMAN = 'human';
Expand All @@ -48,6 +50,8 @@ class Message extends Entity implements \JsonSerializable {
protected $sources;
/** @var ?string */
protected $attachments;
/** @var string */
protected $reasoning;

public static $columns = [
'id',
Expand All @@ -58,6 +62,7 @@ class Message extends Entity implements \JsonSerializable {
'ocp_task_id',
'sources',
'attachments',
'reasoning',
];
public static $fields = [
'id',
Expand All @@ -68,6 +73,7 @@ class Message extends Entity implements \JsonSerializable {
'ocpTaskId',
'sources',
'attachments',
'reasoning',
];

public function __construct() {
Expand All @@ -78,6 +84,7 @@ public function __construct() {
$this->addType('ocpTaskId', Types::INTEGER);
$this->addType('sources', Types::STRING);
$this->addType('attachments', Types::STRING);
$this->addType('reasoning', Types::STRING);
}

#[\ReturnTypeWillChange]
Expand All @@ -90,6 +97,7 @@ public function jsonSerialize() {
'timestamp' => $this->timestamp,
'ocp_task_id' => $this->ocpTaskId,
'sources' => $this->sources,
'reasoning' => $this->reasoning,
'attachments' => $this->attachments === null
? []
: (json_decode($this->attachments, true) ?: []),
Expand Down
2 changes: 2 additions & 0 deletions lib/Listener/ChattyLLMTaskListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ public function handle(Event $event): void {
} else {
$content = trim($taskOutput['output'] ?? '');
$message->setContent($content);
$reasoning = trim($taskOutput['reasoning'] ?? '');
$message->setReasoning($reasoning);
// the task is not an audio one, but we might still need to Tts the answer
// if it is a response to a ContextAgentInteraction confirmation that was asked about an audio message
$this->runTtsIfNeeded($sessionId, $message, $taskTypeId, $task->getUserId());
Expand Down
43 changes: 43 additions & 0 deletions lib/Migration/Version030500Date20260630083738.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 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 Version030500Date20260630083738 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('reasoning')) {
$table->addColumn('reasoning', Types::TEXT, [
'notnull' => false,
]);
$schemaChanged = true;
}
}

return $schemaChanged ? $schema : null;
}
}
4 changes: 3 additions & 1 deletion src/components/ChattyLLM/ChattyLLMInputForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1068,16 +1068,18 @@ export default {
})
},

updateStreamingMessage({ output, sources }, sessionId) {
updateStreamingMessage({ output, sources, reasoning }, sessionId) {
if (this.streamingMessage) {
this.streamingMessage.content = output
this.streamingMessage.sources = sources
this.streamingMessage.reasoning = reasoning
} else {
this.streamingMessage = {
role: Roles.ASSISTANT,
content: output,
attachments: [],
sources,
reasoning,
session_id: sessionId,
id: 0,
timestamp: moment().unix(),
Expand Down
56 changes: 45 additions & 11 deletions src/components/ChattyLLM/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<div v-if="message.content || streamedMessageContent || parsedSources.length || hasAttachments"
<div v-if="message.content || message.reasoning || streamedMessageContent || parsedSources.length || hasAttachments"
class="message"
@mouseover="showMessageActions = true"
@mouseleave="showMessageActions = false">
Expand Down Expand Up @@ -31,19 +31,39 @@
<div class="message__header__role__name">
{{ message.role === 'human' ? displayName : t('assistant', 'Nextcloud Assistant') }}
</div>
<div style="display: flex">
<div style="display: flex; gap: 5px">
<NcPopover v-if="message.reasoning">
<template #trigger>
<NcButton
:aria-label="t('assistant', 'Reasoning content')"
:title="t('assistant', 'Reasoning content')">
<template #icon>
<ReasoningContentIcon :size="20" />
</template>
</NcButton>
</template>
<template #default>
<div class="reasoningcontent_popover_inner">
<h6>{{ t('assistant', 'Reasoning content') }}</h6>
<NcRichText :text="message.reasoning"
:use-markdown="true"
:autolink="true" />
</div>
</template>
</NcPopover>
<NcPopover v-if="parsedSources.length && !streaming">
<template #trigger>
<NcButton
:aria-label="t('assistant', 'Information sources')">
:aria-label="t('assistant', 'Information sources & actions')"
:title="t('assistant', 'Information sources & actions')">
<template #icon>
<InformationBox :size="20" />
<ToolInformationIcon :size="20" />
</template>
</NcButton>
</template>
<template #default>
<div class="toolinfo_popover_inner">
<h6> Information sources </h6>
<h6>{{ t('assistant', 'Information sources & actions') }}</h6>
<ul>
<li v-for="source in parsedSources" :key="source">
{{ source }}
Expand All @@ -56,6 +76,12 @@
</div>
<NcDateTime class="message__header__timestamp" :timestamp="new Date((message?.timestamp ?? 0) * 1000)" :ignore-seconds="true" />
</div>
<div v-if="streaming && !streamedMessageContent" class="message__streamed-reasoning">
<NcChip :text="t('assistant', 'Reasoning…')"
no-close
:variant="!parsedSources.length ? 'primary' : 'secondary'"
style="display: block; margin-bottom: 0.5em;" />
</div>
<div v-if="streaming" class="message__streamed-sources">
<NcChip v-for="(source, index) in parsedSources"
:key="source"
Expand Down Expand Up @@ -91,10 +117,9 @@ import NcButton from '@nextcloud/vue/components/NcButton'
import NcChip from '@nextcloud/vue/components/NcChip'
import { NcRichText } from '@nextcloud/vue/components/NcRichText'

import InformationBox from 'vue-material-design-icons/InformationBox.vue'

import MessageActions from './MessageActions.vue'
import AudioDisplay from '../fields/AudioDisplay.vue'
import { ReasoningContentIcon, ToolInformationIcon } from '../icons/aliases.js'

import { getCurrentUser } from '@nextcloud/auth'
import { showSuccess } from '@nextcloud/dialogs'
Expand All @@ -118,14 +143,15 @@ export default {
NcRichText,
NcPopover,
NcButton,
InformationBox,
ReasoningContentIcon,
ToolInformationIcon,

MessageActions,
NcChip,
},

props: {
// { id: number, session_id: number, role: string, content: string, timestamp: number, sources: string }
// { id: number, session_id: number, role: string, content: string, timestamp: number, sources: string, reasoning: string }
message: {
type: Object,
required: true,
Expand Down Expand Up @@ -288,7 +314,8 @@ export default {
}
}

&__streamed-sources {
&__streamed-sources,
&__streamed-reasoning {
margin-left: 2.6em;
}

Expand All @@ -308,7 +335,8 @@ export default {
</style>

<style lang="scss">
.toolinfo_popover_inner {
.toolinfo_popover_inner,
.reasoningcontent_popover_inner {
margin: 12px;
h6 {
margin: 2px;
Expand All @@ -318,4 +346,10 @@ export default {
padding-left: 18px;
}
}

.reasoningcontent_popover_inner {
max-width: 500px;
max-height: 400px;
overflow-y: auto;
}
</style>
11 changes: 11 additions & 0 deletions src/components/icons/aliases.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import ToolInformationIcon from 'vue-material-design-icons/InformationBox.vue'
import ReasoningContentIcon from 'vue-material-design-icons/ThoughtBubbleOutline.vue'

export {
ToolInformationIcon,
ReasoningContentIcon,
}
Loading