Compare commits

..

10 Commits

Author SHA1 Message Date
5947edec45 Rebrand to iiEasy: naming, logo, l10n, docs, assets
Some checks failed
L10n / l10n (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-19 23:21:16 +05:00
cogwheel
cd536ff8f2 feat(ios): Add AppGroupId for app and share extension 2026-02-12 11:39:45 +08:00
cogwheel
d3e04dbd46 Merge pull request #369 from cogwheel0/fix-voice-call-freezes
fix(voice-call): Improve async handling and state management
2026-02-11 09:21:47 +08:00
cogwheel
1d1dd43c96 Merge pull request #368 from cogwheel0/improve-network-readiness-auth
fix(auth): Improve network readiness handling on cold start
2026-02-11 09:21:28 +08:00
cogwheel
601defa3ee fix(auth): Improve network readiness handling on cold start
Add robust network readiness gate for authentication to prevent
race conditions with Cloudflare tunnels. Implement retry mechanism
for API connectivity checks and silent login attempts when
credentials are available. Enhance error handling and logging for
network-related authentication challenges.
2026-02-05 19:26:16 +05:30
cogwheel
dc2495dca0 fix(voice-call): Improve async handling and state management
Refactor voice call service to handle asynchronous operations more
precisely. Update method signatures to be async, use unawaited for
non-blocking calls, and ensure proper state reset between sessions.
Improve error handling and resource management for voice input and
text-to-speech services.
2026-02-05 17:53:09 +05:30
cogwheel
c75898546a Merge pull request #364 from cogwheel0/fix-settings-null-return
feat(api): Handle null user settings response gracefully
2026-02-05 16:01:31 +08:00
cogwheel
d8c2bdf404 feat(api): Handle null user settings response gracefully
Improve error handling for user settings API call
Prevent potential null or unexpected response types
Return an empty map for new users without settings
2026-02-05 13:30:33 +05:30
cogwheel
252d550c07 Merge pull request #363 from cogwheel0/fix-model-selection
feat(api): Improve default model selection with fallback mechanism
2026-02-05 15:50:50 +08:00
cogwheel
4b8e0752e5 feat(api): Improve default model selection with fallback mechanism 2026-02-05 13:19:59 +05:30
124 changed files with 1405 additions and 587 deletions

View File

@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "20f82749394e68bcfbbeee96bad384abaae09c13"
revision: "582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536"
channel: "stable"
project_type: app
@@ -13,11 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
- platform: macos
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
- platform: linux
create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
# User provided section

View File

@@ -1,8 +1,8 @@
# Conduit Privacy Policy
# iiEasy Privacy Policy
Effective date: 2025-08-09
Conduit is an opensource mobile client for OpenWebUI. This app acts as a client to a server you choose and configure. This policy describes how the app itself handles data on your device. Your configured server may collect, process, and store data under its own policies; please review your server's privacy terms separately.
iiEasy is a mobile client for OpenWebUI (based on the open-source Conduit project). This app acts as a client to a server you choose and configure. This policy describes how the app itself handles data on your device. Your configured server may collect, process, and store data under its own policies; please review your server's privacy terms separately.
## Information We Collect
- Device-stored data: minimal settings and preferences (e.g., theme, UI options) saved locally on your device.
@@ -20,7 +20,7 @@ Conduit is an opensource mobile client for OpenWebUI. This app acts as a c
- Network transfer: when you interact with the app, your data is sent to the server you configured. The app does not send your data to any developercontrolled servers.
## Permissions
Depending on how you use Conduit, the app may request:
Depending on how you use iiEasy, the app may request:
- Microphone: to capture voice input when you opt in.
- Photos/Files: to let you pick and upload attachments.
- Network access: to connect to your configured server.
@@ -40,12 +40,12 @@ We use platformprovided secure storage for sensitive credentials where suppor
- You can choose not to grant optional permissions; some features may not work without them.
## Childrens Privacy
Conduit is not directed to children under 13 (or the minimum age required in your jurisdiction). Do not use the app if you do not meet the applicable age requirements.
iiEasy is not directed to children under 13 (or the minimum age required in your jurisdiction). Do not use the app if you do not meet the applicable age requirements.
## Changes to This Policy
We may update this policy to reflect improvements or legal requirements. Material changes will be reflected in the app bundle and version notes.
## Contact
For questions or requests about this policy, please contact the app maintainer(s) through the project repository.
For questions or requests about this policy, please contact us at [iiEasy.ru](https://iiEasy.ru).

View File

@@ -1,16 +1,11 @@
# Conduit
# iiEasy
**iiEasy** — мобильный клиент для Open-WebUI на базе [Conduit](https://github.com/cogwheel0/conduit). Нативная работа с вашей self-hosted AI инфраструктурой.
<div align="center">
![Latest Release](https://img.shields.io/github/v/release/cogwheel0/conduit?display_name=tag&color=8A2BE2)
![GitHub all downloads](https://img.shields.io/github/downloads/cogwheel0/conduit/total?style=flat-square&label=Downloads&logo=github&color=0A84FF)
<a href="https://play.google.com/store/apps/details?id=app.cogwheel.conduit">
<img src="docs/store-badges/google.webp" alt="Get it on Google Play" style="height:56px;"/>
</a>
<a href="https://apps.apple.com/us/app/conduit-open-webui-client/id6749840287">
<img src="docs/store-badges/apple.webp" alt="Download on the App Store" style="height:56px;"/>
<a href="https://iiEasy.ru">
<img src="docs/store-badges/google.webp" alt="iiEasy" style="height:56px;"/>
</a>
</div>
@@ -18,12 +13,12 @@
<br>
<div align="center">
<img src="docs/screenshots/conduit-demo.gif" alt="Conduit Demo" style="height:600px;"/>
<img src="docs/screenshots/conduit-demo.gif" alt="iiEasy Demo" style="height:600px;"/>
</div>
<br>
Conduit is an open-source, cross-platform mobile application for Open-WebUI, providing a native mobile experience for interacting with your self-hosted AI infrastructure.
iiEasy is a cross-platform mobile application for Open-WebUI (based on the open-source Conduit project), providing a native mobile experience for interacting with your self-hosted AI infrastructure.
## Table of Contents
@@ -71,13 +66,13 @@ flutter run -d ios # or: -d android
- **Tools (Function Calling)**: Invoke server-side tools exposed by OpenWebUI, with result rendering
### Authentication
Conduit supports multiple authentication flows when connecting to your OpenWebUI:
iiEasy supports multiple authentication flows when connecting to your OpenWebUI (same as Conduit):
- **Username + Password**: Sign in directly against servers that expose a login endpoint. Credentials are stored securely using platform keychains.
- **SSO / OAuth** (iOS & Android): Authenticate via your server's configured OAuth providers (Google, Microsoft, GitHub, OIDC, etc.) using an in-app WebView. The token is automatically captured after the OAuth flow completes.
- **Reverse Proxy Support** (iOS & Android): Seamlessly connect to Open WebUI instances behind authentication proxies like oauth2-proxy, Authelia, Authentik, Pangolin, Cloudflare Tunnel, etc. Conduit automatically detects when proxy authentication is required and guides you through the login flow—no endpoint allowlisting or server-side configuration needed. Proxy session cookies are captured from the native cookie store and included in all subsequent API requests.
- **LDAP**: Sign in using LDAP credentials if enabled on your server.
- **JWT Token**: Paste a serverissued JWT token for manual token-based auth.
- **Custom Headers**: Add headers during login (e.g., `X-API-Key`, `Authorization`, `X-Org`) that Conduit will include on all HTTP/WebSocket requests.
- **Custom Headers**: Add headers during login (e.g., `X-API-Key`, `Authorization`, `X-Org`) that iiEasy will include on all HTTP/WebSocket requests.
The authentication page dynamically displays available options based on your server's configuration.
@@ -124,17 +119,49 @@ flutter run -d android
## Building for Release
### Android
**Если Flutter пишет «No Android SDK found» или «Android SDK not found at this location»:** переменная `ANDROID_HOME` должна указывать на каталог, где **реально установлен** SDK. Пустая или несуществующая папка не подойдёт.
1. **Вариант А — Android Studio**
Установите с [developer.android.com/studio](https://developer.android.com/studio). При первом запуске выберите установку SDK — он появится в `~/Android/Sdk`.
2. **Вариант Б — только command-line tools (без Android Studio)**
```bash
mkdir -p ~/Android/Sdk/cmdline-tools
cd ~/Android/Sdk
# Скачать с https://developer.android.com/studio#command-tools (Linux)
# Распаковать так, чтобы внутри было cmdline-tools/latest/bin/sdkmanager
unzip -q commandlinetools-linux-*.zip && mv cmdline-tools latest && mv latest cmdline-tools/
export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH"
yes | sdkmanager --licenses
sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
```
3. **Настройте окружение** (добавьте в `~/.bashrc` и выполните `source ~/.bashrc` или откройте новый терминал):
```bash
export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH"
```
4. Примите лицензии: `flutter doctor --android-licenses` (все запросы — `y`).
5. Проверка: `flutter doctor -v` — в блоке Android toolchain не должно быть ошибок.
После этого:
```bash
flutter build apk --release
# or for App Bundle
# или для выгрузки в Google Play
flutter build appbundle --release
```
APK: `build/app/outputs/flutter-apk/app-release.apk`
AAB: `build/app/outputs/bundle/release/app-release.aab`
### iOS
```bash
flutter build ios --release
```
### Linux (desktop)
Если сборка под Linux падает с ошибкой линковки (`libsecret`, `undefined reference to g_task_set_static_name` и т.п.), это типично для **Flutter из snap**: в snap старая glibc, а системные библиотеки (libsecret, glib) требуют новую. Решение: ставить Flutter **не из snap** — [официальная установка](https://docs.flutter.dev/get-started/install/linux) (manual install). После установки Flutter вручную `flutter run -d linux` и `flutter build linux` должны собираться. Для проверки приложения без Linux-сборки можно использовать эмулятор Android или устройство.
## Configuration
### Android
@@ -180,17 +207,7 @@ lib/
## Contributing
Conduit is currently in active development. We welcome your feedback and contributions!
**How to Contribute:**
- **Bug Reports**: Found a bug? Please [create an issue](https://github.com/cogwheel0/conduit/issues) with details about the problem, steps to reproduce, and your device/platform information.
- **Feature Requests**: Have an idea for a new feature? Start a [discussion](https://github.com/cogwheel0/conduit/discussions) to share your ideas and gather feedback from the community.
- **Questions & Feedback**: Use [GitHub Discussions](https://github.com/cogwheel0/conduit/discussions) to ask questions, share your experience, or discuss the project.
**Note:** As the project is actively evolving, we're not accepting pull requests at this time. Instead, please use issues and discussions to share your ideas, report bugs, and contribute to the project's development.
iiEasy is based on Conduit. For upstream contributions and discussions, see the [Conduit repository](https://github.com/cogwheel0/conduit). For iiEasy-specific feedback and support, visit [iiEasy.ru](https://iiEasy.ru).
## Troubleshooting
@@ -217,8 +234,9 @@ This project is licensed under the GPL3 License - see the LICENSE file for detai
- <a href="https://vercel.com/oss"><img alt="Vercel OSS Program" src="https://vercel.com/oss/program-badge.svg" /></a>
- Open-WebUI team for creating an amazing self-hosted AI interface
- Flutter team for the excellent mobile framework
- All contributors and users of Conduit
- Conduit project and all its contributors
- All users of iiEasy
## Support
For issues and feature requests, please use the [GitHub Issues](https://github.com/cogwheel0/conduit/issues) page.
For support and information, please visit [iiEasy.ru](https://iiEasy.ru).

View File

@@ -17,7 +17,7 @@ if (keystorePropertiesFile.exists()) {
android {
namespace = "app.cogwheel.conduit"
compileSdk = 36
ndkVersion = "29.0.14206865"
ndkVersion = "28.0.13004108"
defaultConfig {
applicationId = "app.cogwheel.conduit"
@@ -56,8 +56,8 @@ android {
if (keystorePropertiesFile.exists()) {
signingConfig = signingConfigs.getByName("release")
}
isMinifyEnabled = true
isShrinkResources = true
isMinifyEnabled = false
isShrinkResources = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"

View File

@@ -35,7 +35,7 @@
ForegroundServiceDidNotStartInTimeException. This ensures channels exist before
any foreground service attempts to use them. -->
<application
android:label="Conduit"
android:label="iiEasy"
android:name=".ConduitApplication"
android:icon="@mipmap/ic_launcher"
android:allowBackup="false"
@@ -103,7 +103,7 @@
<activity
android:name=".ProcessTextActivity"
android:exported="true"
android:label="Ask Conduit"
android:label="Ask iiEasy"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.PROCESS_TEXT" />

View File

@@ -94,7 +94,7 @@ class BackgroundStreamingService : Service() {
// Otherwise startForeground throws "Bad notification" error
ensureNotificationChannel()
val fallbackNotification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Conduit")
.setContentTitle("iiEasy")
.setSmallIcon(R.mipmap.ic_launcher)
.setSilent(true)
.setOngoing(true) // Prevent user from dismissing foreground service notification
@@ -264,7 +264,7 @@ class BackgroundStreamingService : Service() {
// Create a minimal, silent notification (required for foreground service)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Conduit")
.setContentTitle("iiEasy")
.setContentText("Background service active")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
@@ -289,7 +289,7 @@ class BackgroundStreamingService : Service() {
"Background Service",
NotificationManager.IMPORTANCE_MIN,
).apply {
description = "Background service for Conduit"
description = "Background service for iiEasy"
setShowBadge(false)
enableLights(false)
enableVibration(false)
@@ -324,7 +324,7 @@ class BackgroundStreamingService : Service() {
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"Conduit::StreamingWakeLock"
"iiEasy::StreamingWakeLock"
).apply {
// Disable reference counting for deterministic single-holder behavior
// This prevents accumulation if acquireWakeLock is called multiple times
@@ -728,7 +728,7 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "Background Service"
val descriptionText = "Background service for Conduit"
val descriptionText = "Background service for iiEasy"
val importance = NotificationManager.IMPORTANCE_MIN
val channel = NotificationChannel(BackgroundStreamingService.CHANNEL_ID, name, importance).apply {
description = descriptionText

View File

@@ -35,7 +35,7 @@ class ConduitApplication : Application() {
notificationManager,
channelId = BackgroundStreamingService.CHANNEL_ID,
channelName = "Background Service",
description = "Background service for Conduit",
description = "Background service for iiEasy",
importance = NotificationManager.IMPORTANCE_MIN,
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- iiEasy logo: 3 concentric dashed circles (from assets/icons/logo.svg) -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="200"
android:viewportHeight="200">
<!-- Three concentric circles (solid stroke; dash not supported by AAPT in drawable) -->
<path
android:pathData="M 170,100 A 70,70 0 1,1 169.99,100"
android:strokeColor="#1F2937"
android:strokeWidth="6"
android:fillColor="#00000000"/>
<path
android:pathData="M 150,100 A 50,50 0 1,1 149.99,100"
android:strokeColor="#1F2937"
android:strokeWidth="6"
android:fillColor="#00000000"/>
<path
android:pathData="M 130,100 A 30,30 0 1,1 129.99,100"
android:strokeColor="#1F2937"
android:strokeWidth="6"
android:fillColor="#00000000"/>
</vector>

View File

@@ -89,7 +89,7 @@
android:paddingStart="20dp"
android:paddingEnd="8dp">
<!-- Ask Conduit text -->
<!-- Ask iiEasy text -->
<TextView
android:id="@+id/input_area"
android:layout_width="wrap_content"
@@ -97,7 +97,7 @@
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/btn_voice"
android:layout_centerVertical="true"
android:text="Ask Conduit"
android:text="Ask iiEasy"
android:textColor="@android:color/white"
android:textSize="16sp"
android:alpha="0.6"

View File

@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
</adaptive-icon>
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -27,8 +27,5 @@
<color name="widget_secondary_container_fallback">#E8DEF8</color>
<color name="widget_on_secondary_container_fallback">#1D192B</color>
<color name="widget_surface_fallback">#FFFBFE</color>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Conduit</string>
<string name="app_name">iiEasy</string>
<!-- Widget strings -->
<string name="widget_name">Conduit</string>
<string name="widget_description">Quick access to Conduit chat with camera, photos, and clipboard shortcuts</string>
<string name="widget_ask_conduit">Ask Conduit</string>
<string name="widget_name">iiEasy</string>
<string name="widget_description">Quick access to iiEasy chat with camera, photos, and clipboard shortcuts</string>
<string name="widget_ask_conduit">Ask iiEasy</string>
<string name="widget_camera">Camera</string>
<string name="widget_photos">Photos</string>
<string name="widget_clipboard">Clipboard</string>

View File

@@ -1,4 +1,4 @@
Conduit is an open-source, native mobile client for OpenWebUI. Connect to your own server to chat with AI models, manage conversations, and take your selfhosted AI with you—securely and on the go.
iiEasy is a native mobile client for OpenWebUI, based on the open-source Conduit project. Connect to your own server to chat with AI models, manage conversations, and take your selfhosted AI with you—securely and on the go.
Features
- Real-time streaming chat
@@ -13,7 +13,7 @@ Features
- Offline-aware experience
Requirements
- Requires an existing OpenWebUI server. Conduit does not host or provide AI models.
- Requires an existing OpenWebUI server. iiEasy does not host or provide AI models.
- No data is sent to third-party services by default; everything stays with your configured server.
Permissions
@@ -21,11 +21,10 @@ Permissions
- Camera and Photos/Storage: Image/file attachments
- Network: Connect to your OpenWebUI server
Open Source:
Conduit is an open-source project. For support, to report issues, or to view the source code, please visit our GitHub repository:
For support, issues, and more information, please visit:
https://github.com/cogwheel0/conduit
https://iiEasy.ru
-----
Disclaimer: This is an independent, third-party application licensed under the GNU General Public License v3.0 (GPLv3) and is not officially affiliated with the OpenWebUI project.
Disclaimer: iiEasyWeb is an independent, third-party application licensed under the GNU General Public License v3.0 (GPLv3) and is not officially affiliated with the OpenWebUI project.

View File

@@ -1 +1 @@
Conduit: OpenWebUI Client
iiEasyWeb

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 76 KiB

BIN
assets/icons/icon_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

5
assets/icons/logo.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="iiEasy Logo">
<circle cx="100" cy="100" r="70" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="15 85" transform="rotate(-90 100 100)"/>
<circle cx="100" cy="100" r="50" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(-60 100 100)"/>
<circle cx="100" cy="100" r="30" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(-30 100 100)"/>
</svg>

After

Width:  |  Height:  |  Size: 549 B

View File

@@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="iiEasy Logo (dark)">
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85" transform="rotate(-90 100 100)"/>
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
</svg>

After

Width:  |  Height:  |  Size: 554 B

View File

@@ -4,29 +4,29 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conduit — Your AI, Everywhere</title>
<title>iiEasy — Your AI, Everywhere</title>
<meta name="description"
content="Conduit is an open-source, cross-platform mobile application for Open-WebUI. A native mobile experience for your self-hosted AI infrastructure.">
content="iiEasy is a mobile client for Open-WebUI (based on Conduit). A native mobile experience for your self-hosted AI infrastructure.">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://conduit.cogwheel.app/">
<meta property="og:title" content="Conduit — Your AI, Everywhere">
<meta property="og:url" content="https://iiEasy.ru/">
<meta property="og:title" content="iiEasy — Your AI, Everywhere">
<meta property="og:description" content="The native mobile client for Open-WebUI. Beautiful, fast, and secure.">
<meta property="og:image" content="https://conduit.cogwheel.app/og-image.png">
<meta property="og:image" content="https://iiEasy.ru/og-image.png">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="https://conduit.cogwheel.app/">
<meta property="twitter:title" content="Conduit — Your AI, Everywhere">
<meta property="twitter:url" content="https://iiEasy.ru/">
<meta property="twitter:title" content="iiEasy — Your AI, Everywhere">
<meta property="twitter:description" content="The native mobile client for Open-WebUI. Beautiful, fast, and secure.">
<meta property="twitter:image" content="https://conduit.cogwheel.app/og-image.png">
<meta property="twitter:image" content="https://iiEasy.ru/og-image.png">
<link rel="stylesheet" href="styles.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,300;12..96,400;12..96,500;12..96,600;12..96,700;12..96,800&family=Crimson+Pro:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="icon" type="image/png"
href="https://raw.githubusercontent.com/cogwheel0/conduit/main/assets/icons/icon.png">
href="assets/icons/icon.png">
<script src="https://app.rybbit.io/api/script.js" data-site-id="1c79ee193aa9" defer></script>
</head>
@@ -41,15 +41,15 @@
<nav class="navbar">
<div class="container">
<a href="#" class="logo">
<img src="https://raw.githubusercontent.com/cogwheel0/conduit/main/assets/icons/icon.png" alt="Conduit" class="logo-icon">
<span class="logo-text">Conduit</span>
<a href="https://iiEasy.ru" class="logo">
<img src="assets/icons/icon.png" alt="iiEasy" class="logo-icon">
<span class="logo-text">iiEasy</span>
</a>
<div class="nav-links">
<a href="#features">Features</a>
<a href="#gallery">Gallery</a>
<a href="https://github.com/cogwheel0/conduit" target="_blank" class="nav-cta">
<span>View on GitHub</span>
<a href="https://iiEasy.ru" target="_blank" class="nav-cta">
<span>iiEasy.ru</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M7 17L17 7M17 7H7M17 7V17"/>
</svg>
@@ -66,7 +66,7 @@
<div class="mobile-menu">
<a href="#features">Features</a>
<a href="#gallery">Gallery</a>
<a href="https://github.com/cogwheel0/conduit" target="_blank">GitHub</a>
<a href="https://iiEasy.ru" target="_blank">iiEasy.ru</a>
</div>
<header class="hero">
@@ -76,14 +76,14 @@
<span class="badge-dot"></span>
Open Source & Privacy-First
</div>
<a href="https://github.com/cogwheel0/conduit" target="_blank" class="social-badge">
<a href="https://iiEasy.ru" target="_blank" class="social-badge">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
<span class="badge-value" id="github-stars"></span>
<span class="badge-label">Stars</span>
</a>
<a href="https://github.com/cogwheel0/conduit/releases" target="_blank" class="social-badge">
<a href="https://iiEasy.ru/releases" target="_blank" class="social-badge">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
@@ -122,7 +122,7 @@
<div class="hero-device">
<div class="device-glow"></div>
<div class="device-frame">
<img src="screenshots/conduit-demo.gif" alt="Conduit App Demo" loading="eager">
<img src="screenshots/conduit-demo.gif" alt="iiEasy App Demo" loading="eager">
</div>
<div class="device-reflection"></div>
</div>
@@ -403,7 +403,7 @@
</div>
<div class="mini-feature">
<span class="mini-feature-name">Share Extension</span>
<span class="mini-feature-desc">Send from any app to Conduit</span>
<span class="mini-feature-desc">Send from any app to iiEasy</span>
</div>
</div>
</div>
@@ -423,7 +423,7 @@
<div class="category-features">
<div class="mini-feature">
<span class="mini-feature-name">Siri Shortcuts</span>
<span class="mini-feature-desc">"Hey Siri, ask Conduit..."</span>
<span class="mini-feature-desc">"Hey Siri, ask iiEasy..."</span>
</div>
<div class="mini-feature">
<span class="mini-feature-name">Android Assistant</span>
@@ -526,7 +526,7 @@
<div class="cta-card">
<div class="cta-content">
<h2>Ready to get started?</h2>
<p>Join thousands of users who've made Conduit their gateway to self-hosted AI. Open source and available now.</p>
<p>Join thousands of users who've made iiEasy their gateway to self-hosted AI. Based on Conduit. Open source and available now.</p>
<div class="cta-buttons">
<a href="https://apps.apple.com/us/app/conduit-open-webui-client/id6749840287" class="btn btn-white">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
@@ -556,8 +556,8 @@
<div class="footer-main">
<div class="footer-brand">
<a href="#" class="logo">
<img src="https://raw.githubusercontent.com/cogwheel0/conduit/main/assets/icons/icon.png" alt="Conduit" class="logo-icon">
<span class="logo-text">Conduit</span>
<img src="assets/icons/icon.png" alt="iiEasy" class="logo-icon">
<span class="logo-text">iiEasy</span>
</a>
<p>The native mobile client for Open-WebUI.<br>Open source. Privacy-first. Beautiful.</p>
</div>
@@ -566,25 +566,25 @@
<h4>Product</h4>
<a href="#features">Features</a>
<a href="#gallery">Gallery</a>
<a href="https://github.com/cogwheel0/conduit/releases">Releases</a>
<a href="https://iiEasy.ru/releases">Releases</a>
</div>
<div class="footer-col">
<h4>Community</h4>
<a href="https://github.com/cogwheel0/conduit">GitHub</a>
<a href="https://github.com/cogwheel0/conduit/issues">Issues</a>
<a href="https://github.com/cogwheel0/conduit/discussions">Discussions</a>
<a href="https://iiEasy.ru">GitHub</a>
<a href="https://iiEasy.ru/issues">Issues</a>
<a href="https://iiEasy.ru/discussions">Discussions</a>
</div>
<div class="footer-col">
<h4>Legal</h4>
<a href="https://github.com/cogwheel0/conduit/blob/main/LICENSE">License (GPLv3)</a>
<a href="https://github.com/cogwheel0/conduit/blob/main/PRIVACY_POLICY.md">Privacy Policy</a>
<a href="https://iiEasy.ru/blob/main/LICENSE">License (GPLv3)</a>
<a href="https://iiEasy.ru/blob/main/PRIVACY_POLICY.md">Privacy Policy</a>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2025 Conduit. Not affiliated with Open-WebUI.</p>
<p>&copy; 2025 iiEasy. Based on Conduit. Not affiliated with Open-WebUI.</p>
<div class="footer-social">
<a href="https://github.com/cogwheel0/conduit" aria-label="GitHub">
<a href="https://iiEasy.ru" aria-label="GitHub">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>

View File

@@ -739,7 +739,7 @@
DEVELOPMENT_TEAM = X2662V5DT2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Conduit;
INFOPLIST_KEY_CFBundleDisplayName = iiEasy;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -813,7 +813,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -870,7 +870,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -925,7 +925,7 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Debug";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
@@ -934,7 +934,7 @@
DEVELOPMENT_TEAM = X2662V5DT2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Conduit Debug";
INFOPLIST_KEY_CFBundleDisplayName = "iiEasy Debug";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -969,7 +969,7 @@
DEVELOPMENT_TEAM = X2662V5DT2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Conduit;
INFOPLIST_KEY_CFBundleDisplayName = iiEasy;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -992,8 +992,8 @@
F15AFED22EE5499E00A1FABB /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppIcon;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = AppIcon;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
@@ -1009,7 +1009,7 @@
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ConduitWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ConduitWidget;
INFOPLIST_KEY_CFBundleDisplayName = iiEasy;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 26.1;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1038,8 +1038,8 @@
F15AFED32EE5499E00A1FABB /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppIcon;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = AppIcon;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
@@ -1055,7 +1055,7 @@
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ConduitWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ConduitWidget;
INFOPLIST_KEY_CFBundleDisplayName = iiEasy;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 26.1;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1081,8 +1081,8 @@
F15AFED42EE5499E00A1FABB /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppIcon;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = AppIcon;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
@@ -1098,7 +1098,7 @@
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ConduitWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ConduitWidget;
INFOPLIST_KEY_CFBundleDisplayName = iiEasy;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 26.1;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1141,7 +1141,7 @@
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
INFOPLIST_KEY_CFBundleDisplayName = "Ask iiEasy";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1189,7 +1189,7 @@
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
INFOPLIST_KEY_CFBundleDisplayName = "Ask iiEasy";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1234,7 +1234,7 @@
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
INFOPLIST_KEY_CFBundleDisplayName = "Ask iiEasy";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (

View File

@@ -1,134 +1 @@
{
"images": [
{
"filename": "AppIcon@2x.png",
"idiom": "iphone",
"scale": "2x",
"size": "60x60"
},
{
"filename": "AppIcon@3x.png",
"idiom": "iphone",
"scale": "3x",
"size": "60x60"
},
{
"filename": "AppIcon~ipad.png",
"idiom": "ipad",
"scale": "1x",
"size": "76x76"
},
{
"filename": "AppIcon@2x~ipad.png",
"idiom": "ipad",
"scale": "2x",
"size": "76x76"
},
{
"filename": "AppIcon-83.5@2x~ipad.png",
"idiom": "ipad",
"scale": "2x",
"size": "83.5x83.5"
},
{
"filename": "AppIcon-40@2x.png",
"idiom": "iphone",
"scale": "2x",
"size": "40x40"
},
{
"filename": "AppIcon-40@3x.png",
"idiom": "iphone",
"scale": "3x",
"size": "40x40"
},
{
"filename": "AppIcon-40~ipad.png",
"idiom": "ipad",
"scale": "1x",
"size": "40x40"
},
{
"filename": "AppIcon-40@2x~ipad.png",
"idiom": "ipad",
"scale": "2x",
"size": "40x40"
},
{
"filename": "AppIcon-20@2x.png",
"idiom": "iphone",
"scale": "2x",
"size": "20x20"
},
{
"filename": "AppIcon-20@3x.png",
"idiom": "iphone",
"scale": "3x",
"size": "20x20"
},
{
"filename": "AppIcon-20~ipad.png",
"idiom": "ipad",
"scale": "1x",
"size": "20x20"
},
{
"filename": "AppIcon-20@2x~ipad.png",
"idiom": "ipad",
"scale": "2x",
"size": "20x20"
},
{
"filename": "AppIcon-29.png",
"idiom": "iphone",
"scale": "1x",
"size": "29x29"
},
{
"filename": "AppIcon-29@2x.png",
"idiom": "iphone",
"scale": "2x",
"size": "29x29"
},
{
"filename": "AppIcon-29@3x.png",
"idiom": "iphone",
"scale": "3x",
"size": "29x29"
},
{
"filename": "AppIcon-29~ipad.png",
"idiom": "ipad",
"scale": "1x",
"size": "29x29"
},
{
"filename": "AppIcon-29@2x~ipad.png",
"idiom": "ipad",
"scale": "2x",
"size": "29x29"
},
{
"filename": "AppIcon-60@2x~car.png",
"idiom": "car",
"scale": "2x",
"size": "60x60"
},
{
"filename": "AppIcon-60@3x~car.png",
"idiom": "car",
"scale": "3x",
"size": "60x60"
},
{
"filename": "AppIcon~ios-marketing.png",
"idiom": "ios-marketing",
"scale": "1x",
"size": "1024x1024"
}
],
"info": {
"author": "iconkitchen",
"version": 1
}
}
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -11,7 +11,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Conduit</string>
<string>iiEasy</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -32,7 +32,7 @@
<string>zh-Hant</string>
</array>
<key>CFBundleName</key>
<string>conduit</string>
<string>iiEasy</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
@@ -64,6 +64,8 @@
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>AppGroupId</key>
<string>group.app.cogwheel.conduit</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
@@ -76,13 +78,13 @@
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>Conduit uses the camera to take photos or videos you choose to share in chats. For example, you can snap a photo of a document and attach it to a message.</string>
<string>iiEasy uses the camera to take photos or videos you choose to share in chats. For example, you can snap a photo of a document and attach it to a message.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Conduit uses the microphone to record voice messages and enable voice-to-text in chats. For example, when you hold the mic button in a conversation, we capture your speech to send as an audio message or transcript.</string>
<string>iiEasy uses the microphone to record voice messages and enable voice-to-text in chats. For example, when you hold the mic button in a conversation, we capture your speech to send as an audio message or transcript.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Conduit needs access to your photo library so you can select existing images or videos to share in chats. For example, you can pick a screenshot to include in a conversation.</string>
<string>iiEasy needs access to your photo library so you can select existing images or videos to share in chats. For example, you can pick a screenshot to include in a conversation.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Conduit uses on-device speech recognition so you can dictate messages handsfree. Your speech is converted to text on your device when available.</string>
<string>iiEasy uses on-device speech recognition so you can dictate messages handsfree. Your speech is converted to text on your device when available.</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>

View File

@@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Conduit";
CFBundleDisplayName = "iiEasy";

View File

@@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Conduit";
CFBundleDisplayName = "iiEasy";

View File

@@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Conduit";
CFBundleDisplayName = "iiEasy";

View File

@@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Conduit";
CFBundleDisplayName = "iiEasy";

View File

@@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Conduit";
CFBundleDisplayName = "iiEasy";

View File

@@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Conduit";
CFBundleDisplayName = "iiEasy";

View File

@@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Conduit";
CFBundleDisplayName = "iiEasy";

View File

@@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Conduit";
CFBundleDisplayName = "iiEasy";

View File

@@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Conduit";
CFBundleDisplayName = "iiEasy";

View File

@@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Conduit";
CFBundleDisplayName = "iiEasy";

View File

@@ -3,10 +3,9 @@
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Ask Conduit</string>
<!-- Uncomment below lines if you want to use a custom group id rather than the default. Set it in Build Settings -> User-Defined -->
<!-- <key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string> -->
<string>Ask iiEasy</string>
<key>AppGroupId</key>
<string>group.app.cogwheel.conduit</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>

View File

@@ -1 +1 @@
Conduit: OpenWebUI Client
iiEasyWeb

View File

@@ -1 +1 @@
Conduit: OpenWebUI Client
iiEasyWeb

View File

@@ -1 +1 @@
Conduit: OpenWebUI Client
iiEasyWeb

View File

@@ -1 +1 @@
Conduit: Cliente OpenWebUI
iiEasyWeb

View File

@@ -1 +1 @@
Conduit : Client OpenWebUI
iiEasyWeb

View File

@@ -1 +1 @@
Conduit: Client OpenWebUI
iiEasyWeb

View File

@@ -1 +1 @@
Conduit: OpenWebUI 클라이언트
iiEasyWeb

View File

@@ -1 +1 @@
Conduit: OpenWebUI Client
iiEasyWeb

View File

@@ -1 +1 @@
Conduit: Клиент OpenWebUI
iiEasyWeb

View File

@@ -1 +1 @@
Conduit: OpenWebUI 客户端
iiEasyWeb

View File

@@ -1 +1 @@
Conduit
iiEasyWeb

View File

@@ -226,6 +226,73 @@ class AuthStateManager extends _$AuthStateManager {
// Fast path: trust token format to avoid blocking startup on network
final formatOk = _isValidTokenFormat(token);
if (formatOk) {
// Network readiness gate: Wait for API to be reachable before
// transitioning to authenticated state. This prevents race conditions
// on cold starts with Cloudflare tunnels where the tunnel connection
// may not be established yet.
final apiReady = await _waitForApiReadiness();
if (!apiReady) {
DebugLogger.auth(
'API not reachable on cold start - keeping loading state',
);
// Keep loading state and retry via silent login if we have creds
final hasCreds = await storage.hasCredentials();
if (hasCreds) {
DebugLogger.auth(
'Has credentials - attempting silent login after API ready',
);
// Schedule a delayed retry that will wait for network.
// Allow time for network stack to stabilize after initial failure.
unawaited(
Future.delayed(const Duration(milliseconds: 500), () async {
if (!ref.mounted) return;
try {
final retryReady = await _waitForApiReadiness(
timeout: const Duration(seconds: 10),
);
if (!ref.mounted) return;
if (retryReady) {
await _performSilentLogin();
} else {
_update(
(current) => current.copyWith(
status: AuthStatus.error,
error: 'Unable to connect to server',
isLoading: false,
),
);
}
} catch (e, stack) {
if (!ref.mounted) return;
DebugLogger.error(
'delayed-retry-failed',
scope: 'auth/state',
error: e,
stackTrace: stack,
);
_update(
(current) => current.copyWith(
status: AuthStatus.error,
error: 'Connection retry failed',
isLoading: false,
),
);
}
}),
);
return;
}
// No credentials - show error
_update(
(current) => current.copyWith(
status: AuthStatus.error,
error: 'Unable to connect to server',
isLoading: false,
),
);
return;
}
_update(
(current) => current.copyWith(
status: AuthStatus.authenticated,
@@ -652,6 +719,61 @@ class AuthStateManager extends _$AuthStateManager {
}
}
/// Wait for the API to be reachable (network readiness gate).
///
/// On cold starts with Cloudflare tunnels or other proxy setups, the network
/// connection may not be established immediately. This method performs a
/// health check with retries to ensure we don't show the wrong screen due to
/// a race condition between auth state initialization and network readiness.
///
/// Returns true if the API is reachable within the timeout, false otherwise.
Future<bool> _waitForApiReadiness({
Duration timeout = const Duration(seconds: 3),
Duration retryDelay = const Duration(milliseconds: 300),
}) async {
final stopwatch = Stopwatch()..start();
// First ensure the API service provider is available
await _ensureApiServiceAvailable(timeout: const Duration(seconds: 1));
while (stopwatch.elapsed < timeout) {
if (!ref.mounted) return false;
final api = ref.read(apiServiceProvider);
if (api == null) {
await Future.delayed(retryDelay);
continue;
}
try {
// Use checkHealth which hits the /health endpoint
final healthy = await api.checkHealth();
if (healthy) {
DebugLogger.auth(
'API readiness confirmed in ${stopwatch.elapsedMilliseconds}ms',
);
return true;
}
} catch (e) {
DebugLogger.auth(
'API readiness check failed (${stopwatch.elapsedMilliseconds}ms): $e',
);
}
// Wait before retrying
if (stopwatch.elapsed + retryDelay < timeout) {
await Future.delayed(retryDelay);
} else {
break;
}
}
DebugLogger.auth(
'API readiness timed out after ${stopwatch.elapsedMilliseconds}ms',
);
return false;
}
/// Perform silent auto-login with saved credentials
Future<bool> silentLogin() async {
// Coalesce concurrent calls (e.g., UI + interceptor retry)

View File

@@ -794,45 +794,67 @@ class ApiService {
DebugLogger.log('settings-ok', scope: 'api/user-settings');
final data = response.data;
if (data is! Map<String, dynamic>) {
DebugLogger.warning(
'settings-format',
scope: 'api/user-settings',
data: {'type': data.runtimeType},
);
return null;
}
// Extract default model from ui.models array
final ui = data['ui'];
if (ui is Map<String, dynamic>) {
final models = ui['models'];
if (models is List && models.isNotEmpty) {
// Return the first model in the user's preferred models list
final defaultModel = models.first.toString();
DebugLogger.log(
'default-model',
scope: 'api/user-settings',
data: {'id': defaultModel},
);
return defaultModel;
if (data is Map<String, dynamic>) {
// Extract default model from ui.models array
final ui = data['ui'];
if (ui is Map<String, dynamic>) {
final models = ui['models'];
if (models is List && models.isNotEmpty) {
// Return the first model in the user's preferred models list
final defaultModel = models.first.toString();
DebugLogger.log(
'default-model',
scope: 'api/user-settings',
data: {'id': defaultModel},
);
return defaultModel;
}
}
}
DebugLogger.warning('default-model-missing', scope: 'api/user-settings');
return null;
// Fallback: user has no default model configured, pick first available
// This fixes issue #353 where secondary accounts couldn't send messages
DebugLogger.log(
'default-model-fallback',
scope: 'api/user-settings',
);
return _getFirstAvailableModelId();
} catch (e) {
DebugLogger.error(
'default-model-error',
scope: 'api/user-settings',
error: e,
);
// Do not call admin-only configs endpoint here; let the caller
// handle fallback (e.g., first available model from /api/models).
return null;
// Attempt fallback even on error
return _getFirstAvailableModelId();
}
}
/// Returns the ID of the first available model, or null if none available.
///
/// Used as a fallback when user has no default model configured.
Future<String?> _getFirstAvailableModelId() async {
try {
final models = await getModels();
if (models.isNotEmpty) {
final fallbackId = models.first.id;
DebugLogger.log(
'default-model-fallback-selected',
scope: 'api/user-settings',
data: {'id': fallbackId},
);
return fallbackId;
}
} catch (e) {
DebugLogger.error(
'default-model-fallback-failed',
scope: 'api/user-settings',
error: e,
);
}
return null;
}
// Conversations - Updated to use correct OpenWebUI API
Future<List<Conversation>> getConversations({int? limit, int? skip}) async {
final pinnedFuture = _fetchChatCollection(
@@ -1498,7 +1520,12 @@ class ApiService {
Future<Map<String, dynamic>> getUserSettings() async {
_traceApi('Fetching user settings');
final response = await _dio.get('/api/v1/users/user/settings');
return response.data as Map<String, dynamic>;
final data = response.data;
// Handle null response from server (happens for new users with no settings)
if (data is Map<String, dynamic>) {
return data;
}
return <String, dynamic>{};
}
Future<void> updateUserSettings(Map<String, dynamic> settings) async {

View File

@@ -405,12 +405,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
Widget _buildWelcomeSection() {
return Column(
children: [
BrandService.createBrandIcon(
size: 48,
useGradient: false,
addShadow: false,
context: context,
),
BrandService.createLogoImage(size: 48, context: context),
const SizedBox(height: Spacing.lg),
Text(
AppLocalizations.of(context)!.signIn,

View File

@@ -604,12 +604,7 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
alignment: Alignment.center,
children: [
// Brand logo
BrandService.createBrandIcon(
size: 56,
useGradient: false,
addShadow: false,
context: context,
),
BrandService.createLogoImage(size: 56, context: context),
// Reviewer mode badge
if (reviewerMode)
Positioned(

View File

@@ -149,6 +149,9 @@ class TextToSpeechService {
Future<void> dispose() async {
await _eventSubscription?.cancel();
_eventSubscription = null;
// Reset the singleton state for next session
await TtsManager.instance.reset();
}
/// Updates TTS settings.

View File

@@ -376,6 +376,28 @@ class TtsManager {
}
}
/// Resets the manager state for a new session.
///
/// Call this between voice calls to ensure clean state. This clears
/// playback buffers and resets session tracking without destroying
/// the singleton instance.
Future<void> reset() async {
await stop();
// Reset playback state
_resetPlaybackState();
_activeSession = null;
_sessionCounter = 0;
// Reset server audio buffer
_serverAudioBuffer.clear();
_serverWaitingForNext = false;
// Reset cached voice defaults so they're refetched if needed
_serverDefaultVoice = null;
_serverDefaultVoiceFuture = null;
}
/// Disposes the manager and releases resources.
Future<void> dispose() async {
await stop();

View File

@@ -395,7 +395,7 @@ class VoiceCallService {
_activeAssistantMessageId = null;
_responseCompleted = false;
_listeningSuspendedForSpeech = false;
_resetServerAudio(stopPlayback: true);
await _resetServerAudio(stopPlayback: true);
if (_pauseReasons.isNotEmpty) {
_listeningPaused = true;
@@ -584,7 +584,11 @@ class VoiceCallService {
_speechQueue.clear();
_enqueuedSentenceCount = 0;
_responseCompleted = false;
_resetServerAudio(stopPlayback: true);
// Fire-and-forget: This is a synchronous event handler where blocking
// would delay socket event processing. The audio player stop is fast
// and race conditions are acceptable here since new playback will
// wait for this session to complete anyway.
unawaited(_resetServerAudio(stopPlayback: true));
if (_isSpeaking) {
_isSpeaking = false;
unawaited(_tts.stop());
@@ -749,14 +753,14 @@ class VoiceCallService {
_maybeResumeListeningAfterSpeech();
}
void _resetServerAudio({bool stopPlayback = false}) {
Future<void> _resetServerAudio({bool stopPlayback = false}) async {
_serverAudioBuffer.clear();
_pendingServerAudioFetches = 0;
_serverAudioSession++;
_nextServerChunkId = 0;
_nextServerPlaybackId = 0;
if (stopPlayback) {
unawaited(_serverAudioPlayer.stop());
await _serverAudioPlayer.stop();
_isSpeaking = false;
}
_serverPipelineActive = false;
@@ -819,7 +823,7 @@ class VoiceCallService {
if (_isDisposed) return;
_isSpeaking = false;
_speechQueue.clear();
_resetServerAudio(stopPlayback: true);
unawaited(_resetServerAudio(stopPlayback: true));
_listeningSuspendedForSpeech = false;
_updateState(VoiceCallState.error);
// Try to recover by restarting listening
@@ -865,7 +869,7 @@ class VoiceCallService {
_listeningSuspendedForSpeech = false;
_activeAssistantMessageId = null;
_isSpeaking = false;
_resetServerAudio(stopPlayback: true);
await _resetServerAudio(stopPlayback: true);
_updateState(VoiceCallState.disconnected);
}
@@ -911,7 +915,7 @@ class VoiceCallService {
_enqueuedSentenceCount = 0;
_responseCompleted = false;
_listeningSuspendedForSpeech = false;
_resetServerAudio(stopPlayback: true);
await _resetServerAudio(stopPlayback: true);
await _tts.stop();
_isSpeaking = false;
_accumulatedResponse = '';
@@ -985,7 +989,7 @@ class VoiceCallService {
_enqueuedSentenceCount = 0;
_responseCompleted = false;
_listeningSuspendedForSpeech = false;
_resetServerAudio(stopPlayback: true);
unawaited(_resetServerAudio(stopPlayback: true));
pauseListening(reason: VoiceCallPauseReason.mute);
} else {
resumeListening(reason: VoiceCallPauseReason.mute);
@@ -1006,7 +1010,7 @@ class VoiceCallService {
_callKitEventSubscription = null;
_socketSubscription?.dispose();
_voiceInput.dispose();
await _voiceInput.dispose();
await _tts.dispose();
await _serverAudioStateSub?.cancel();
await _serverAudioPlayer.dispose();
@@ -1029,7 +1033,7 @@ class VoiceCallService {
}
}
@Riverpod(keepAlive: true)
@riverpod
VoiceCallService voiceCallService(Ref ref) {
final voiceInput = ref.watch(voiceInputServiceProvider);
final api = ref.watch(apiServiceProvider);

View File

@@ -1026,12 +1026,12 @@ class VoiceInputService {
});
}
void dispose() {
stopListening();
unawaited(_disposeVadHandler());
unawaited(_microphonePermissionProbe.dispose());
Future<void> dispose() async {
await stopListening();
await _disposeVadHandler();
await _microphonePermissionProbe.dispose();
try {
_speech.stop();
await _speech.stop();
} catch (_) {}
}
}

View File

@@ -1,25 +1,18 @@
import 'package:flutter/material.dart';
import '../../../shared/theme/theme_extensions.dart';
import '../../../shared/widgets/ii_easy_loading_logo.dart';
class SplashLauncherPage extends StatelessWidget {
const SplashLauncherPage({super.key});
@override
Widget build(BuildContext context) {
final isDark = context.conduitTheme.isDark;
return Scaffold(
backgroundColor: context.conduitTheme.surfaceBackground,
body: Center(
child: SizedBox(
width: 28,
height: 28,
child: CircularProgressIndicator(
strokeWidth: 2.5,
valueColor: AlwaysStoppedAnimation<Color>(
context.conduitTheme.loadingIndicator,
),
),
),
),
backgroundColor: isDark
? const Color(0xFF0A0A0A)
: Colors.white,
body: IiEasyLoadingLogo(isDark: isDark),
);
}
}

View File

@@ -35,9 +35,6 @@ import '../../../shared/widgets/model_avatar.dart';
/// Profile page (You tab) showing user info and main actions
/// Enhanced with production-grade design tokens for better cohesion
class ProfilePage extends ConsumerWidget {
static const _githubSponsorsUrl = 'https://github.com/sponsors/cogwheel0';
static const _buyMeACoffeeUrl = 'https://www.buymeacoffee.com/cogwheel0';
const ProfilePage({super.key});
@override
@@ -117,113 +114,10 @@ class ProfilePage extends ConsumerWidget {
_buildProfileHeader(context, userData, api),
const SizedBox(height: Spacing.xl),
_buildAccountSection(context, ref),
const SizedBox(height: Spacing.xl),
_buildSupportSection(context),
],
);
}
Widget _buildSupportSection(BuildContext context) {
final theme = context.conduitTheme;
final textTheme =
theme.bodySmall?.copyWith(
color: theme.sidebarForeground.withValues(alpha: 0.75),
) ??
TextStyle(color: theme.sidebarForeground.withValues(alpha: 0.75));
final supportTiles = [
_buildSupportOption(
context,
icon: UiUtils.platformIcon(
ios: CupertinoIcons.gift,
android: Icons.coffee,
),
title: AppLocalizations.of(context)!.buyMeACoffeeTitle,
subtitle: AppLocalizations.of(context)!.buyMeACoffeeSubtitle,
url: _buyMeACoffeeUrl,
color: theme.warning,
),
_buildSupportOption(
context,
icon: UiUtils.platformIcon(
ios: CupertinoIcons.heart,
android: Icons.favorite_border,
),
title: AppLocalizations.of(context)!.githubSponsorsTitle,
subtitle: AppLocalizations.of(context)!.githubSponsorsSubtitle,
url: _githubSponsorsUrl,
color: theme.success,
),
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.supportConduit,
style: theme.headingSmall?.copyWith(color: theme.sidebarForeground),
),
const SizedBox(height: Spacing.xs),
Text(
AppLocalizations.of(context)!.supportConduitSubtitle,
style: textTheme,
),
const SizedBox(height: Spacing.sm),
for (var i = 0; i < supportTiles.length; i++) ...[
supportTiles[i],
if (i != supportTiles.length - 1) const SizedBox(height: Spacing.md),
],
],
);
}
Widget _buildSupportOption(
BuildContext context, {
required IconData icon,
required String title,
required String subtitle,
required String url,
required Color color,
}) {
final theme = context.conduitTheme;
return _ProfileSettingTile(
onTap: () => _openExternalLink(context, url),
leading: _buildIconBadge(context, icon, color: color),
title: title,
subtitle: subtitle,
trailing: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.arrow_up_right,
android: Icons.open_in_new,
),
color: theme.iconSecondary,
size: IconSize.small,
),
);
}
Future<void> _openExternalLink(BuildContext context, String url) async {
try {
final launched = await launchUrlString(
url,
mode: LaunchMode.externalApplication,
);
if (!launched && context.mounted) {
UiUtils.showMessage(
context,
AppLocalizations.of(context)!.errorMessage,
);
}
} on PlatformException catch (_) {
if (!context.mounted) return;
UiUtils.showMessage(context, AppLocalizations.of(context)!.errorMessage);
} catch (_) {
if (!context.mounted) return;
UiUtils.showMessage(context, AppLocalizations.of(context)!.errorMessage);
}
}
Widget _buildProfileHeader(
BuildContext context,
dynamic user,
@@ -551,8 +445,8 @@ class ProfilePage extends ConsumerWidget {
try {
final info = await PackageInfo.fromPlatform();
// Update dialog with dynamic version each time
// GitHub repo URL source of truth
const githubUrl = 'https://github.com/cogwheel0/conduit';
// Developer / app info URL
const githubUrl = 'https://iiEasy.ru';
if (!context.mounted) return;
await showDialog<void>(
@@ -606,6 +500,13 @@ class ProfilePage extends ConsumerWidget {
],
),
),
const SizedBox(height: Spacing.sm),
Text(
'Based on Conduit',
style: ctx.conduitTheme.bodySmall?.copyWith(
color: ctx.sidebarTheme.foreground.withValues(alpha: 0.6),
),
),
],
),
actions: [

View File

@@ -1,6 +1,7 @@
{
"@@locale": "de",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "Zukunft. Einfach.",
"retry": "Erneut versuchen",
"back": "Zurück",
"you": "Du",
@@ -15,8 +16,8 @@
"description": "Untertitel mit den verfügbaren Aktionen, wenn der Server nicht erreichbar ist"
},
"account": "Konto",
"supportConduit": "Conduit unterstützen",
"supportConduitSubtitle": "Hilf, die Weiterentwicklung und neue Funktionen zu finanzieren.",
"supportConduit": "iiEasy unterstützen",
"supportConduitSubtitle": "Unterstütze iiEasy durch Finanzierung der Weiterentwicklung.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "Werde monatliche*r Sponsor*in und unterstütze die Roadmap.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "Passwort",
"signInWithToken": "Mit Token anmelden",
"connectToServer": "Mit Server verbinden",
"enterServerAddress": "Gib die Adresse deines Open-WebUI-Servers ein, um zu beginnen",
"enterServerAddress": "Gib die Adresse deines iiEasyWeb-Servers ein, um zu beginnen",
"serverUrl": "Server-URL",
"serverUrlHint": "https://dein-server.com",
"enterServerUrlSemantic": "Gib deine Server-URL oder IP-Adresse ein",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "Tippe auf \"Starten\", um zu beginnen",
"voiceActionStop": "Stopp",
"voiceActionStart": "Starten",
"messageHintText": "Frag Conduit",
"messageHintText": "Frag iiEasy",
"stopGenerating": "Generierung stoppen",
"codeCopiedToClipboard": "Code in die Zwischenablage kopiert.",
"send": "Senden",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "Dieser Ordner und seine Zuordnungen werden entfernt.",
"failedToDeleteFolder": "Ordner konnte nicht gelöscht werden",
"aboutApp": "Über",
"aboutAppSubtitle": "Conduit Informationen und Links",
"aboutAppSubtitle": "iiEasy Informationen und Links",
"web": "Web",
"imageGen": "Bild-Gen",
"pinned": "Angeheftet",
@@ -280,7 +281,7 @@
"themeLight": "Hell",
"currentlyUsingDarkTheme": "Aktuell dunkles Thema",
"currentlyUsingLightTheme": "Aktuell helles Thema",
"aboutConduit": "Über Conduit",
"aboutConduit": "Über iiEasy",
"versionLabel": "Version: {version} ({build})",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "Sprache zu Text",
"sttEngineLabel": "Erkennungs-Engine",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Schlichtes neutrales Design für Conduit.",
"themePaletteConduitDescription": "Schlichtes neutrales Design für iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,9 +1,13 @@
{
"@@locale": "en",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"@appTitle": {
"description": "Application name displayed in the app and OS UI."
},
"appSlogan": "Future. Simple.",
"@appSlogan": {
"description": "Brand slogan shown on loading/splash."
},
"retry": "Retry",
"@retry": {
"description": "Button label to try an action again."
@@ -36,11 +40,11 @@
"@account": {
"description": "Section header for account-related options."
},
"supportConduit": "Support Conduit",
"supportConduit": "Support iiEasy",
"@supportConduit": {
"description": "Section header inviting the user to financially support the project."
},
"supportConduitSubtitle": "Keep Conduit independent by funding ongoing development.",
"supportConduitSubtitle": "Keep iiEasy independent by funding ongoing development.",
"@supportConduitSubtitle": {
"description": "Subtitle explaining why donations are helpful."
},
@@ -232,7 +236,7 @@
"@connectToServer": {
"description": "Call-to-action button for server connection."
},
"enterServerAddress": "Enter your Open-WebUI server address to get started",
"enterServerAddress": "Enter your iiEasyWeb server address to get started",
"@enterServerAddress": {
"description": "Instruction telling user to provide server URL to begin."
},
@@ -554,7 +558,7 @@
"@voiceCallErrorHelp": {
"description": "Guidance shown when the voice call encounters an error."
},
"messageHintText": "Ask Conduit",
"messageHintText": "Ask iiEasy",
"@messageHintText": {
"description": "Short placeholder text in the message input."
},
@@ -920,7 +924,7 @@
"@aboutApp": {
"description": "Settings tile title to view app information."
},
"aboutAppSubtitle": "Conduit information and links",
"aboutAppSubtitle": "iiEasy information and links",
"@aboutAppSubtitle": {
"description": "Subtitle/description for the About section."
},
@@ -1173,11 +1177,11 @@
"@themePalette": {
"description": "Title for selecting the app color palette."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Clean neutral theme designed for Conduit.",
"themePaletteConduitDescription": "Clean neutral theme designed for iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},
@@ -1225,7 +1229,7 @@
"@currentlyUsingLightTheme": {
"description": "Status text indicating light theme is active."
},
"aboutConduit": "About Conduit",
"aboutConduit": "About iiEasy",
"@aboutConduit": {
"description": "Dialog title for app information."
},
@@ -1241,7 +1245,7 @@
}
}
},
"githubRepository": "GitHub Repository",
"githubRepository": "iiEasy.ru",
"@githubRepository": {
"description": "Link label pointing to the app repository."
},
@@ -1325,7 +1329,7 @@
"@androidAssistantOverlayOption": {
"description": "Option label for keeping the current assistant overlay."
},
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"@androidAssistantNewChatOption": {
"description": "Option label for opening the app to a fresh chat from the assistant trigger."
},

View File

@@ -1,6 +1,7 @@
{
"@@locale": "es",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "Futuro. Simple.",
"retry": "Reintentar",
"back": "Atrás",
"you": "Tú",
@@ -15,8 +16,8 @@
"description": "Subtítulo que explica las acciones disponibles cuando no se puede acceder al servidor"
},
"account": "Cuenta",
"supportConduit": "Apoyar Conduit",
"supportConduitSubtitle": "Mantén Conduit independiente financiando el desarrollo continuo.",
"supportConduit": "Apoyar iiEasy",
"supportConduitSubtitle": "Mantén iiEasy independiente financiando el desarrollo continuo.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "Conviértete en un patrocinador recurrente para financiar elementos del roadmap.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "Contraseña",
"signInWithToken": "Iniciar sesión con token",
"connectToServer": "Conectar al servidor",
"enterServerAddress": "Ingresa la dirección de tu servidor Open-WebUI para comenzar",
"enterServerAddress": "Ingresa la dirección de tu servidor iiEasyWeb para comenzar",
"serverUrl": "URL del servidor",
"serverUrlHint": "https://tu-servidor.com",
"enterServerUrlSemantic": "Ingresa la URL o dirección IP de tu servidor",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "Toca Iniciar para comenzar",
"voiceActionStop": "Detener",
"voiceActionStart": "Iniciar",
"messageHintText": "Pregunta a Conduit",
"messageHintText": "Pregunta a iiEasy",
"stopGenerating": "Detener generación",
"codeCopiedToClipboard": "Código copiado al portapapeles.",
"send": "Enviar",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "Esta carpeta y sus referencias de asignación se eliminarán.",
"failedToDeleteFolder": "No se pudo eliminar la carpeta",
"aboutApp": "Acerca de",
"aboutAppSubtitle": "Información y enlaces de Conduit",
"aboutAppSubtitle": "Información y enlaces de iiEasy",
"web": "Web",
"imageGen": "Generación de imágenes",
"pinned": "Anclado",
@@ -280,7 +281,7 @@
"themeLight": "Claro",
"currentlyUsingDarkTheme": "Usando actualmente el tema oscuro",
"currentlyUsingLightTheme": "Usando actualmente el tema claro",
"aboutConduit": "Acerca de Conduit",
"aboutConduit": "Acerca de iiEasy",
"versionLabel": "Versión: {version} ({build})",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "Voz a texto",
"sttEngineLabel": "Motor de reconocimiento",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Tema neutro y limpio diseñado para Conduit.",
"themePaletteConduitDescription": "Tema neutro y limpio diseñado para iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,6 +1,7 @@
{
"@@locale": "fr",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "Avenir. Simple.",
"retry": "Réessayer",
"back": "Retour",
"you": "Vous",
@@ -15,8 +16,8 @@
"description": "Sous-titre expliquant les actions possibles quand le serveur est injoignable"
},
"account": "Compte",
"supportConduit": "Soutenir Conduit",
"supportConduitSubtitle": "Financez le développement continu et les nouvelles fonctionnalités.",
"supportConduit": "Soutenir iiEasy",
"supportConduitSubtitle": "Soutenez iiEasy en finançant le développement continu.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "Devenez sponsor récurrent pour soutenir la feuille de route.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "Mot de passe",
"signInWithToken": "Se connecter avec un jeton",
"connectToServer": "Se connecter au serveur",
"enterServerAddress": "Saisissez l'adresse de votre serveur Open-WebUI pour commencer",
"enterServerAddress": "Saisissez l'adresse de votre serveur iiEasyWeb pour commencer",
"serverUrl": "URL du serveur",
"serverUrlHint": "https://votre-serveur.com",
"enterServerUrlSemantic": "Saisissez l'URL ou l'adresse IP de votre serveur",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "Appuyez sur \"Démarrer\" pour commencer",
"voiceActionStop": "Arrêter",
"voiceActionStart": "Démarrer",
"messageHintText": "Demander à Conduit",
"messageHintText": "Demander à iiEasy",
"stopGenerating": "Arrêter la génération",
"codeCopiedToClipboard": "Code copié dans le presse-papiers.",
"send": "Envoyer",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "Ce dossier et ses associations seront supprimés.",
"failedToDeleteFolder": "Échec de la suppression du dossier",
"aboutApp": "À propos",
"aboutAppSubtitle": "Informations et liens Conduit",
"aboutAppSubtitle": "Informations et liens iiEasy",
"web": "Web",
"imageGen": "Gén. image",
"pinned": "Épinglé",
@@ -280,7 +281,7 @@
"themeLight": "Clair",
"currentlyUsingDarkTheme": "Thème sombre actuellement utilisé",
"currentlyUsingLightTheme": "Thème clair actuellement utilisé",
"aboutConduit": "À propos de Conduit",
"aboutConduit": "À propos de iiEasy",
"versionLabel": "Version : {version} ({build})",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "Voix vers texte",
"sttEngineLabel": "Moteur de reconnaissance",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Thème neutre et épuré conçu pour Conduit.",
"themePaletteConduitDescription": "Thème neutre et épuré conçu pour iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,6 +1,7 @@
{
"@@locale": "it",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "Futuro. Semplice.",
"retry": "Riprova",
"back": "Indietro",
"you": "Tu",
@@ -15,8 +16,8 @@
"description": "Sottotitolo che spiega le azioni disponibili quando il server non è raggiungibile"
},
"account": "Account",
"supportConduit": "Sostieni Conduit",
"supportConduitSubtitle": "Mantieni Conduit indipendente finanziando lo sviluppo continuo.",
"supportConduit": "Sostieni iiEasy",
"supportConduitSubtitle": "Mantieni iiEasy indipendente finanziando lo sviluppo continuo.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "Diventa sponsor ricorrente per supportare la roadmap.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "Password",
"signInWithToken": "Accedi con token",
"connectToServer": "Connetti al server",
"enterServerAddress": "Inserisci l'indirizzo del server Open-WebUI per iniziare",
"enterServerAddress": "Inserisci l'indirizzo del server iiEasyWeb per iniziare",
"serverUrl": "URL del server",
"serverUrlHint": "https://tuo-server.com",
"enterServerUrlSemantic": "Inserisci l'URL o l'indirizzo IP del server",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "Tocca \"Avvia\" per iniziare",
"voiceActionStop": "Stop",
"voiceActionStart": "Avvia",
"messageHintText": "Chiedi a Conduit",
"messageHintText": "Chiedi a iiEasy",
"stopGenerating": "Interrompi generazione",
"codeCopiedToClipboard": "Codice copiato negli appunti.",
"send": "Invia",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "Questa cartella e le sue associazioni verranno rimosse.",
"failedToDeleteFolder": "Impossibile eliminare la cartella",
"aboutApp": "Informazioni",
"aboutAppSubtitle": "Informazioni e link di Conduit",
"aboutAppSubtitle": "Informazioni e link di iiEasy",
"web": "Web",
"imageGen": "Gen. immagini",
"pinned": "Fissati",
@@ -280,7 +281,7 @@
"themeLight": "Chiaro",
"currentlyUsingDarkTheme": "Attualmente tema scuro",
"currentlyUsingLightTheme": "Attualmente tema chiaro",
"aboutConduit": "Informazioni su Conduit",
"aboutConduit": "Informazioni su iiEasy",
"versionLabel": "Versione: {version} ({build})",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "Voce in testo",
"sttEngineLabel": "Motore di riconoscimento",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Tema neutro e pulito progettato per Conduit.",
"themePaletteConduitDescription": "Tema neutro e pulito progettato per iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

Some files were not shown because too many files have changed in this diff Show More