Skip to content

Commit 463da63

Browse files
authored
Merge pull request #411 from nextcloud/enh/noid/input-length-limit
Limit the number of text input characters in the UI and the backend
2 parents 9b2d5b5 + 62595e4 commit 463da63

9 files changed

Lines changed: 51 additions & 9 deletions

File tree

lib/AppInfo/Application.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class Application extends App implements IBootstrap {
5555

5656
public const CHAT_USER_INSTRUCTIONS = 'This is a conversation in a specific language between the user and you, Nextcloud Assistant. You are a kind, polite and helpful AI that helps the user to the best of its abilities. If you do not understand something, you will ask for clarification. Detect the language that the user is using. Make sure to use the same language in your response. Do not mention the language explicitly.';
5757
public const CHAT_USER_INSTRUCTIONS_TITLE = 'Above is a chat session in a specific language between the user and you, Nextcloud Assistant. Generate a suitable title summarizing the conversation in the same language. Output only the title in plain text, nothing else.';
58+
public const MAX_TEXT_INPUT_LENGTH = 64_000;
5859

5960
private IAppConfig $appConfig;
6061
private IManager $taskProcessingManager;

lib/Controller/ChattyLLMController.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ public function newMessage(
292292
if ($this->userId === null) {
293293
return new JSONResponse(['error' => $this->l10n->t('User not logged in')], Http::STATUS_UNAUTHORIZED);
294294
}
295+
if (strlen($content) > Application::MAX_TEXT_INPUT_LENGTH) {
296+
return new JSONResponse(['error' => $this->l10n->t('The new message is too long')], Http::STATUS_BAD_REQUEST);
297+
}
295298

296299
try {
297300
$sessionExists = $this->sessionMapper->exists($this->userId, $sessionId);

lib/Service/AssistantService.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,11 @@ public function getAvailableTaskTypes(): array {
244244
'id' => 'core:inputList',
245245
'priority' => 0,
246246
'inputShape' => [
247+
'textList' => new ShapeDescriptor(
248+
'Input text list',
249+
'plop',
250+
EShapeType::ListOfTexts,
251+
),
247252
'fileList' => new ShapeDescriptor(
248253
'Input file list',
249254
'plop',

src/components/AssistantTextProcessingForm.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ import TaskList from './TaskList.vue'
152152
import TaskTypeSelect from './TaskTypeSelect.vue'
153153
import TranslateForm from './Translate/TranslateForm.vue'
154154
155-
import { SHAPE_TYPE_NAMES } from '../constants.js'
155+
import { SHAPE_TYPE_NAMES, MAX_TEXT_INPUT_LENGTH } from '../constants.js'
156156
157157
import axios from '@nextcloud/axios'
158158
import { generateOcsUrl, generateUrl } from '@nextcloud/router'
@@ -330,15 +330,23 @@ export default {
330330
}
331331
const fieldType = taskType.inputShape[k].type
332332
const value = this.myInputs[k]
333-
return ([SHAPE_TYPE_NAMES.Text, SHAPE_TYPE_NAMES.Enum].includes(fieldType) && typeof value === 'string' && !!value?.trim())
333+
return ([SHAPE_TYPE_NAMES.Text, SHAPE_TYPE_NAMES.Enum].includes(fieldType)
334+
&& typeof value === 'string'
335+
&& !!value?.trim()
336+
// check that the input text is not too long for text fields
337+
&& (fieldType === SHAPE_TYPE_NAMES.Enum || value.trim().length <= MAX_TEXT_INPUT_LENGTH))
334338
|| ([
335339
SHAPE_TYPE_NAMES.Number,
336340
SHAPE_TYPE_NAMES.File,
337341
SHAPE_TYPE_NAMES.Image,
338342
SHAPE_TYPE_NAMES.Audio,
339343
SHAPE_TYPE_NAMES.Video,
340344
].includes(fieldType) && typeof value === 'number')
341-
|| (fieldType === SHAPE_TYPE_NAMES.ListOfTexts && typeof value === 'object' && !!value && value.every(v => typeof v === 'string'))
345+
|| (fieldType === SHAPE_TYPE_NAMES.ListOfTexts && typeof value === 'object' && !!value && value.every(v => {
346+
return typeof v === 'string'
347+
&& !!v?.trim()
348+
&& v.trim().length <= MAX_TEXT_INPUT_LENGTH
349+
}))
342350
|| (fieldType === SHAPE_TYPE_NAMES.ListOfNumbers && typeof value === 'object' && !!value && value.every(v => typeof v === 'number'))
343351
|| ([
344352
SHAPE_TYPE_NAMES.ListOfFiles,

src/components/ChattyLLM/ChattyLLMInputForm.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@
128128
<p class="session-area__disclaimer">
129129
{{ t('assistant', 'Output shown here is generated by AI. Make sure to always double-check.') }}
130130
</p>
131+
<p v-if="chatContent?.length > 64_000"
132+
class="session-area__disclaimer">
133+
{{ t('assistant', 'Messages should not be longer than {maxLength} characters (currently {length}).', { maxLength: 64_000, length: chatContent.length }) }}
134+
</p>
131135
<InputArea ref="inputComponent"
132136
v-model:chat-content="chatContent"
133137
class="session-area__input-area"

src/components/ChattyLLM/InputArea.vue

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,18 @@
1212
:disabled="disabled"
1313
:placeholder="loading.llmGeneration ? thinkingText : placeholderText"
1414
:aria-label="loading.llmGeneration ? thinkingText : placeholderText"
15-
:maxlength="1600"
15+
:maxlength="64_000"
1616
:multiline="isMobile"
1717
dir="auto"
1818
@update:model-value="$emit('update:chatContent', $event)"
19-
@submit="$emit('submit', $event)" />
19+
@submit="onSubmitText" />
2020
<div class="input-area__button-box">
2121
<NcButton v-if="!audioChatAvailable || chatContent"
2222
class="input-area__button-box__button"
2323
:aria-label="submitBtnAriaText"
24-
:disabled="disabled || !chatContent.trim()"
24+
:disabled="disabled || !chatContent.trim() || chatContentTooLong"
2525
variant="primary"
26-
@click="$emit('submit', $event)">
26+
@click="onSubmitText">
2727
<template #icon>
2828
<SendIcon :size="20" />
2929
</template>
@@ -50,6 +50,7 @@ import { generateOcsUrl } from '@nextcloud/router'
5050
import axios from '@nextcloud/axios'
5151
import { showError } from '@nextcloud/dialogs'
5252
import { loadState } from '@nextcloud/initial-state'
53+
import { MAX_TEXT_INPUT_LENGTH } from '../../constants.js'
5354
5455
/*
5556
maxlength calculation (just a rough estimate):
@@ -112,6 +113,9 @@ export default {
112113
disabled() {
113114
return this.loading.llmGeneration || this.loading.olderMessages || this.loading.initialMessages || this.loading.titleGeneration || this.loading.newHumanMessage || this.loading.newSession
114115
},
116+
chatContentTooLong() {
117+
return this.chatContent.length > MAX_TEXT_INPUT_LENGTH
118+
},
115119
},
116120
117121
mounted() {
@@ -139,6 +143,11 @@ export default {
139143
console.error(error)
140144
})
141145
},
146+
onSubmitText(e) {
147+
if (!this.chatContentTooLong) {
148+
this.$emit('submit', e)
149+
}
150+
},
142151
},
143152
}
144153
</script>

src/components/fields/ListOfTextsField.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export default {
137137
&--item {
138138
display: flex;
139139
gap: 4px;
140-
align-items: center;
140+
align-items: end;
141141
.text-input {
142142
flex-grow: 1;
143143
}

src/components/fields/TextInput.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
<div class="text-input">
77
<label :for="id">
88
{{ label }}
9+
<br v-if="limitLabel">
10+
{{ limitLabel ?? '' }}
911
</label>
1012
<NcRichContenteditable
1113
:id="id"
1214
ref="input"
1315
:model-value="value ?? ''"
1416
:link-autocomplete="false"
1517
:multiline="isMobile"
18+
:maxlength="maxLength"
1619
class="editable-input"
1720
:class="{ shadowed: isOutput }"
1821
:placeholder="placeholder"
@@ -55,7 +58,7 @@ import isMobile from '../../mixins/isMobile.js'
5558
import axios from '@nextcloud/axios'
5659
import { getFilePickerBuilder, showError } from '@nextcloud/dialogs'
5760
import { generateOcsUrl } from '@nextcloud/router'
58-
import { VALID_TEXT_MIME_TYPES } from '../../constants.js'
61+
import { VALID_TEXT_MIME_TYPES, MAX_TEXT_INPUT_LENGTH } from '../../constants.js'
5962
6063
const picker = (callback, target) => getFilePickerBuilder(t('assistant', 'Choose a text file'))
6164
.setMimeTypeFilter(VALID_TEXT_MIME_TYPES)
@@ -123,6 +126,7 @@ export default {
123126
data() {
124127
return {
125128
copied: false,
129+
maxLength: MAX_TEXT_INPUT_LENGTH,
126130
}
127131
},
128132
@@ -136,6 +140,12 @@ export default {
136140
hasValue() {
137141
return this.formattedValue !== ''
138142
},
143+
limitLabel() {
144+
const length = this.value?.length ?? 0
145+
return length > this.maxLength
146+
? t('assistant', 'Warning: The input text exceeds the maximum length of {limit} characters (currently {length}).', { length, limit: this.maxLength })
147+
: undefined
148+
},
139149
},
140150
141151
watch: {

src/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6+
export const MAX_TEXT_INPUT_LENGTH = 64_000
7+
68
export const TASK_STATUS_INT = {
79
cancelled: 5,
810
failed: 4,

0 commit comments

Comments
 (0)