Added model adding
This commit is contained in:
parent
daff6a722f
commit
1d3a5e91d9
|
|
@ -135,9 +135,82 @@
|
||||||
"description": "Text displayed for add model button",
|
"description": "Text displayed for add model button",
|
||||||
"context": "Visible in the model dialog"
|
"context": "Visible in the model dialog"
|
||||||
},
|
},
|
||||||
"modelDialogAddSteps": "Adding models is not supported. Go to your host pc and add models there.",
|
"modelDialogAddPromptTitle": "Add new model",
|
||||||
"@modelDialogAddSteps": {
|
"@modelDialogAddPromptTitle": {
|
||||||
"description": "Steps to add a new model",
|
"description": "Title of the add model dialog",
|
||||||
|
"context": "Visible in the model dialog"
|
||||||
|
},
|
||||||
|
"modelDialogAddPromptDescription": "This can have either be a normal name (e.g. 'llama3') or name and tag (e.g. 'llama3:70b').",
|
||||||
|
"@modelDialogAddPromptDescription": {
|
||||||
|
"description": "Description of the add model dialog",
|
||||||
|
"context": "Visible in the model dialog"
|
||||||
|
},
|
||||||
|
"modelDialogAddPromptAlreadyExists": "Model already exists",
|
||||||
|
"@modelDialogAddPromptAlreadyExists": {
|
||||||
|
"description": "Text displayed when the model already exists",
|
||||||
|
"context": "Visible in the model dialog"
|
||||||
|
},
|
||||||
|
"modelDialogAddPromptInvalid": "Invalid model name",
|
||||||
|
"@modelDialogAddPromptInvalid": {
|
||||||
|
"description": "Text displayed when the model name is invalid",
|
||||||
|
"context": "Visible in the model dialog"
|
||||||
|
},
|
||||||
|
"modelDialogAddAssuranceTitle": "Add {model}?",
|
||||||
|
"@modelDialogAddAssuranceTitle": {
|
||||||
|
"description": "Title of the add model assurance dialog",
|
||||||
|
"context": "Visible in the model dialog",
|
||||||
|
"placeholders": {
|
||||||
|
"model": {
|
||||||
|
"type": "String",
|
||||||
|
"description": "Name of the model to be added"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modelDialogAddAssuranceDescription": "Pressing 'Add' will download the model '{model}' directly from the Ollama server to your host.\nThis can take a while depending on your internet connection. The action cannot be canceled.\nIf the app is closed during the download, it'll resume if you enter the name into the model dialog again.",
|
||||||
|
"@modelDialogAddAssuranceDescription": {
|
||||||
|
"description": "Description of the add model assurance dialog",
|
||||||
|
"context": "Visible in the model dialog",
|
||||||
|
"placeholders": {
|
||||||
|
"model": {
|
||||||
|
"type": "String",
|
||||||
|
"description": "Name of the model to be added"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modelDialogAddAssuranceAdd": "Add",
|
||||||
|
"@modelDialogAddAssuranceAdd": {
|
||||||
|
"description": "Text displayed for add button, should be capitalized",
|
||||||
|
"context": "Visible in the model dialog"
|
||||||
|
},
|
||||||
|
"modelDialogAddAssuranceCancel": "Cancel",
|
||||||
|
"@modelDialogAddAssuranceCancel": {
|
||||||
|
"description": "Text displayed for cancel button, should be capitalized",
|
||||||
|
"context": "Visible in the model dialog"
|
||||||
|
},
|
||||||
|
"modelDialogAddDownloadPercentLoading": "loading progress",
|
||||||
|
"@modelDialogAddDownloadPercentLoading": {
|
||||||
|
"description": "Text displayed while loading the download progress",
|
||||||
|
"context": "Visible in the model dialog; 'loading progress in the moment'"
|
||||||
|
},
|
||||||
|
"modelDialogAddDownloadPercent": "download at {percent}%",
|
||||||
|
"@modelDialogAddDownloadPercent": {
|
||||||
|
"description": "Text displayed while downloading a model",
|
||||||
|
"context": "Visible in the model dialog; download is at x percent",
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "String",
|
||||||
|
"description": "Percentage of the download"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modelDialogAddDownloadFailed": "Disconnected, try again",
|
||||||
|
"@modelDialogAddDownloadFailed": {
|
||||||
|
"description": "Text displayed when the download of a model fails",
|
||||||
|
"context": "Visible in the model dialog"
|
||||||
|
},
|
||||||
|
"modelDialogAddDownloadSuccess": "Download successful",
|
||||||
|
"@modelDialogAddDownloadSuccess": {
|
||||||
|
"description": "Text displayed when the download of a model is successful",
|
||||||
"context": "Visible in the model dialog"
|
"context": "Visible in the model dialog"
|
||||||
},
|
},
|
||||||
"deleteDialogTitle": "Delete Chat",
|
"deleteDialogTitle": "Delete Chat",
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ SpeechToText speech = SpeechToText();
|
||||||
FlutterTts voice = FlutterTts();
|
FlutterTts voice = FlutterTts();
|
||||||
bool voiceSupported = false;
|
bool voiceSupported = false;
|
||||||
|
|
||||||
|
BuildContext? mainContext;
|
||||||
void Function(void Function())? setGlobalState;
|
void Function(void Function())? setGlobalState;
|
||||||
void Function(void Function())? setMainAppState;
|
void Function(void Function())? setMainAppState;
|
||||||
|
|
||||||
|
|
@ -673,6 +674,7 @@ class _MainAppState extends State<MainApp> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
mainContext = context;
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
(_) async {
|
(_) async {
|
||||||
|
|
@ -692,6 +694,10 @@ class _MainAppState extends State<MainApp> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(allowSettings || useHost)) {
|
if (!(allowSettings || useHost)) {
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
resetSystemNavigation(context,
|
||||||
|
statusBarColor: Colors.black,
|
||||||
|
systemNavigationBarColor: Colors.black);
|
||||||
showDialog(
|
showDialog(
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,7 @@ class _ScreenSettingsState extends State<ScreenSettings> {
|
||||||
value: (prefs!
|
value: (prefs!
|
||||||
.getString("hostHeaders") ??
|
.getString("hostHeaders") ??
|
||||||
""),
|
""),
|
||||||
|
enableSuggestions: false,
|
||||||
valueIfCanceled: "{}",
|
valueIfCanceled: "{}",
|
||||||
validator: (content) async {
|
validator: (content) async {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ void setModel(BuildContext context, Function setState) {
|
||||||
List<String> modelsReal = [];
|
List<String> modelsReal = [];
|
||||||
List<bool> modal = [];
|
List<bool> modal = [];
|
||||||
int usedIndex = -1;
|
int usedIndex = -1;
|
||||||
|
int oldIndex = -1;
|
||||||
int addIndex = -1;
|
int addIndex = -1;
|
||||||
bool loaded = false;
|
bool loaded = false;
|
||||||
Function? setModalState;
|
Function? setModalState;
|
||||||
|
|
@ -52,6 +53,18 @@ void setModel(BuildContext context, Function setState) {
|
||||||
for (var i = 0; i < modelsReal.length; i++) {
|
for (var i = 0; i < modelsReal.length; i++) {
|
||||||
if (modelsReal[i] == model) {
|
if (modelsReal[i] == model) {
|
||||||
usedIndex = i;
|
usedIndex = i;
|
||||||
|
oldIndex = usedIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prefs!.getBool("modelTags") == null) {
|
||||||
|
List duplicateFinder = [];
|
||||||
|
for (var model in models) {
|
||||||
|
if (duplicateFinder.contains(model)) {
|
||||||
|
prefs!.setBool("modelTags", true);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
duplicateFinder.add(model);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loaded = true;
|
loaded = true;
|
||||||
|
|
@ -64,7 +77,6 @@ void setModel(BuildContext context, Function setState) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
content: Text(
|
content: Text(
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
AppLocalizations.of(context)!.settingsHostInvalid("timeout")),
|
AppLocalizations.of(context)!.settingsHostInvalid("timeout")),
|
||||||
|
|
@ -84,18 +96,14 @@ void setModel(BuildContext context, Function setState) {
|
||||||
onPopInvoked: (didPop) async {
|
onPopInvoked: (didPop) async {
|
||||||
if (!loaded) return;
|
if (!loaded) return;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
|
bool preload = false;
|
||||||
if (usedIndex >= 0 && modelsReal[usedIndex] != model) {
|
if (usedIndex >= 0 && modelsReal[usedIndex] != model) {
|
||||||
|
preload = true;
|
||||||
if (prefs!.getBool("resetOnModelSelect") ??
|
if (prefs!.getBool("resetOnModelSelect") ??
|
||||||
true && allowMultipleChats) {
|
true && allowMultipleChats) {
|
||||||
messages = [];
|
messages = [];
|
||||||
chatUuid = null;
|
chatUuid = null;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
desktopTitleVisible = true;
|
|
||||||
});
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
model = (usedIndex >= 0) ? modelsReal[usedIndex] : null;
|
model = (usedIndex >= 0) ? modelsReal[usedIndex] : null;
|
||||||
chatAllowed = !(model == null);
|
chatAllowed = !(model == null);
|
||||||
|
|
@ -108,6 +116,7 @@ void setModel(BuildContext context, Function setState) {
|
||||||
prefs?.setBool("multimodal", multimodal);
|
prefs?.setBool("multimodal", multimodal);
|
||||||
|
|
||||||
if (model != null &&
|
if (model != null &&
|
||||||
|
preload &&
|
||||||
int.parse(prefs!.getString("keepAlive") ?? "300") != 0 &&
|
int.parse(prefs!.getString("keepAlive") ?? "300") != 0 &&
|
||||||
(prefs!.getBool("preloadModel") ?? true)) {
|
(prefs!.getBool("preloadModel") ?? true)) {
|
||||||
setLocalState(() {});
|
setLocalState(() {});
|
||||||
|
|
@ -152,7 +161,9 @@ void setModel(BuildContext context, Function setState) {
|
||||||
setState(() {
|
setState(() {
|
||||||
desktopTitleVisible = true;
|
desktopTitleVisible = true;
|
||||||
});
|
});
|
||||||
Navigator.of(context).pop();
|
try {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -251,14 +262,16 @@ void setModel(BuildContext context, Function setState) {
|
||||||
onSelected: (bool selected) {
|
onSelected: (bool selected) {
|
||||||
selectionHaptic();
|
selectionHaptic();
|
||||||
if (addIndex == index) {
|
if (addIndex == index) {
|
||||||
|
usedIndex = oldIndex;
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
ScaffoldMessenger.of(context)
|
// ScaffoldMessenger.of(context)
|
||||||
.showSnackBar(SnackBar(
|
// .showSnackBar(SnackBar(
|
||||||
content: Text(
|
// content: Text(
|
||||||
AppLocalizations.of(
|
// AppLocalizations.of(
|
||||||
context)!
|
// context)!
|
||||||
.modelDialogAddSteps),
|
// .modelDialogAddSteps),
|
||||||
showCloseIcon: true));
|
// showCloseIcon: true));
|
||||||
|
addModel(context, setState);
|
||||||
}
|
}
|
||||||
if (!chatAllowed && model != null) {
|
if (!chatAllowed && model != null) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -299,6 +312,166 @@ void setModel(BuildContext context, Function setState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addModel(BuildContext context, Function setState) async {
|
||||||
|
var client = llama.OllamaClient(
|
||||||
|
headers: (jsonDecode(prefs!.getString("hostHeaders") ?? "{}") as Map)
|
||||||
|
.cast<String, String>(),
|
||||||
|
baseUrl: "$host/api");
|
||||||
|
bool canceled = false;
|
||||||
|
bool networkError = false;
|
||||||
|
bool alreadyExists = false;
|
||||||
|
final String invalidText =
|
||||||
|
AppLocalizations.of(context)!.modelDialogAddPromptInvalid;
|
||||||
|
final networkErrorText =
|
||||||
|
AppLocalizations.of(context)!.settingsHostInvalid("other");
|
||||||
|
final alreadyExistsText =
|
||||||
|
AppLocalizations.of(context)!.modelDialogAddPromptAlreadyExists;
|
||||||
|
final downloadSuccessText =
|
||||||
|
AppLocalizations.of(context)!.modelDialogAddDownloadSuccess;
|
||||||
|
final downloadFailedText =
|
||||||
|
AppLocalizations.of(context)!.modelDialogAddDownloadFailed;
|
||||||
|
var requestedModel = await prompt(
|
||||||
|
context,
|
||||||
|
title: AppLocalizations.of(context)!.modelDialogAddPromptTitle,
|
||||||
|
description: AppLocalizations.of(context)!.modelDialogAddPromptDescription,
|
||||||
|
placeholder: "llama3:latest",
|
||||||
|
enableSuggestions: false,
|
||||||
|
validator: (content) async {
|
||||||
|
var model = content;
|
||||||
|
model = model.removeSuffix(":latest");
|
||||||
|
if (model == "") return false;
|
||||||
|
canceled = false;
|
||||||
|
networkError = false;
|
||||||
|
alreadyExists = false;
|
||||||
|
try {
|
||||||
|
var request = await client.listModels().timeout(Duration(
|
||||||
|
seconds: (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0))
|
||||||
|
.round()));
|
||||||
|
for (var element in request.models!) {
|
||||||
|
var localModel = element.model!.removeSuffix(":latest");
|
||||||
|
if (localModel == model) {
|
||||||
|
alreadyExists = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (alreadyExists) return false;
|
||||||
|
} catch (_) {
|
||||||
|
networkError = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
http.Response response;
|
||||||
|
try {
|
||||||
|
response = await http
|
||||||
|
.get(Uri.parse("https://ollama.com/library/$model"))
|
||||||
|
.timeout(Duration(
|
||||||
|
seconds: (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0))
|
||||||
|
.round()));
|
||||||
|
} catch (_) {
|
||||||
|
networkError = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
bool returnValue = false;
|
||||||
|
await showDialog(
|
||||||
|
context: mainContext!,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(AppLocalizations.of(context)!
|
||||||
|
.modelDialogAddAssuranceTitle(model)),
|
||||||
|
content: Text(AppLocalizations.of(context)!
|
||||||
|
.modelDialogAddAssuranceDescription(model)),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
canceled = true;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(AppLocalizations.of(context)!
|
||||||
|
.modelDialogAddAssuranceCancel)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
returnValue = true;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(AppLocalizations.of(context)!
|
||||||
|
.modelDialogAddAssuranceAdd))
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
validatorErrorCallback: (content) {
|
||||||
|
if (networkError) return networkErrorText;
|
||||||
|
if (alreadyExists) return alreadyExistsText;
|
||||||
|
if (canceled) return null;
|
||||||
|
return invalidText;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (requestedModel == "") return;
|
||||||
|
requestedModel = requestedModel.removeSuffix(":latest");
|
||||||
|
double? percent;
|
||||||
|
Function? setDialogState;
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: mainContext!,
|
||||||
|
builder: (context) {
|
||||||
|
return StatefulBuilder(builder: (context, setLocalState) {
|
||||||
|
setDialogState = setLocalState;
|
||||||
|
return PopScope(
|
||||||
|
canPop: false,
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
top: 16,
|
||||||
|
bottom: desktopLayout(context) ? 16 : 0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
percent == null
|
||||||
|
? AppLocalizations.of(context)!
|
||||||
|
.modelDialogAddDownloadPercentLoading
|
||||||
|
: AppLocalizations.of(context)!
|
||||||
|
.modelDialogAddDownloadPercent(
|
||||||
|
(percent * 100).round().toString()),
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(top: 4)),
|
||||||
|
LinearProgressIndicator(value: percent),
|
||||||
|
],
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
final stream = client
|
||||||
|
.pullModelStream(request: llama.PullModelRequest(model: requestedModel))
|
||||||
|
.timeout(Duration(
|
||||||
|
seconds: (10.0 * (prefs!.getDouble("timeoutMultiplier") ?? 1.0))
|
||||||
|
.round()));
|
||||||
|
await for (final res in stream) {
|
||||||
|
percent = ((res.completed ?? 0).toInt() / (res.total ?? 100).toInt());
|
||||||
|
if ((percent * 100).round() == 0) {
|
||||||
|
percent = null;
|
||||||
|
}
|
||||||
|
setDialogState!(() {});
|
||||||
|
}
|
||||||
|
Navigator.of(mainContext!).pop();
|
||||||
|
setState(() {
|
||||||
|
model = requestedModel;
|
||||||
|
if (model!.split(":").length == 1) {
|
||||||
|
model = "$model:latest";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ScaffoldMessenger.of(mainContext!).showSnackBar(
|
||||||
|
SnackBar(content: Text(downloadSuccessText), showCloseIcon: true));
|
||||||
|
} catch (_) {
|
||||||
|
Navigator.of(mainContext!).pop();
|
||||||
|
ScaffoldMessenger.of(mainContext!).showSnackBar(
|
||||||
|
SnackBar(content: Text(downloadFailedText), showCloseIcon: true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void saveChat(String uuid, Function setState) async {
|
void saveChat(String uuid, Function setState) async {
|
||||||
int index = -1;
|
int index = -1;
|
||||||
for (var i = 0; i < (prefs!.getStringList("chats") ?? []).length; i++) {
|
for (var i = 0; i < (prefs!.getStringList("chats") ?? []).length; i++) {
|
||||||
|
|
@ -493,11 +666,15 @@ Future<String> prompt(BuildContext context,
|
||||||
String title = "",
|
String title = "",
|
||||||
String? valueIfCanceled,
|
String? valueIfCanceled,
|
||||||
TextInputType keyboard = TextInputType.text,
|
TextInputType keyboard = TextInputType.text,
|
||||||
|
bool autocorrect = true,
|
||||||
|
Iterable<String> autofillHints = const [],
|
||||||
|
bool enableSuggestions = true,
|
||||||
Icon? prefixIcon,
|
Icon? prefixIcon,
|
||||||
int maxLines = 1,
|
int maxLines = 1,
|
||||||
String? uuid,
|
String? uuid,
|
||||||
Future<bool> Function(String content)? validator,
|
Future<bool> Function(String content)? validator,
|
||||||
String? validatorError,
|
String? validatorError,
|
||||||
|
String? Function(String content)? validatorErrorCallback,
|
||||||
String? placeholder,
|
String? placeholder,
|
||||||
bool prefill = true}) async {
|
bool prefill = true}) async {
|
||||||
var returnText = (valueIfCanceled != null) ? valueIfCanceled : value;
|
var returnText = (valueIfCanceled != null) ? valueIfCanceled : value;
|
||||||
|
|
@ -510,6 +687,35 @@ Future<String> prompt(BuildContext context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return StatefulBuilder(builder: (context, setLocalState) {
|
return StatefulBuilder(builder: (context, setLocalState) {
|
||||||
|
void submit() async {
|
||||||
|
selectionHaptic();
|
||||||
|
if (validator != null) {
|
||||||
|
setLocalState(() {
|
||||||
|
error = null;
|
||||||
|
loading = true;
|
||||||
|
});
|
||||||
|
bool valid = await validator(controller.text);
|
||||||
|
setLocalState(() {
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
if (!valid) {
|
||||||
|
setLocalState(() {
|
||||||
|
if (validatorError != null) {
|
||||||
|
error = validatorError;
|
||||||
|
} else if (validatorErrorCallback != null) {
|
||||||
|
error = validatorErrorCallback(controller.text);
|
||||||
|
} else {
|
||||||
|
error = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
returnText = controller.text;
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
|
|
@ -540,25 +746,11 @@ Future<String> prompt(BuildContext context,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
keyboardType: keyboard,
|
keyboardType: keyboard,
|
||||||
|
autocorrect: autocorrect,
|
||||||
|
autofillHints: autofillHints,
|
||||||
|
enableSuggestions: enableSuggestions,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
onSubmitted: (value) async {
|
onSubmitted: (_) => submit(),
|
||||||
if (validator != null) {
|
|
||||||
selectionHaptic();
|
|
||||||
setLocalState(() {
|
|
||||||
error = null;
|
|
||||||
});
|
|
||||||
bool valid = await validator(controller.text);
|
|
||||||
if (!valid) {
|
|
||||||
setLocalState(() {
|
|
||||||
error = validatorError;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
returnText = controller.text;
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
hintText: placeholder,
|
hintText: placeholder,
|
||||||
|
|
@ -566,25 +758,7 @@ Future<String> prompt(BuildContext context,
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
tooltip: AppLocalizations.of(context)!
|
tooltip: AppLocalizations.of(context)!
|
||||||
.tooltipSave,
|
.tooltipSave,
|
||||||
onPressed: () async {
|
onPressed: submit,
|
||||||
if (validator != null) {
|
|
||||||
selectionHaptic();
|
|
||||||
setLocalState(() {
|
|
||||||
error = null;
|
|
||||||
});
|
|
||||||
bool valid =
|
|
||||||
await validator(controller.text);
|
|
||||||
if (!valid) {
|
|
||||||
setLocalState(() {
|
|
||||||
error = validatorError;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
returnText = controller.text;
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.save_rounded)),
|
icon: const Icon(Icons.save_rounded)),
|
||||||
prefixIcon: (title ==
|
prefixIcon: (title ==
|
||||||
AppLocalizations.of(context)!
|
AppLocalizations.of(context)!
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,18 @@
|
||||||
"de": [
|
"de": [
|
||||||
"deleteChat",
|
"deleteChat",
|
||||||
"renameChat",
|
"renameChat",
|
||||||
|
"modelDialogAddPromptTitle",
|
||||||
|
"modelDialogAddPromptDescription",
|
||||||
|
"modelDialogAddPromptAlreadyExists",
|
||||||
|
"modelDialogAddPromptInvalid",
|
||||||
|
"modelDialogAddAssuranceTitle",
|
||||||
|
"modelDialogAddAssuranceDescription",
|
||||||
|
"modelDialogAddAssuranceAdd",
|
||||||
|
"modelDialogAddAssuranceCancel",
|
||||||
|
"modelDialogAddDownloadPercentLoading",
|
||||||
|
"modelDialogAddDownloadPercent",
|
||||||
|
"modelDialogAddDownloadFailed",
|
||||||
|
"modelDialogAddDownloadSuccess",
|
||||||
"settingsDescriptionBehavior",
|
"settingsDescriptionBehavior",
|
||||||
"settingsDescriptionInterface",
|
"settingsDescriptionInterface",
|
||||||
"settingsDescriptionVoice",
|
"settingsDescriptionVoice",
|
||||||
|
|
@ -27,6 +39,18 @@
|
||||||
"it": [
|
"it": [
|
||||||
"deleteChat",
|
"deleteChat",
|
||||||
"renameChat",
|
"renameChat",
|
||||||
|
"modelDialogAddPromptTitle",
|
||||||
|
"modelDialogAddPromptDescription",
|
||||||
|
"modelDialogAddPromptAlreadyExists",
|
||||||
|
"modelDialogAddPromptInvalid",
|
||||||
|
"modelDialogAddAssuranceTitle",
|
||||||
|
"modelDialogAddAssuranceDescription",
|
||||||
|
"modelDialogAddAssuranceAdd",
|
||||||
|
"modelDialogAddAssuranceCancel",
|
||||||
|
"modelDialogAddDownloadPercentLoading",
|
||||||
|
"modelDialogAddDownloadPercent",
|
||||||
|
"modelDialogAddDownloadFailed",
|
||||||
|
"modelDialogAddDownloadSuccess",
|
||||||
"settingsDescriptionBehavior",
|
"settingsDescriptionBehavior",
|
||||||
"settingsDescriptionInterface",
|
"settingsDescriptionInterface",
|
||||||
"settingsDescriptionVoice",
|
"settingsDescriptionVoice",
|
||||||
|
|
@ -52,6 +76,18 @@
|
||||||
"tr": [
|
"tr": [
|
||||||
"deleteChat",
|
"deleteChat",
|
||||||
"renameChat",
|
"renameChat",
|
||||||
|
"modelDialogAddPromptTitle",
|
||||||
|
"modelDialogAddPromptDescription",
|
||||||
|
"modelDialogAddPromptAlreadyExists",
|
||||||
|
"modelDialogAddPromptInvalid",
|
||||||
|
"modelDialogAddAssuranceTitle",
|
||||||
|
"modelDialogAddAssuranceDescription",
|
||||||
|
"modelDialogAddAssuranceAdd",
|
||||||
|
"modelDialogAddAssuranceCancel",
|
||||||
|
"modelDialogAddDownloadPercentLoading",
|
||||||
|
"modelDialogAddDownloadPercent",
|
||||||
|
"modelDialogAddDownloadFailed",
|
||||||
|
"modelDialogAddDownloadSuccess",
|
||||||
"settingsDescriptionBehavior",
|
"settingsDescriptionBehavior",
|
||||||
"settingsDescriptionInterface",
|
"settingsDescriptionInterface",
|
||||||
"settingsDescriptionVoice",
|
"settingsDescriptionVoice",
|
||||||
|
|
@ -77,6 +113,18 @@
|
||||||
"zh": [
|
"zh": [
|
||||||
"deleteChat",
|
"deleteChat",
|
||||||
"renameChat",
|
"renameChat",
|
||||||
|
"modelDialogAddPromptTitle",
|
||||||
|
"modelDialogAddPromptDescription",
|
||||||
|
"modelDialogAddPromptAlreadyExists",
|
||||||
|
"modelDialogAddPromptInvalid",
|
||||||
|
"modelDialogAddAssuranceTitle",
|
||||||
|
"modelDialogAddAssuranceDescription",
|
||||||
|
"modelDialogAddAssuranceAdd",
|
||||||
|
"modelDialogAddAssuranceCancel",
|
||||||
|
"modelDialogAddDownloadPercentLoading",
|
||||||
|
"modelDialogAddDownloadPercent",
|
||||||
|
"modelDialogAddDownloadFailed",
|
||||||
|
"modelDialogAddDownloadSuccess",
|
||||||
"settingsDescriptionBehavior",
|
"settingsDescriptionBehavior",
|
||||||
"settingsDescriptionInterface",
|
"settingsDescriptionInterface",
|
||||||
"settingsDescriptionVoice",
|
"settingsDescriptionVoice",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue