Restrict desktop settings width, centralized delete dialog, allow changing titles and deleting non current chats

This commit is contained in:
JHubi1 2024-08-21 00:58:13 +02:00
parent 8813571b65
commit efcca113c8
No known key found for this signature in database
GPG Key ID: 7BF82570CBBBD050
10 changed files with 1210 additions and 1302 deletions

View File

@ -424,7 +424,8 @@ class _MainAppState extends State<MainApp> {
? null
: () async {
selectionHaptic();
if (!chatAllowed) return;
if (!chatAllowed &&
chatUuid == jsonDecode(item)["uuid"]) return;
if (!allowSettings) return;
String oldTitle = jsonDecode(item)["title"];
var newTitle = await prompt(context,
@ -480,6 +481,10 @@ class _MainAppState extends State<MainApp> {
width: 24,
child: IconButton(
onPressed: () {
if (!chatAllowed &&
chatUuid ==
jsonDecode(item)["uuid"])
return;
if (!allowMultipleChats) {
for (var i = 0;
i <
@ -511,124 +516,14 @@ class _MainAppState extends State<MainApp> {
return;
}
if (!allowSettings) {
if (prefs!.getBool(
"askBeforeDeletion") ??
false) {
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context,
setLocalState) {
return AlertDialog(
title: Text(
AppLocalizations.of(
context)!
.deleteDialogTitle),
content: Column(
mainAxisSize:
MainAxisSize
.min,
children: [
Text(AppLocalizations.of(
context)!
.deleteDialogDescription),
]),
actions: [
TextButton(
onPressed:
() {
Navigator.of(
context)
.pop();
},
child: Text(AppLocalizations.of(
context)!
.deleteDialogCancel)),
TextButton(
onPressed:
() {
Navigator.of(
context)
.pop();
for (var i =
0;
i < (prefs!.getStringList("chats") ?? []).length;
i++) {
if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])[
"uuid"] ==
jsonDecode(
item)["uuid"]) {
List<String>
tmp =
prefs!.getStringList("chats")!;
tmp.removeAt(
i);
prefs!.setStringList(
"chats",
tmp);
break;
}
}
if (chatUuid ==
jsonDecode(
item)["uuid"]) {
messages =
[];
chatUuid =
null;
if (!desktopLayoutRequired(
context)) {
Navigator.of(context)
.pop();
}
}
setState(
() {});
},
child: Text(AppLocalizations.of(
context)!
.deleteDialogDelete))
]);
});
});
} else {
for (var i = 0;
i <
(prefs!.getStringList(
"chats") ??
[])
.length;
i++) {
if (jsonDecode((prefs!
.getStringList(
"chats") ??
[])[i])["uuid"] ==
jsonDecode(
item)["uuid"]) {
List<String> tmp = prefs!
.getStringList(
"chats")!;
tmp.removeAt(i);
prefs!.setStringList(
"chats", tmp);
break;
}
}
if (chatUuid ==
jsonDecode(item)["uuid"]) {
messages = [];
chatUuid = null;
if (!desktopLayoutRequired(
context)) {
Navigator.of(context).pop();
}
}
setState(() {});
}
deleteChatDialog(
context, setState,
additionalCondition: false,
uuid:
jsonDecode(item)["uuid"],
popSidebar: true);
return;
}
if (!chatAllowed) return;
if (!desktopLayoutRequired(
context)) {
Navigator.of(context).pop();
@ -636,7 +531,22 @@ class _MainAppState extends State<MainApp> {
showModalBottomSheet(
context: context,
builder: (context) {
return Padding(
return Container(
decoration: (Theme.of(context)
.brightness ==
Brightness.dark)
? BoxDecoration(
border: Border.all(
color: Colors
.white),
borderRadius: const BorderRadius.only(
topLeft:
Radius.circular(
26),
topRight:
Radius.circular(
26)))
: null,
padding:
const EdgeInsets.only(
left: 16,
@ -655,68 +565,11 @@ class _MainAppState extends State<MainApp> {
() {
Navigator.of(context)
.pop();
if (prefs!.getBool("askBeforeDeletion") ??
false) {
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(builder: (context, setLocalState) {
return AlertDialog(
title: Text(AppLocalizations.of(context)!.deleteDialogTitle),
content: Column(mainAxisSize: MainAxisSize.min, children: [
Text(AppLocalizations.of(context)!.deleteDialogDescription),
]),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(AppLocalizations.of(context)!.deleteDialogCancel)),
TextButton(
onPressed: () {
Navigator.of(context).pop();
for (var i = 0; i < (prefs!.getStringList("chats") ?? []).length; i++) {
if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])["uuid"] == jsonDecode(item)["uuid"]) {
List<String> tmp = prefs!.getStringList("chats")!;
tmp.removeAt(i);
prefs!.setStringList("chats", tmp);
break;
}
}
if (chatUuid == jsonDecode(item)["uuid"]) {
messages = [];
chatUuid = null;
if (!desktopLayoutRequired(context)) {
Navigator.of(context).pop();
}
}
setState(() {});
},
child: Text(AppLocalizations.of(context)!.deleteDialogDelete))
]);
});
});
} else {
for (var i = 0;
i < (prefs!.getStringList("chats") ?? []).length;
i++) {
if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])["uuid"] == jsonDecode(item)["uuid"]) {
List<String> tmp = prefs!.getStringList("chats")!;
tmp.removeAt(i);
prefs!.setStringList("chats", tmp);
break;
}
}
if (chatUuid ==
jsonDecode(item)["uuid"]) {
messages = [];
chatUuid = null;
if (!desktopLayoutRequired(context)) {
Navigator.of(context).pop();
}
}
setState(() {});
}
deleteChatDialog(
context,
setState,
uuid: jsonDecode(item)["uuid"],
popSidebar: true);
},
icon: const Icon(Icons
.delete_forever_rounded),
@ -793,48 +646,10 @@ class _MainAppState extends State<MainApp> {
? DismissDirection.startToEnd
: DismissDirection.none,
confirmDismiss: (direction) async {
bool returnValue = false;
if (!chatAllowed) return false;
if (prefs!.getBool("askBeforeDeletion") ?? false) {
await showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setLocalState) {
return AlertDialog(
title: Text(AppLocalizations.of(context)!
.deleteDialogTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(AppLocalizations.of(context)!
.deleteDialogDescription),
]),
actions: [
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
returnValue = false;
},
child: Text(AppLocalizations.of(context)!
.deleteDialogCancel)),
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
returnValue = true;
},
child: Text(AppLocalizations.of(context)!
.deleteDialogDelete))
]);
});
});
} else {
returnValue = true;
}
return returnValue;
if (!chatAllowed && chatUuid == jsonDecode(item)["uuid"])
return false;
return await deleteChatDialog(context, setState,
takeAction: false);
},
onDismissed: (direction) {
selectionHaptic();
@ -937,15 +752,18 @@ class _MainAppState extends State<MainApp> {
resetSystemNavigation(context);
Widget selector = InkWell(
onTap: () {
if (host == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(AppLocalizations.of(context)!.noHostSelected),
showCloseIcon: true));
return;
}
setModel(context, setState);
},
onTap: !useModel
? () {
if (host == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content:
Text(AppLocalizations.of(context)!.noHostSelected),
showCloseIcon: true));
return;
}
setModel(context, setState);
}
: null,
splashFactory: NoSplash.splashFactory,
highlightColor: Colors.transparent,
enableFeedback: false,
@ -1053,90 +871,8 @@ class _MainAppState extends State<MainApp> {
onPressed: () {
selectionHaptic();
if (!chatAllowed) return;
if (prefs!.getBool("askBeforeDeletion") ??
// ignore: dead_code
false && messages.isNotEmpty) {
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setLocalState) {
return AlertDialog(
title: Text(
AppLocalizations.of(context)!
.deleteDialogTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(AppLocalizations.of(context)!
.deleteDialogDescription),
]),
actions: [
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!
.deleteDialogCancel)),
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
for (var i = 0;
i <
(prefs!.getStringList(
"chats") ??
[])
.length;
i++) {
if (jsonDecode((prefs!
.getStringList(
"chats") ??
[])[i])["uuid"] ==
chatUuid) {
List<String> tmp = prefs!
.getStringList(
"chats")!;
tmp.removeAt(i);
prefs!.setStringList(
"chats", tmp);
break;
}
}
messages = [];
chatUuid = null;
setState(() {});
},
child: Text(
AppLocalizations.of(context)!
.deleteDialogDelete))
]);
});
});
} else {
for (var i = 0;
i <
(prefs!.getStringList("chats") ?? [])
.length;
i++) {
if (jsonDecode((prefs!.getStringList("chats") ??
[])[i])["uuid"] ==
chatUuid) {
List<String> tmp =
prefs!.getStringList("chats")!;
tmp.removeAt(i);
prefs!.setStringList("chats", tmp);
break;
}
}
messages = [];
chatUuid = null;
}
setState(() {});
deleteChatDialog(context, setState,
additionalCondition: messages.isNotEmpty);
},
icon: const Icon(Icons.restart_alt_rounded))
: const SizedBox.shrink()
@ -1533,189 +1269,198 @@ class _MainAppState extends State<MainApp> {
context: context,
builder: (context) {
return Container(
decoration: (Theme.of(context)
.brightness ==
Brightness.dark)
? BoxDecoration(
border: Border.all(
color:
Colors.white),
borderRadius:
const BorderRadius.only(
topLeft: Radius
.circular(
26),
topRight:
Radius.circular(
26)))
: null,
width: double.infinity,
padding: const EdgeInsets.only(
left: 16,
right: 16,
top: 16),
child: Column(
mainAxisSize:
MainAxisSize.min,
children: [
(prefs?.getBool(
"voiceModeEnabled") ??
false)
? SizedBox(
width: double
.infinity,
child: OutlinedButton
.icon(
onPressed:
() async {
selectionHaptic();
Navigator.of(context)
.pop();
setMainState =
setState;
settingsOpen =
true;
logoVisible =
false;
Navigator.of(context).push(MaterialPageRoute(
child:
Column(mainAxisSize: MainAxisSize.min, children: [
(prefs?.getBool(
"voiceModeEnabled") ??
false)
? SizedBox(
width:
double.infinity,
child: OutlinedButton
.icon(
onPressed:
() async {
selectionHaptic();
Navigator.of(
context)
.pop();
setMainState =
setState;
settingsOpen =
true;
logoVisible =
false;
Navigator.of(
context)
.push(MaterialPageRoute(
builder: (context) =>
const ScreenVoice()));
},
icon: const Icon(
Icons
.headphones_rounded),
label: Text(
AppLocalizations.of(context)!
.settingsTitleVoice)))
: const SizedBox
.shrink(),
(prefs?.getBool(
"voiceModeEnabled") ??
false)
? const SizedBox(
height: 8)
: const SizedBox
.shrink(),
SizedBox(
width:
double.infinity,
child: OutlinedButton
.icon(
onPressed:
() async {
selectionHaptic();
Navigator.of(
context)
.pop();
final result =
await ImagePicker()
.pickImage(
source: ImageSource
.camera,
);
if (result ==
null) {
return;
}
final bytes =
await result
.readAsBytes();
final image =
await decodeImageFromList(
bytes);
final message =
types
.ImageMessage(
author:
user,
createdAt:
DateTime.now()
.millisecondsSinceEpoch,
height: image
.height
.toDouble(),
id: const Uuid()
.v4(),
name: result
.name,
size: bytes
.length,
uri: result
.path,
width: image
.width
.toDouble(),
);
messages.insert(
0,
message);
setState(
() {});
selectionHaptic();
},
icon: const Icon(
Icons
.photo_camera_rounded),
.headphones_rounded),
label: Text(AppLocalizations.of(
context)!
.settingsTitleVoice)))
: const SizedBox.shrink(),
(prefs?.getBool(
"voiceModeEnabled") ??
false)
? const SizedBox(
height: 8)
: const SizedBox.shrink(),
SizedBox(
width: double.infinity,
child:
OutlinedButton.icon(
onPressed:
() async {
selectionHaptic();
Navigator.of(
context)
.pop();
final result =
await ImagePicker()
.pickImage(
source:
ImageSource
.camera,
);
if (result ==
null) {
return;
}
final bytes =
await result
.readAsBytes();
final image =
await decodeImageFromList(
bytes);
final message =
types
.ImageMessage(
author: user,
createdAt: DateTime
.now()
.millisecondsSinceEpoch,
height: image
.height
.toDouble(),
id: const Uuid()
.v4(),
name: result
.name,
size: bytes
.length,
uri: result
.path,
width: image
.width
.toDouble(),
);
messages.insert(
0, message);
setState(() {});
selectionHaptic();
},
icon: const Icon(Icons
.photo_camera_rounded),
label: Text(
AppLocalizations.of(
context)!
.takeImage))),
const SizedBox(height: 8),
SizedBox(
width:
double.infinity,
child: OutlinedButton
.icon(
onPressed:
() async {
selectionHaptic();
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child:
OutlinedButton.icon(
onPressed:
() async {
selectionHaptic();
Navigator.of(
context)
.pop();
final result =
await ImagePicker()
.pickImage(
source: ImageSource
Navigator.of(
context)
.pop();
final result =
await ImagePicker()
.pickImage(
source:
ImageSource
.gallery,
);
if (result ==
null) {
return;
}
);
if (result ==
null) {
return;
}
final bytes =
await result
.readAsBytes();
final image =
await decodeImageFromList(
bytes);
final bytes =
await result
.readAsBytes();
final image =
await decodeImageFromList(
bytes);
final message =
types
.ImageMessage(
author:
user,
createdAt:
DateTime.now()
.millisecondsSinceEpoch,
height: image
.height
.toDouble(),
id: const Uuid()
.v4(),
name: result
.name,
size: bytes
.length,
uri: result
.path,
width: image
.width
.toDouble(),
);
final message =
types
.ImageMessage(
author: user,
createdAt: DateTime
.now()
.millisecondsSinceEpoch,
height: image
.height
.toDouble(),
id: const Uuid()
.v4(),
name: result
.name,
size: bytes
.length,
uri: result
.path,
width: image
.width
.toDouble(),
);
messages.insert(
0,
message);
setState(
() {});
selectionHaptic();
},
icon: const Icon(
Icons
.image_rounded),
label: Text(AppLocalizations.of(
messages.insert(
0, message);
setState(() {});
selectionHaptic();
},
icon: const Icon(Icons
.image_rounded),
label: Text(
AppLocalizations.of(
context)!
.uploadImage)))
]));
]));
});
},
l10n: ChatL10nEn(

View File

@ -125,14 +125,15 @@ Widget title(String text, {double top = 16, double bottom = 16}) {
Padding(
padding: const EdgeInsets.only(left: 24, right: 24),
child: Text(text)),
const Expanded(child: Divider())
const Expanded(child: Divider(height: 1))
]));
}
Widget titleDivider({double? top, double? bottom, BuildContext? context}) {
top ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16;
bottom ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16;
return Padding(
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: EdgeInsets.only(left: 8, right: 8, top: top, bottom: bottom),
child: const Row(
mainAxisSize: MainAxisSize.max,
@ -143,13 +144,12 @@ Widget verticalTitleDivider(
{double? left, double? right, BuildContext? context}) {
left ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16;
right ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16;
return Padding(
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: EdgeInsets.only(left: left, right: right, top: 8, bottom: 8),
child: const Row(mainAxisSize: MainAxisSize.max, children: [
// Expanded(child:
VerticalDivider()
// ),
]));
child: const Row(
mainAxisSize: MainAxisSize.max,
children: [VerticalDivider(width: 1)]));
}
Widget button(String text, IconData? icon, void Function()? onPressed,
@ -359,6 +359,7 @@ class _ScreenSettingsState extends State<ScreenSettings> {
TextField(
controller: hostInputController,
keyboardType: TextInputType.url,
autofillHints: const [AutofillHints.url],
readOnly: useHost,
onSubmitted: (value) {
selectionHaptic();

View File

@ -42,123 +42,130 @@ class _ScreenSettingsAboutState extends State<ScreenSettingsAbout> {
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
]),
actions: desktopControlsActions(context)),
body: Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [
Expanded(
child: ListView(children: [
// const SizedBox(height: 8),
button(
AppLocalizations.of(context)!
.settingsVersion(currentVersion ?? ""),
Icons.verified_rounded,
null),
(updateStatus == "notAvailable")
? const SizedBox.shrink()
: button(
(!updateChecked
? AppLocalizations.of(context)!
.settingsUpdateCheck
: updateLoading
? AppLocalizations.of(context)!
.settingsUpdateChecking
: (updateStatus == "rateLimit")
? AppLocalizations.of(context)!
.settingsUpdateRateLimit
: (updateStatus != "ok")
? AppLocalizations.of(context)!
.settingsUpdateIssue
: (Version.parse(latestVersion ??
"1.0.0") >
Version.parse(
currentVersion ??
"2.0.0"))
? AppLocalizations.of(context)!
.settingsUpdateAvailable(
latestVersion!)
: AppLocalizations.of(context)!
.settingsUpdateLatest),
((updateStatus != "ok")
? Icons.warning_rounded
: (Version.parse(latestVersion ?? "1.0.0") >
Version.parse(
currentVersion ?? "2.0.0"))
? Icons.info_outline_rounded
: Icons.update_rounded), () {
if (updateLoading) return;
selectionHaptic();
if ((Version.parse(latestVersion ?? "1.0.0") >
Version.parse(currentVersion ?? "2.0.0")) &&
(updateStatus == "ok")) {
updateDialog(context, title);
} else {
checkUpdate(setState);
return;
}
}),
(updateStatus == "notAvailable")
? const SizedBox.shrink()
: toggle(
context,
AppLocalizations.of(context)!
.settingsCheckForUpdates,
(prefs!.getBool("checkUpdateOnSettingsOpen") ??
false), (value) {
selectionHaptic();
prefs!.setBool("checkUpdateOnSettingsOpen", value);
setState(() {});
}),
titleDivider(context: context),
button(AppLocalizations.of(context)!.settingsGithub,
SimpleIcons.github, () {
selectionHaptic();
launchUrl(
mode: LaunchMode.inAppBrowserView,
Uri.parse(repoUrl));
}),
button(AppLocalizations.of(context)!.settingsReportIssue,
Icons.report_rounded, () {
selectionHaptic();
launchUrl(
mode: LaunchMode.inAppBrowserView,
Uri.parse("$repoUrl/issues"));
}),
button(AppLocalizations.of(context)!.settingsLicenses,
Icons.gavel_rounded, () {
selectionHaptic();
String legal = "Copyright 2024 JHubi1";
Widget icon = const Padding(
padding: EdgeInsets.all(16),
child: ImageIcon(AssetImage("assets/logo512.png"),
size: 48),
);
if (desktopFeature()) {
showDialog(
context: context,
builder: (context) {
return Dialog(
child: ClipRRect(
borderRadius: BorderRadius.circular(28),
child: LicensePage(
applicationName: "Ollama App",
applicationVersion: currentVersion,
applicationIcon: icon,
applicationLegalese: legal),
));
});
} else {
showLicensePage(
context: context,
applicationName: "Ollama App",
applicationVersion: currentVersion,
applicationIcon: icon,
applicationLegalese: legal);
}
}),
const SizedBox(height: 16)
]),
)
]))),
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 1000),
padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [
Expanded(
child: ListView(children: [
// const SizedBox(height: 8),
button(
AppLocalizations.of(context)!
.settingsVersion(currentVersion ?? ""),
Icons.verified_rounded,
null),
(updateStatus == "notAvailable")
? const SizedBox.shrink()
: button(
(!updateChecked
? AppLocalizations.of(context)!
.settingsUpdateCheck
: updateLoading
? AppLocalizations.of(context)!
.settingsUpdateChecking
: (updateStatus == "rateLimit")
? AppLocalizations.of(context)!
.settingsUpdateRateLimit
: (updateStatus != "ok")
? AppLocalizations.of(context)!
.settingsUpdateIssue
: (Version.parse(latestVersion ??
"1.0.0") >
Version.parse(
currentVersion ??
"2.0.0"))
? AppLocalizations.of(
context)!
.settingsUpdateAvailable(
latestVersion!)
: AppLocalizations.of(
context)!
.settingsUpdateLatest),
((updateStatus != "ok")
? Icons.warning_rounded
: (Version.parse(latestVersion ?? "1.0.0") >
Version.parse(
currentVersion ?? "2.0.0"))
? Icons.info_outline_rounded
: Icons.update_rounded), () {
if (updateLoading) return;
selectionHaptic();
if ((Version.parse(latestVersion ?? "1.0.0") >
Version.parse(
currentVersion ?? "2.0.0")) &&
(updateStatus == "ok")) {
updateDialog(context, title);
} else {
checkUpdate(setState);
return;
}
}),
(updateStatus == "notAvailable")
? const SizedBox.shrink()
: toggle(
context,
AppLocalizations.of(context)!
.settingsCheckForUpdates,
(prefs!.getBool("checkUpdateOnSettingsOpen") ??
false), (value) {
selectionHaptic();
prefs!
.setBool("checkUpdateOnSettingsOpen", value);
setState(() {});
}),
titleDivider(context: context),
button(AppLocalizations.of(context)!.settingsGithub,
SimpleIcons.github, () {
selectionHaptic();
launchUrl(
mode: LaunchMode.inAppBrowserView,
Uri.parse(repoUrl));
}),
button(AppLocalizations.of(context)!.settingsReportIssue,
Icons.report_rounded, () {
selectionHaptic();
launchUrl(
mode: LaunchMode.inAppBrowserView,
Uri.parse("$repoUrl/issues"));
}),
button(AppLocalizations.of(context)!.settingsLicenses,
Icons.gavel_rounded, () {
selectionHaptic();
String legal = "Copyright 2024 JHubi1";
Widget icon = const Padding(
padding: EdgeInsets.all(16),
child: ImageIcon(AssetImage("assets/logo512.png"),
size: 48),
);
if (desktopFeature()) {
showDialog(
context: context,
builder: (context) {
return Dialog(
child: ClipRRect(
borderRadius: BorderRadius.circular(28),
child: LicensePage(
applicationName: "Ollama App",
applicationVersion: currentVersion,
applicationIcon: icon,
applicationLegalese: legal),
));
});
} else {
showLicensePage(
context: context,
applicationName: "Ollama App",
applicationVersion: currentVersion,
applicationIcon: icon,
applicationLegalese: legal);
}
}),
const SizedBox(height: 16)
]),
)
])),
)),
);
}
}

View File

@ -37,71 +37,75 @@ class _ScreenSettingsBehaviorState extends State<ScreenSettingsBehavior> {
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
]),
actions: desktopControlsActions(context)),
body: Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [
Expanded(
child: ListView(children: [
const SizedBox(height: 8),
TextField(
controller: systemInputController,
keyboardType: TextInputType.multiline,
maxLines: desktopLayoutNotRequired(context) ? 5 : 2,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!
.settingsSystemMessage,
hintText: "You are a helpful assistant",
suffixIcon: IconButton(
tooltip:
AppLocalizations.of(context)!.tooltipSave,
onPressed: () {
selectionHaptic();
prefs?.setString(
"system",
(systemInputController.text.isNotEmpty)
? systemInputController.text
: "You are a helpful assistant");
},
icon: const Icon(Icons.save_rounded),
),
border: const OutlineInputBorder())),
const SizedBox(height: 16),
toggle(
context,
AppLocalizations.of(context)!.settingsUseSystem,
(prefs!.getBool("useSystem") ?? true),
(value) {
selectionHaptic();
prefs!.setBool("useSystem", value);
setState(() {});
},
icon: const Icon(Icons.info_outline_rounded),
onLongTap: () {
selectionHaptic();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(AppLocalizations.of(context)!
.settingsUseSystemDescription),
showCloseIcon: true));
}),
toggle(
context,
AppLocalizations.of(context)!.settingsDisableMarkdown,
(prefs!.getBool("noMarkdown") ?? false), (value) {
selectionHaptic();
prefs!.setBool("noMarkdown", value);
setState(() {});
})
]),
),
const SizedBox(height: 8),
button(
AppLocalizations.of(context)!
.settingsBehaviorNotUpdatedForOlderChats,
Icons.info_rounded,
null,
color: Colors.grey
.harmonizeWith(Theme.of(context).colorScheme.primary))
]))),
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 1000),
padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [
Expanded(
child: ListView(children: [
const SizedBox(height: 8),
TextField(
controller: systemInputController,
keyboardType: TextInputType.multiline,
maxLines: desktopLayoutNotRequired(context) ? 5 : 2,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!
.settingsSystemMessage,
alignLabelWithHint: true,
hintText: "You are a helpful assistant",
suffixIcon: IconButton(
tooltip:
AppLocalizations.of(context)!.tooltipSave,
onPressed: () {
selectionHaptic();
prefs?.setString(
"system",
(systemInputController.text.isNotEmpty)
? systemInputController.text
: "You are a helpful assistant");
},
icon: const Icon(Icons.save_rounded),
),
border: const OutlineInputBorder())),
const SizedBox(height: 16),
toggle(
context,
AppLocalizations.of(context)!.settingsUseSystem,
(prefs!.getBool("useSystem") ?? true),
(value) {
selectionHaptic();
prefs!.setBool("useSystem", value);
setState(() {});
},
icon: const Icon(Icons.info_outline_rounded),
onLongTap: () {
selectionHaptic();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(AppLocalizations.of(context)!
.settingsUseSystemDescription),
showCloseIcon: true));
}),
toggle(
context,
AppLocalizations.of(context)!.settingsDisableMarkdown,
(prefs!.getBool("noMarkdown") ?? false), (value) {
selectionHaptic();
prefs!.setBool("noMarkdown", value);
setState(() {});
})
]),
),
const SizedBox(height: 8),
button(
AppLocalizations.of(context)!
.settingsBehaviorNotUpdatedForOlderChats,
Icons.info_rounded,
null,
color: Colors.grey
.harmonizeWith(Theme.of(context).colorScheme.primary))
])),
)),
);
}
}

View File

@ -36,192 +36,204 @@ class _ScreenSettingsExportState extends State<ScreenSettingsExport> {
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
]),
actions: desktopControlsActions(context)),
body: Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [
Expanded(
child: ListView(children: [
// const SizedBox(height: 8),
button(AppLocalizations.of(context)!.settingsExportChats,
Icons.upload_rounded, () async {
selectionHaptic();
var name =
"ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json";
var content =
jsonEncode(prefs!.getStringList("chats") ?? []);
if (kIsWeb) {
// web fallback
final bytes = utf8.encode(content);
final blob = html.Blob([bytes]);
final url = html.Url.createObjectUrlFromBlob(blob);
final anchor = html.document.createElement("a")
as html.AnchorElement
..href = url
..style.display = "none"
..download = name;
html.document.body!.children.add(anchor);
anchor.click();
html.document.body!.children.remove(anchor);
html.Url.revokeObjectUrl(url);
} else {
String? path = "";
try {
path = (await file_selector
.getSaveLocation(acceptedTypeGroups: [
const file_selector.XTypeGroup(
label: "Ollama App File", extensions: ["json"])
], suggestedName: name))
?.path;
} catch (_) {
path = await FilePicker.platform.saveFile(
type: FileType.custom,
allowedExtensions: ["json"],
fileName: name,
bytes: utf8.encode(jsonEncode(
prefs!.getStringList("chats") ?? [])));
}
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 1000),
padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [
Expanded(
child: ListView(children: [
// const SizedBox(height: 8),
button(AppLocalizations.of(context)!.settingsExportChats,
Icons.upload_rounded, () async {
selectionHaptic();
if (path == null) return;
if (desktopFeature()) {
File(path).writeAsString(content);
var name =
"ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json";
var content =
jsonEncode(prefs!.getStringList("chats") ?? []);
if (kIsWeb) {
// web fallback
final bytes = utf8.encode(content);
final blob = html.Blob([bytes]);
final url = html.Url.createObjectUrlFromBlob(blob);
final anchor = html.document.createElement("a")
as html.AnchorElement
..href = url
..style.display = "none"
..download = name;
html.document.body!.children.add(anchor);
anchor.click();
html.document.body!.children.remove(anchor);
html.Url.revokeObjectUrl(url);
} else {
String? path = "";
try {
path = (await file_selector
.getSaveLocation(acceptedTypeGroups: [
const file_selector.XTypeGroup(
label: "Ollama App File",
extensions: ["json"])
], suggestedName: name))
?.path;
} catch (_) {
path = await FilePicker.platform.saveFile(
type: FileType.custom,
allowedExtensions: ["json"],
fileName: name,
bytes: utf8.encode(jsonEncode(
prefs!.getStringList("chats") ?? [])));
}
selectionHaptic();
if (path == null) return;
if (desktopFeature()) {
File(path).writeAsString(content);
}
}
}
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
// ignore: use_build_context_synchronously
content: Text(AppLocalizations.of(context)!
.settingsExportChatsSuccess),
showCloseIcon: true));
}),
allowMultipleChats
? button(
AppLocalizations.of(context)!.settingsImportChats,
Icons.download_rounded, () {
selectionHaptic();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(AppLocalizations.of(context)!
.settingsImportChatsTitle),
content: Text(
AppLocalizations.of(context)!
.settingsImportChatsDescription),
actions: [
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
},
child: Text(AppLocalizations.of(
context)!
.settingsImportChatsCancel)),
TextButton(
onPressed: () async {
selectionHaptic();
String content;
try {
if (kIsWeb) {
throw Exception(
"web must use file picker");
}
file_selector.XFile? result =
await file_selector.openFile(
acceptedTypeGroups: [
const file_selector
.XTypeGroup(
label:
"Ollama App File",
extensions: ["json"])
]);
if (result == null) {
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
return;
}
content =
await result.readAsString();
} catch (_) {
FilePickerResult? result =
await FilePicker.platform
.pickFiles(
type:
FileType.custom,
allowedExtensions: [
"json"
]);
if (result == null) {
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
return;
}
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
// ignore: use_build_context_synchronously
content: Text(AppLocalizations.of(context)!
.settingsExportChatsSuccess),
showCloseIcon: true));
}),
allowMultipleChats
? button(
AppLocalizations.of(context)!.settingsImportChats,
Icons.download_rounded, () {
selectionHaptic();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
surfaceTintColor:
(Theme.of(context).brightness ==
Brightness.dark)
? Colors.grey[800]
: null,
title: Text(
AppLocalizations.of(context)!
.settingsImportChatsTitle),
content: Text(AppLocalizations.of(
context)!
.settingsImportChatsDescription),
actions: [
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
},
child: Text(AppLocalizations.of(
context)!
.settingsImportChatsCancel)),
TextButton(
onPressed: () async {
selectionHaptic();
String content;
try {
File file = File(result
.files.single.path!);
content =
await file.readAsString();
if (kIsWeb) {
throw Exception(
"web must use file picker");
}
file_selector.XFile? result =
await file_selector.openFile(
acceptedTypeGroups: [
const file_selector
.XTypeGroup(
label:
"Ollama App File",
extensions: [
"json"
])
]);
if (result == null) {
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
return;
}
content = await result
.readAsString();
} catch (_) {
// web fallback
content = utf8.decode(result
.files
.single
.bytes as List<int>);
FilePickerResult? result =
await FilePicker.platform
.pickFiles(
type: FileType
.custom,
allowedExtensions: [
"json"
]);
if (result == null) {
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
return;
}
try {
File file = File(result
.files.single.path!);
content = await file
.readAsString();
} catch (_) {
// web fallback
content = utf8.decode(result
.files
.single
.bytes as List<int>);
}
}
}
List<dynamic> tmpHistory =
jsonDecode(content);
List<String> history = [];
List<dynamic> tmpHistory =
jsonDecode(content);
List<String> history = [];
for (var i = 0;
i < tmpHistory.length;
i++) {
history.add(tmpHistory[i]);
}
for (var i = 0;
i < tmpHistory.length;
i++) {
history.add(tmpHistory[i]);
}
prefs!.setStringList(
"chats", history);
prefs!.setStringList(
"chats", history);
messages = [];
chatUuid = null;
messages = [];
chatUuid = null;
setState(() {});
setState(() {});
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text(AppLocalizations
// ignore: use_build_context_synchronously
.of(context)!
.settingsImportChatsSuccess),
showCloseIcon: true));
},
child: Text(
AppLocalizations.of(context)!
.settingsImportChatsImport))
]);
});
})
: const SizedBox.shrink()
]),
),
const SizedBox(height: 8),
button(AppLocalizations.of(context)!.settingsExportInfo,
Icons.info_rounded, null,
color: Colors.grey
.harmonizeWith(Theme.of(context).colorScheme.primary)),
button(AppLocalizations.of(context)!.settingsExportWarning,
Icons.warning_rounded, null,
color: Colors.orange
.harmonizeWith(Theme.of(context).colorScheme.primary))
]))),
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text(AppLocalizations
// ignore: use_build_context_synchronously
.of(context)!
.settingsImportChatsSuccess),
showCloseIcon: true));
},
child: Text(AppLocalizations.of(
context)!
.settingsImportChatsImport))
]);
});
})
: const SizedBox.shrink()
]),
),
const SizedBox(height: 8),
button(AppLocalizations.of(context)!.settingsExportInfo,
Icons.info_rounded, null,
color: Colors.grey.harmonizeWith(
Theme.of(context).colorScheme.primary)),
button(AppLocalizations.of(context)!.settingsExportWarning,
Icons.warning_rounded, null,
color: Colors.orange
.harmonizeWith(Theme.of(context).colorScheme.primary))
])),
)),
);
}
}

View File

@ -36,460 +36,487 @@ class _ScreenSettingsInterfaceState extends State<ScreenSettingsInterface> {
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
]),
actions: desktopControlsActions(context)),
body: Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [
Expanded(
child: ListView(children: [
// const SizedBox(height: 8),
toggle(
context,
AppLocalizations.of(context)!.settingsShowModelTags,
(prefs!.getBool("modelTags") ?? false), (value) {
selectionHaptic();
prefs!.setBool("modelTags", value);
setState(() {});
}),
toggle(
context,
AppLocalizations.of(context)!.settingsPreloadModels,
(prefs!.getBool("preloadModel") ?? true), (value) {
selectionHaptic();
prefs!.setBool("preloadModel", value);
setState(() {});
}),
toggle(
context,
AppLocalizations.of(context)!
.settingsResetOnModelChange,
(prefs!.getBool("resetOnModelSelect") ?? true),
(value) {
selectionHaptic();
prefs!.setBool("resetOnModelSelect", value);
setState(() {});
}),
titleDivider(
bottom: desktopLayoutNotRequired(context) ? 38 : 20,
context: context),
SegmentedButton(
segments: [
ButtonSegment(
value: "stream",
label: Text(AppLocalizations.of(context)!
.settingsRequestTypeStream),
icon: const Icon(Icons.stream_rounded)),
ButtonSegment(
value: "request",
label: Text(AppLocalizations.of(context)!
.settingsRequestTypeRequest),
icon: const Icon(Icons.send_rounded))
],
selected: {
prefs!.getString("requestType") ?? "stream"
},
onSelectionChanged: (p0) {
selectionHaptic();
setState(() {
prefs!.setString("requestType", p0.elementAt(0));
});
}),
const SizedBox(height: 16),
toggle(
context,
AppLocalizations.of(context)!.settingsGenerateTitles,
(prefs!.getBool("generateTitles") ?? true), (value) {
selectionHaptic();
prefs!.setBool("generateTitles", value);
setState(() {});
}),
toggle(
context,
AppLocalizations.of(context)!.settingsEnableEditing,
(prefs!.getBool("enableEditing") ?? true), (value) {
selectionHaptic();
prefs!.setBool("enableEditing", value);
setState(() {});
}),
toggle(
context,
AppLocalizations.of(context)!.settingsAskBeforeDelete,
(prefs!.getBool("askBeforeDeletion") ?? false),
(value) {
selectionHaptic();
prefs!.setBool("askBeforeDeletion", value);
setState(() {});
}),
toggle(
context,
AppLocalizations.of(context)!.settingsShowTips,
(prefs!.getBool("tips") ?? true), (value) {
selectionHaptic();
prefs!.setBool("tips", value);
setState(() {});
}),
titleDivider(context: context),
toggle(
context,
AppLocalizations.of(context)!
.settingsKeepModelLoadedAlways,
int.parse(prefs!.getString("keepAlive") ?? "300") == -1,
(value) {
selectionHaptic();
setState(() {
if (value) {
prefs!.setString("keepAlive", "-1");
} else {
prefs!.setString("keepAlive", "300");
}
});
}),
toggle(
context,
AppLocalizations.of(context)!
.settingsKeepModelLoadedNever,
int.parse(prefs!.getString("keepAlive") ?? "300") == 0,
(value) {
selectionHaptic();
setState(() {
if (value) {
prefs!.setString("keepAlive", "0");
} else {
prefs!.setString("keepAlive", "300");
}
});
}),
button(
(int.parse(prefs!.getString("keepAlive") ?? "300") > 0)
? AppLocalizations.of(context)!
.settingsKeepModelLoadedSet((int.parse(
prefs!.getString("keepAlive") ??
"300") ~/
60)
.toString())
: AppLocalizations.of(context)!
.settingsKeepModelLoadedFor,
Icons.snooze_rounded, () async {
selectionHaptic();
bool loaded = false;
await showDialog(
context: context,
builder: (context) {
return Dialog(
alignment: desktopLayout(context)
? null
: Alignment.bottomRight,
child: StatefulBuilder(
builder: (context, setLocalState) {
if (int.parse(prefs!.getString("keepAlive") ??
"0") <=
0 &&
loaded == false) {
prefs!.setString("keepAlive", "0");
WidgetsBinding.instance
.addPostFrameCallback((timeStamp) {
setLocalState(() {});
void load() async {
try {
while (int.parse(prefs!
.getString("keepAlive")!) <
300) {
await Future.delayed(const Duration(
milliseconds: 5));
prefs!.setString(
"keepAlive",
(int.parse(prefs!.getString(
"keepAlive")!) +
30)
.toString());
setLocalState(() {});
setState(() {});
}
prefs!.setString("keepAlive", "300");
loaded = true;
} catch (_) {
prefs!.setString("keepAlive", "300");
loaded = true;
}
}
load();
});
} else {
loaded = true;
}
return Padding(
padding: const EdgeInsets.all(16),
child: Theme(
data: (prefs?.getBool("useDeviceTheme") ??
false)
? Theme.of(context)
: ThemeData.from(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.black)),
child: DurationPicker(
duration: Duration(
seconds: int.parse(prefs!
.getString("keepAlive") ??
"300")),
baseUnit: BaseUnit.minute,
lowerBound:
const Duration(minutes: 1),
upperBound:
const Duration(minutes: 60),
onChange: (value) {
if (!loaded) return;
if (value.inSeconds == 0) return;
prefs!.setString("keepAlive",
value.inSeconds.toString());
setLocalState(() {});
setState(() {});
}),
),
);
}));
});
}),
titleDivider(context: context),
toggle(
context,
AppLocalizations.of(context)!
.settingsEnableHapticFeedback,
(prefs!.getBool("enableHaptic") ?? true), (value) {
prefs!.setBool("enableHaptic", value);
selectionHaptic();
setState(() {});
}),
desktopFeature()
? toggle(
context,
AppLocalizations.of(context)!
.settingsMaximizeOnStart,
(prefs!.getBool("maximizeOnStart") ?? false),
(value) {
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 1000),
padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [
Expanded(
child: ListView(children: [
// const SizedBox(height: 8),
toggle(
context,
AppLocalizations.of(context)!.settingsShowModelTags,
(prefs!.getBool("modelTags") ?? false), (value) {
selectionHaptic();
prefs!.setBool("modelTags", value);
setState(() {});
}),
toggle(
context,
AppLocalizations.of(context)!.settingsPreloadModels,
(prefs!.getBool("preloadModel") ?? true), (value) {
selectionHaptic();
prefs!.setBool("preloadModel", value);
setState(() {});
}),
toggle(
context,
AppLocalizations.of(context)!
.settingsResetOnModelChange,
(prefs!.getBool("resetOnModelSelect") ?? true),
(value) {
selectionHaptic();
prefs!.setBool("resetOnModelSelect", value);
setState(() {});
}),
titleDivider(
bottom: desktopLayoutNotRequired(context) ? 38 : 20,
context: context),
SegmentedButton(
segments: [
ButtonSegment(
value: "stream",
label: Text(AppLocalizations.of(context)!
.settingsRequestTypeStream),
icon: const Icon(Icons.stream_rounded)),
ButtonSegment(
value: "request",
label: Text(AppLocalizations.of(context)!
.settingsRequestTypeRequest),
icon: const Icon(Icons.send_rounded))
],
selected: {
prefs!.getString("requestType") ?? "stream"
},
onSelectionChanged: (p0) {
selectionHaptic();
prefs!.setBool("maximizeOnStart", value);
setState(() {});
})
: const SizedBox.shrink(),
const SizedBox(height: 8),
SegmentedButton(
segments: [
ButtonSegment(
value: "dark",
label: Text(AppLocalizations.of(context)!
.settingsBrightnessDark),
icon: const Icon(Icons.brightness_4_rounded)),
ButtonSegment(
value: "system",
label: Text(AppLocalizations.of(context)!
.settingsBrightnessSystem),
icon: const Icon(Icons.brightness_auto_rounded)),
ButtonSegment(
value: "light",
label: Text(AppLocalizations.of(context)!
.settingsBrightnessLight),
icon: const Icon(Icons.brightness_high_rounded))
],
selected: {
prefs!.getString("brightness") ?? "system"
},
onSelectionChanged: (p0) {
selectionHaptic();
var tmp = prefs!.getString("brightness") ?? "system";
prefs!.setString("brightness", p0.elementAt(0));
setState(() {});
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setLocalState) {
return PopScope(
onPopInvoked: (didPop) {
prefs!.setString("brightness", tmp);
setState(() {});
},
child: AlertDialog(
title: Text(AppLocalizations.of(
context)!
.settingsBrightnessRestartTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(AppLocalizations.of(
context)!
.settingsBrightnessRestartDescription),
]),
actions: [
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
},
child: Text(AppLocalizations.of(
context)!
.settingsBrightnessRestartCancel)),
TextButton(
onPressed: () async {
selectionHaptic();
await prefs!.setString(
"brightness",
p0.elementAt(0));
if (desktopFeature()) {
exit(0);
} else {
Restart.restartApp();
}
},
child: Text(AppLocalizations.of(
context)!
.settingsBrightnessRestartRestart))
]));
});
});
}),
const SizedBox(height: 8),
!kIsWeb
? SegmentedButton(
segments: [
ButtonSegment(
value: "device",
label: Text(AppLocalizations.of(context)!
.settingsThemeDevice),
icon: const Icon(Icons.devices_rounded)),
ButtonSegment(
value: "ollama",
label: Text(AppLocalizations.of(context)!
.settingsThemeOllama),
icon: const ImageIcon(
AssetImage("assets/logo512.png")))
],
selected: {
(prefs?.getBool("useDeviceTheme") ?? false)
? "device"
: "ollama"
},
onSelectionChanged: (p0) {
selectionHaptic();
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setLocalState) {
return AlertDialog(
title: Text(
AppLocalizations.of(context)!
.settingsThemeRestartTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(AppLocalizations.of(
context)!
.settingsThemeRestartDescription),
]),
actions: [
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
},
child: Text(AppLocalizations.of(
context)!
.settingsThemeRestartCancel)),
TextButton(
onPressed: () async {
selectionHaptic();
await prefs!.setBool(
"useDeviceTheme",
p0.elementAt(0) ==
"device");
if (desktopFeature()) {
exit(0);
} else {
Restart.restartApp();
}
},
child: Text(AppLocalizations.of(
context)!
.settingsThemeRestartRestart))
]);
});
});
})
: const SizedBox.shrink(),
titleDivider(),
button(AppLocalizations.of(context)!.settingsTemporaryFixes,
Icons.fast_forward_rounded, () {
selectionHaptic();
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return Container(
width: double.infinity,
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 16,
bottom: desktopLayout(context) ? 16 : 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
button(
AppLocalizations.of(context)!
.settingsTemporaryFixesDescription,
Icons.info_rounded,
null,
color: Colors.grey.harmonizeWith(
Theme.of(context)
.colorScheme
.primary)),
button(
AppLocalizations.of(context)!
.settingsTemporaryFixesInstructions,
Icons.warning_rounded,
null,
color: Colors.orange.harmonizeWith(
Theme.of(context)
.colorScheme
.primary)),
titleDivider(),
// Text(
// AppLocalizations.of(context)!
// .settingsTemporaryFixesNoFixes,
// style: const TextStyle(
// color: Colors.grey)),
toggle(
context,
"Fixing code block not scrollable",
(prefs!.getBool(
"fixCodeblockScroll") ??
false), (value) {
selectionHaptic();
prefs!.setBool(
"fixCodeblockScroll", value);
if ((prefs!.getBool(
"fixCodeblockScroll") ??
false) ==
false) {
prefs!.remove("fixCodeblockScroll");
}
setState(() {});
}, onLongTap: () {
selectionHaptic();
launchUrl(Uri.parse(
"https://github.com/JHubi1/ollama-app/issues/26"));
}),
const SizedBox(height: 16)
]),
);
setState(() {
prefs!.setString("requestType", p0.elementAt(0));
});
});
}),
const SizedBox(height: 16)
]),
)
]))),
}),
const SizedBox(height: 16),
toggle(
context,
AppLocalizations.of(context)!.settingsGenerateTitles,
(prefs!.getBool("generateTitles") ?? true), (value) {
selectionHaptic();
prefs!.setBool("generateTitles", value);
setState(() {});
}),
toggle(
context,
AppLocalizations.of(context)!.settingsEnableEditing,
(prefs!.getBool("enableEditing") ?? true), (value) {
selectionHaptic();
prefs!.setBool("enableEditing", value);
setState(() {});
}),
toggle(
context,
AppLocalizations.of(context)!.settingsAskBeforeDelete,
(prefs!.getBool("askBeforeDeletion") ?? false),
(value) {
selectionHaptic();
prefs!.setBool("askBeforeDeletion", value);
setState(() {});
}),
toggle(
context,
AppLocalizations.of(context)!.settingsShowTips,
(prefs!.getBool("tips") ?? true), (value) {
selectionHaptic();
prefs!.setBool("tips", value);
setState(() {});
}),
titleDivider(context: context),
toggle(
context,
AppLocalizations.of(context)!
.settingsKeepModelLoadedAlways,
int.parse(prefs!.getString("keepAlive") ?? "300") ==
-1, (value) {
selectionHaptic();
setState(() {
if (value) {
prefs!.setString("keepAlive", "-1");
} else {
prefs!.setString("keepAlive", "300");
}
});
}),
toggle(
context,
AppLocalizations.of(context)!
.settingsKeepModelLoadedNever,
int.parse(prefs!.getString("keepAlive") ?? "300") ==
0, (value) {
selectionHaptic();
setState(() {
if (value) {
prefs!.setString("keepAlive", "0");
} else {
prefs!.setString("keepAlive", "300");
}
});
}),
button(
(int.parse(prefs!.getString("keepAlive") ?? "300") >
0)
? AppLocalizations.of(context)!
.settingsKeepModelLoadedSet((int.parse(
prefs!.getString("keepAlive") ??
"300") ~/
60)
.toString())
: AppLocalizations.of(context)!
.settingsKeepModelLoadedFor,
Icons.snooze_rounded, () async {
selectionHaptic();
bool loaded = false;
await showDialog(
context: context,
builder: (context) {
return Dialog(
surfaceTintColor: (Theme.of(context).brightness == Brightness.dark) ? Colors.grey[800] : null,
alignment: desktopLayout(context)
? null
: Alignment.bottomRight,
child: StatefulBuilder(
builder: (context, setLocalState) {
if (int.parse(
prefs!.getString("keepAlive") ??
"0") <=
0 &&
loaded == false) {
prefs!.setString("keepAlive", "0");
WidgetsBinding.instance
.addPostFrameCallback((timeStamp) {
setLocalState(() {});
void load() async {
try {
while (int.parse(prefs!
.getString("keepAlive")!) <
300) {
await Future.delayed(
const Duration(
milliseconds: 5));
prefs!.setString(
"keepAlive",
(int.parse(prefs!.getString(
"keepAlive")!) +
30)
.toString());
setLocalState(() {});
setState(() {});
}
prefs!
.setString("keepAlive", "300");
loaded = true;
} catch (_) {
prefs!
.setString("keepAlive", "300");
loaded = true;
}
}
load();
});
} else {
loaded = true;
}
return Padding(
padding: const EdgeInsets.all(16),
child: Theme(
data:
(prefs?.getBool("useDeviceTheme") ??
false)
? Theme.of(context)
: ThemeData.from(
colorScheme:
ColorScheme.fromSeed(
seedColor:
Colors.black)),
child: DurationPicker(
duration: Duration(
seconds: int.parse(prefs!
.getString(
"keepAlive") ??
"300")),
baseUnit: BaseUnit.minute,
lowerBound:
const Duration(minutes: 1),
upperBound:
const Duration(minutes: 60),
onChange: (value) {
if (!loaded) return;
if (value.inSeconds == 0) return;
prefs!.setString("keepAlive",
value.inSeconds.toString());
setLocalState(() {});
setState(() {});
}),
),
);
}));
});
}),
titleDivider(context: context),
toggle(
context,
AppLocalizations.of(context)!
.settingsEnableHapticFeedback,
(prefs!.getBool("enableHaptic") ?? true), (value) {
prefs!.setBool("enableHaptic", value);
selectionHaptic();
setState(() {});
}),
desktopFeature()
? toggle(
context,
AppLocalizations.of(context)!
.settingsMaximizeOnStart,
(prefs!.getBool("maximizeOnStart") ?? false),
(value) {
selectionHaptic();
prefs!.setBool("maximizeOnStart", value);
setState(() {});
})
: const SizedBox.shrink(),
const SizedBox(height: 8),
SegmentedButton(
segments: [
ButtonSegment(
value: "dark",
label: Text(AppLocalizations.of(context)!
.settingsBrightnessDark),
icon: const Icon(Icons.brightness_4_rounded)),
ButtonSegment(
value: "system",
label: Text(AppLocalizations.of(context)!
.settingsBrightnessSystem),
icon:
const Icon(Icons.brightness_auto_rounded)),
ButtonSegment(
value: "light",
label: Text(AppLocalizations.of(context)!
.settingsBrightnessLight),
icon: const Icon(Icons.brightness_high_rounded))
],
selected: {
prefs!.getString("brightness") ?? "system"
},
onSelectionChanged: (p0) {
selectionHaptic();
var tmp =
prefs!.getString("brightness") ?? "system";
prefs!.setString("brightness", p0.elementAt(0));
setState(() {});
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setLocalState) {
return PopScope(
onPopInvoked: (didPop) {
prefs!.setString("brightness", tmp);
setState(() {});
},
child: AlertDialog(
surfaceTintColor: (Theme.of(context).brightness == Brightness.dark) ? Colors.grey[800] : null,
title: Text(AppLocalizations.of(
context)!
.settingsBrightnessRestartTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(AppLocalizations.of(
context)!
.settingsBrightnessRestartDescription),
]),
actions: [
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
},
child: Text(AppLocalizations
.of(context)!
.settingsBrightnessRestartCancel)),
TextButton(
onPressed: () async {
selectionHaptic();
await prefs!.setString(
"brightness",
p0.elementAt(0));
if (desktopFeature()) {
exit(0);
} else {
Restart.restartApp();
}
},
child: Text(AppLocalizations
.of(context)!
.settingsBrightnessRestartRestart))
]));
});
});
}),
const SizedBox(height: 8),
!kIsWeb
? SegmentedButton(
segments: [
ButtonSegment(
value: "device",
label: Text(AppLocalizations.of(context)!
.settingsThemeDevice),
icon: const Icon(Icons.devices_rounded)),
ButtonSegment(
value: "ollama",
label: Text(AppLocalizations.of(context)!
.settingsThemeOllama),
icon: const ImageIcon(
AssetImage("assets/logo512.png")))
],
selected: {
(prefs?.getBool("useDeviceTheme") ?? false)
? "device"
: "ollama"
},
onSelectionChanged: (p0) {
selectionHaptic();
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setLocalState) {
return AlertDialog(
surfaceTintColor: (Theme.of(context).brightness == Brightness.dark) ? Colors.grey[800] : null,
title: Text(
AppLocalizations.of(context)!
.settingsThemeRestartTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(AppLocalizations.of(
context)!
.settingsThemeRestartDescription),
]),
actions: [
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
},
child: Text(AppLocalizations
.of(context)!
.settingsThemeRestartCancel)),
TextButton(
onPressed: () async {
selectionHaptic();
await prefs!.setBool(
"useDeviceTheme",
p0.elementAt(0) ==
"device");
if (desktopFeature()) {
exit(0);
} else {
Restart.restartApp();
}
},
child: Text(AppLocalizations
.of(context)!
.settingsThemeRestartRestart))
]);
});
});
})
: const SizedBox.shrink(),
titleDivider(),
button(
AppLocalizations.of(context)!.settingsTemporaryFixes,
Icons.fast_forward_rounded, () {
selectionHaptic();
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return Container(
width: double.infinity,
decoration: (Theme.of(context).brightness ==
Brightness.dark)
? BoxDecoration(
border:
Border.all(color: Colors.white),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(26),
topRight: Radius.circular(26)))
: null,
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 16,
bottom: desktopLayout(context) ? 16 : 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
button(
AppLocalizations.of(context)!
.settingsTemporaryFixesDescription,
Icons.info_rounded,
null,
color: Colors.grey.harmonizeWith(
Theme.of(context)
.colorScheme
.primary)),
button(
AppLocalizations.of(context)!
.settingsTemporaryFixesInstructions,
Icons.warning_rounded,
null,
color: Colors.orange.harmonizeWith(
Theme.of(context)
.colorScheme
.primary)),
titleDivider(),
// Text(
// AppLocalizations.of(context)!
// .settingsTemporaryFixesNoFixes,
// style: const TextStyle(
// color: Colors.grey)),
toggle(
context,
"Fixing code block not scrollable",
(prefs!.getBool(
"fixCodeblockScroll") ??
false), (value) {
selectionHaptic();
prefs!.setBool(
"fixCodeblockScroll", value);
if ((prefs!.getBool(
"fixCodeblockScroll") ??
false) ==
false) {
prefs!.remove("fixCodeblockScroll");
}
setState(() {});
}, onLongTap: () {
selectionHaptic();
launchUrl(Uri.parse(
"https://github.com/JHubi1/ollama-app/issues/26"));
}),
const SizedBox(height: 16)
]),
);
});
});
}),
const SizedBox(height: 16)
]),
)
])),
)),
);
}
}

View File

@ -216,6 +216,21 @@ class _ScreenSettingsVoiceState extends State<ScreenSettingsVoice> {
});
},
child: Container(
decoration: (Theme.of(context)
.brightness ==
Brightness.dark)
? BoxDecoration(
border: Border.all(
color: Colors.white),
borderRadius:
const BorderRadius.only(
topLeft:
Radius.circular(
26),
topRight:
Radius.circular(
26)))
: null,
width: double.infinity,
padding: const EdgeInsets.only(
left: 16,

View File

@ -80,7 +80,6 @@ List getHistoryString([String? uuid]) {
}
Future<String> getTitleAi(List history) async {
print(history);
final generated = await (llama.OllamaClient(
headers: (jsonDecode(prefs!.getString("hostHeaders") ?? "{}") as Map)
.cast<String, String>(),
@ -92,7 +91,7 @@ Future<String> getTitleAi(List history) async {
const llama.Message(
role: llama.MessageRole.system,
content:
"Generate a three to six word title for the conversation provided by the user. If an object or person is very important in the conversation, put it in the title as well; keep the focus on the main subject. You must not put the assistant in the focus and you must not put the word 'assistant' in the title! Do preferably use title case. Use a formal tone, don't use dramatic words, like 'mystery' Use spaces between words, do not use camel case! You must not use markdown or any other formatting language! You must not use emojis or any other symbols! You must not use general clauses like 'assistance', 'help' or 'session' in your title! \n\n~~User Introduces Themselves~~ -> User Introduction\n~~User Asks for Help with a Problem~~ -> Problem Help\n~~User has a _**big**_ Problem~~ -> Big Problem\n~~Conversation~~ -> Conversation about Fireflies"),
"Generate a three to six word title for the conversation provided by the user. If an object or person is very important in the conversation, put it in the title as well; keep the focus on the main subject. You must not put the assistant in the focus and you must not put the word 'assistant' in the title! Do preferably use title case. Use a formal tone, don't use dramatic words, like 'mystery' Use spaces between words, do not use camel case! You must not use markdown or any other formatting language! You must not use emojis or any other symbols! You must not use general clauses like 'assistance', 'help' or 'session' in your title! \n\n~~User Introduces Themselves~~ -> User Introduction\n~~User Asks for Help with a Problem~~ -> Problem Help\n~~User has a _**big**_ Problem~~ -> Big Problem"),
llama.Message(
role: llama.MessageRole.user,
content: "```\n${jsonEncode(history)}\n```")

View File

@ -278,6 +278,10 @@ void setModel(BuildContext context, Function setState) {
? const Offset(289, 0)
: const Offset(0, 0),
child: Dialog(
surfaceTintColor:
(Theme.of(context).brightness == Brightness.dark)
? Colors.grey[800]
: null,
alignment: desktopLayoutRequired(context)
? Alignment.topLeft
: Alignment.topCenter,
@ -285,7 +289,17 @@ void setModel(BuildContext context, Function setState) {
);
});
} else {
showModalBottomSheet(context: context, builder: (context) => content);
showModalBottomSheet(
context: context,
builder: (context) => Container(
decoration: (Theme.of(context).brightness == Brightness.dark)
? BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(26),
topRight: Radius.circular(26)))
: null,
child: content));
}
}
@ -406,6 +420,79 @@ void loadChat(String uuid, Function setState) {
setState(() {});
}
Future<bool> deleteChatDialog(BuildContext context, Function setState,
{bool takeAction = true,
bool? additionalCondition,
String? uuid,
bool popSidebar = false}) async {
additionalCondition ??= true;
uuid ??= chatUuid;
bool returnValue = false;
void delete() {
returnValue = true;
if (takeAction) {
for (var i = 0; i < (prefs!.getStringList("chats") ?? []).length; i++) {
if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])["uuid"] ==
uuid) {
List<String> tmp = prefs!.getStringList("chats")!;
tmp.removeAt(i);
prefs!.setStringList("chats", tmp);
break;
}
}
if (chatUuid == uuid) {
messages = [];
chatUuid = null;
if (!desktopLayoutRequired(context) &&
Navigator.of(context).canPop() &&
popSidebar) {
Navigator.of(context).pop();
}
}
}
}
if ((prefs!.getBool("askBeforeDeletion") ?? false) && additionalCondition) {
await showDialog(
context: context,
builder: (context) {
return StatefulBuilder(builder: (context, setLocalState) {
return AlertDialog(
surfaceTintColor:
(Theme.of(context).brightness == Brightness.dark)
? Colors.grey[800]
: null,
title: Text(AppLocalizations.of(context)!.deleteDialogTitle),
content: Column(mainAxisSize: MainAxisSize.min, children: [
Text(AppLocalizations.of(context)!.deleteDialogDescription),
]),
actions: [
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.deleteDialogCancel)),
TextButton(
onPressed: () {
selectionHaptic();
Navigator.of(context).pop();
delete();
},
child: Text(
AppLocalizations.of(context)!.deleteDialogDelete))
]);
});
});
} else {
delete();
}
setState(() {});
return returnValue;
}
Future<String> prompt(BuildContext context,
{String description = "",
String value = "",
@ -431,6 +518,13 @@ Future<String> prompt(BuildContext context,
return StatefulBuilder(builder: (context, setLocalState) {
return PopScope(
child: Container(
decoration: (Theme.of(context).brightness == Brightness.dark)
? BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(26),
topRight: Radius.circular(26)))
: null,
padding: EdgeInsets.only(
left: 16,
right: 16,

View File

@ -125,6 +125,7 @@ void updateDialog(BuildContext context, Function title) {
context: context,
builder: (context) {
return AlertDialog(
surfaceTintColor: (Theme.of(context).brightness == Brightness.dark) ? Colors.grey[800] : null,
title:
Text(AppLocalizations.of(context)!.settingsUpdateDialogTitle),
content: Column(mainAxisSize: MainAxisSize.min, children: [
@ -133,9 +134,12 @@ void updateDialog(BuildContext context, Function title) {
title(AppLocalizations.of(context)!.settingsUpdateChangeLog),
Flexible(
child: SingleChildScrollView(
child: MarkdownBody(
data: updateChangeLog ?? "No changelog given.",
shrinkWrap: true)))
child: Container(
constraints: const BoxConstraints(maxWidth: 1000),
child: MarkdownBody(
data: updateChangeLog ?? "No changelog given.",
shrinkWrap: true),
)))
]),
actions: [
TextButton(