Added (very!) experimental web support
This commit is contained in:
parent
4667f7a69b
commit
6a5a123026
21
.metadata
21
.metadata
|
@ -4,7 +4,7 @@
|
|||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "a14f74ff3a1cbd521163c5f03d68113d50af93d3"
|
||||
revision: "b0850beeb25f6d5b10426284f506557f66181b36"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
@ -13,20 +13,17 @@ project_type: app
|
|||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
||||
base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
||||
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
- platform: android
|
||||
create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
||||
base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
||||
- platform: linux
|
||||
create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
||||
base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
||||
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
- platform: web
|
||||
create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
||||
base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
||||
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
- platform: windows
|
||||
create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
||||
base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
||||
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
|
||||
# User provided section
|
||||
|
||||
|
|
|
@ -1060,7 +1060,35 @@ class _MainAppState extends State<MainApp> {
|
|||
child: SizedBox(
|
||||
height: 200, child: MoveWindow()))
|
||||
]
|
||||
: [Expanded(child: selector)]),
|
||||
: desktopLayoutRequired(context)
|
||||
? [
|
||||
// bottom left tile
|
||||
const SizedBox(width: 304, height: 200),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: AnimatedOpacity(
|
||||
opacity: menuVisible ? 1.0 : 0.0,
|
||||
duration:
|
||||
const Duration(milliseconds: 300),
|
||||
child: VerticalDivider(
|
||||
width: 2,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withAlpha(20)))),
|
||||
AnimatedOpacity(
|
||||
opacity: desktopTitleVisible ? 1.0 : 0.0,
|
||||
duration: desktopTitleVisible
|
||||
? const Duration(milliseconds: 300)
|
||||
: const Duration(milliseconds: 0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: selector,
|
||||
),
|
||||
),
|
||||
const Expanded(child: SizedBox(height: 200))
|
||||
]
|
||||
: [Expanded(child: selector)]),
|
||||
actions: desktopControlsActions(context, [
|
||||
const SizedBox(width: 4),
|
||||
allowMultipleChats
|
||||
|
|
|
@ -520,7 +520,7 @@ class _ScreenSettingsState extends State<ScreenSettings> {
|
|||
context: context,
|
||||
description:
|
||||
"\n${AppLocalizations.of(context)!.settingsDescriptionInterface}"),
|
||||
(!desktopFeature())
|
||||
(!desktopFeature(web: true))
|
||||
? button(
|
||||
AppLocalizations.of(context)!
|
||||
.settingsTitleVoice,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
// ignore: avoid_web_libraries_in_flutter
|
||||
import 'dart:html' as html;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../main.dart';
|
||||
import '../worker/haptic.dart';
|
||||
|
@ -42,18 +45,37 @@ class _ScreenSettingsExportState extends State<ScreenSettingsExport> {
|
|||
button(AppLocalizations.of(context)!.settingsExportChats,
|
||||
Icons.upload_rounded, () async {
|
||||
selectionHaptic();
|
||||
var path = await FilePicker.platform.saveFile(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["json"],
|
||||
fileName:
|
||||
"ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json",
|
||||
bytes: utf8.encode(
|
||||
jsonEncode(prefs!.getStringList("chats") ?? [])));
|
||||
selectionHaptic();
|
||||
if (path == null) return;
|
||||
if (desktopFeature()) {
|
||||
File(path).writeAsString(
|
||||
jsonEncode(prefs!.getStringList("chats") ?? []));
|
||||
var name =
|
||||
"ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json";
|
||||
var content =
|
||||
jsonEncode(prefs!.getStringList("chats") ?? []);
|
||||
if (kIsWeb) {
|
||||
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 {
|
||||
var 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);
|
||||
}
|
||||
}
|
||||
}),
|
||||
allowMultipleChats
|
||||
|
@ -95,10 +117,18 @@ class _ScreenSettingsExportState extends State<ScreenSettingsExport> {
|
|||
return;
|
||||
}
|
||||
|
||||
File file = File(
|
||||
result.files.single.path!);
|
||||
var content =
|
||||
await file.readAsString();
|
||||
String content;
|
||||
try {
|
||||
File file = File(
|
||||
result.files.single.path!);
|
||||
content =
|
||||
await file.readAsString();
|
||||
} catch (_) {
|
||||
content = utf8.decode(result
|
||||
.files
|
||||
.single
|
||||
.bytes as List<int>);
|
||||
}
|
||||
List<dynamic> tmpHistory =
|
||||
jsonDecode(content);
|
||||
List<String> history = [];
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../main.dart';
|
||||
import '../worker/haptic.dart';
|
||||
|
@ -342,69 +343,74 @@ class _ScreenSettingsInterfaceState extends State<ScreenSettingsInterface> {
|
|||
});
|
||||
}),
|
||||
const SizedBox(height: 8),
|
||||
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))
|
||||
]);
|
||||
});
|
||||
});
|
||||
}),
|
||||
!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(),
|
||||
toggle(context, "Fix to code block not scrollable",
|
||||
(prefs!.getBool("fixCodeblockScroll") ?? false),
|
||||
|
|
|
@ -6,26 +6,30 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
|
||||
bool desktopFeature({bool web = false}) {
|
||||
return (Platform.isWindows ||
|
||||
Platform.isLinux ||
|
||||
Platform.isMacOS ||
|
||||
(web ? kIsWeb : false));
|
||||
try {
|
||||
return (Platform.isWindows ||
|
||||
Platform.isLinux ||
|
||||
Platform.isMacOS ||
|
||||
(web ? kIsWeb : false));
|
||||
} catch (_) {
|
||||
return web ? kIsWeb : false;
|
||||
}
|
||||
}
|
||||
|
||||
bool desktopLayout(BuildContext context,
|
||||
{bool web = false, double? value, double valueCap = 1000}) {
|
||||
{bool web = true, double? value, double valueCap = 1000}) {
|
||||
value ??= MediaQuery.of(context).size.width;
|
||||
return (desktopFeature(web: web) || value >= valueCap);
|
||||
}
|
||||
|
||||
bool desktopLayoutRequired(BuildContext context,
|
||||
{bool web = false, double? value, double valueCap = 1000}) {
|
||||
{bool web = true, double? value, double valueCap = 1000}) {
|
||||
value ??= MediaQuery.of(context).size.width;
|
||||
return (desktopFeature(web: web) && value >= valueCap);
|
||||
}
|
||||
|
||||
bool desktopLayoutNotRequired(BuildContext context,
|
||||
{bool web = false, double? value, double valueCap = 1000}) {
|
||||
{bool web = true, double? value, double valueCap = 1000}) {
|
||||
value ??= MediaQuery.of(context).size.width;
|
||||
return (value >= valueCap);
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ Future<bool> updatesSupported(Function setState,
|
|||
"com.machiav3lli.fdroid",
|
||||
"nya.kitsunyan.foxydroid"
|
||||
];
|
||||
if (!desktopFeature()) {
|
||||
if (!desktopFeature(web: true)) {
|
||||
if ((await InstallReferrer.referrer !=
|
||||
InstallationAppReferrer.androidManually) ||
|
||||
(installerApps
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
],
|
||||
|
||||
"it": [
|
||||
"deleteChat",
|
||||
"renameChat",
|
||||
"settingsDescriptionBehavior",
|
||||
"settingsDescriptionInterface",
|
||||
"settingsDescriptionVoice",
|
||||
|
@ -30,6 +32,8 @@
|
|||
],
|
||||
|
||||
"tr": [
|
||||
"deleteChat",
|
||||
"renameChat",
|
||||
"settingsDescriptionBehavior",
|
||||
"settingsDescriptionInterface",
|
||||
"settingsDescriptionVoice",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
|
@ -0,0 +1,101 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- <base href="$FLUTTER_BASE_HREF"> -->
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="A modern and easy-to-use client for Ollama">
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="ollama_app">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
|
||||
<title>Ollama App</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<style>
|
||||
body {
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 28px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background: black;
|
||||
transform-origin: top;
|
||||
display: grid;
|
||||
animation: l3-0 1s infinite linear;
|
||||
}
|
||||
|
||||
.loader::before,
|
||||
.loader::after {
|
||||
content: "";
|
||||
grid-area: 1/1;
|
||||
background: lightgrey;
|
||||
border-radius: 50%;
|
||||
transform-origin: top;
|
||||
animation: inherit;
|
||||
animation-name: l3-1;
|
||||
}
|
||||
|
||||
.loader::after {
|
||||
background: black;
|
||||
--s: 180deg;
|
||||
}
|
||||
|
||||
@keyframes l3-0 {
|
||||
|
||||
0%,
|
||||
20% {
|
||||
transform: rotate(0)
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes l3-1 {
|
||||
50% {
|
||||
transform: rotate(var(--s, 90deg))
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(0)
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.loader {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.loader::after {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="loader"></div>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "Ollama App",
|
||||
"short_name": "Ollama",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#000000",
|
||||
"theme_color": "#000000",
|
||||
"description": "A modern and easy-to-use client for Ollama",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/Icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-maskable-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-maskable-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue