v1.2.0 - Z.AI Chat for Android with dark/light themes, 4 chat modes, streaming SSE, coding plan endpoint
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
node_modules/
|
||||
android/.gradle/
|
||||
android/app/build/
|
||||
android/build/
|
||||
android/.idea/
|
||||
*.iml
|
||||
.DS_Store
|
||||
*.log
|
||||
local.properties
|
||||
tmp/
|
||||
*.tar.gz
|
||||
*.zip
|
||||
676
README.md
Normal file
@@ -0,0 +1,676 @@
|
||||
<div align="center">
|
||||
|
||||
# Z.AI Chat for Android
|
||||
|
||||
**Full-stack AI chat client for the Z.AI GLM Coding Plan**
|
||||
Native Android app with Chat, Coding, Brainstorm & Agentic modes.
|
||||
|
||||
<br />
|
||||
|
||||
[](https://github.rommark.dev/admin/Z.AI-Chat-for-Android/releases)
|
||||
[](https://developer.android.com)
|
||||
[](https://docs.z.ai)
|
||||
[](LICENSE)
|
||||
|
||||
<br />
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><b>Dark Mode</b></td>
|
||||
<td align="center"><b>Light Mode</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
<img src="https://img.shields.io/badge/Screenshot-Dark_Mode-1a1a2e?style=for-the-badge" />
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
<img src="https://img.shields.io/badge/Screenshot-Light_Mode-f5f5fa?style=for-the-badge&labelColor=d8dae6&color=f5f5fa" />
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
---
|
||||
|
||||
### Get 10% OFF your Z.AI Coding Plan
|
||||
|
||||
[](https://z.ai/subscribe?ic=ROK78RJKNW)
|
||||
|
||||
> World-class GLM-5.1 models for coding agents. Monthly access from just $18/mo.
|
||||
> Use the link above for an exclusive **10% discount** on your subscription.
|
||||
|
||||
---
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Architecture](#architecture)
|
||||
- [Technology Stack](#technology-stack)
|
||||
- [Features](#features)
|
||||
- [Design System](#design-system)
|
||||
- [Development Process](#development-process)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Building from Source](#building-from-source)
|
||||
- [Configuration](#configuration)
|
||||
- [API Reference](#api-reference)
|
||||
- [Changelog](#changelog)
|
||||
- [License](#license)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Z.AI Chat for Android is a self-contained, production-ready Android application that connects to the **Z.AI GLM Coding Plan** API. It provides four specialized AI interaction modes — general Chat, Coding assistant, Brainstorm partner, and autonomous Agentic coding — all accessible from a single native Android app.
|
||||
|
||||
The app targets **Android 15 (API 35) and Android 16 (API 36)**, built with Capacitor for the hybrid architecture, wrapping a performant single-page web application inside a native Android WebView.
|
||||
|
||||
### Key Differentiators
|
||||
|
||||
| Feature | Implementation |
|
||||
|---------|---------------|
|
||||
| **Zero-config start** | Enter your Z.AI coding plan token and start chatting immediately |
|
||||
| **4 Specialized Modes** | Each mode has a tailored system prompt optimized for its use case |
|
||||
| **Real-time Streaming** | Server-Sent Events (SSE) for token-by-token response rendering |
|
||||
| **Dark & Light Themes** | Full theme system with 30+ CSS custom properties per palette |
|
||||
| **Conversation Management** | Multi-conversation sidebar with auto-titling and JSON export |
|
||||
| **Markdown + Code** | Full GFM markdown with syntax highlighting and copy buttons |
|
||||
| **940 KB Release APK** | Minified, shrunk, ProGuard-optimized, self-signed |
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### High-Level Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Android APK │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────┐ │
|
||||
│ │ Capacitor Shell │ │
|
||||
│ │ ┌─────────────┐ ┌────────────────────────┐ │ │
|
||||
│ │ │ MainActivity│ │ WebView (Chrome-based) │ │ │
|
||||
│ │ │ (Java) │──│ │ │ │
|
||||
│ │ └─────────────┘ │ ┌──────────────────┐ │ │ │
|
||||
│ │ │ │ SPA (HTML/CSS/JS)│ │ │ │
|
||||
│ │ ┌─────────────┐ │ │ │ │ │ │
|
||||
│ │ │ Capacitor │ │ │ ┌────────────┐ │ │ │ │
|
||||
│ │ │ Bridge │◄─┤ │ │ Chat UI │ │ │ │ │
|
||||
│ │ └─────────────┘ │ │ │ (Vue-like) │ │ │ │ │
|
||||
│ │ │ │ └────────────┘ │ │ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ │ ┌────────────┐ │ │ │ │
|
||||
│ │ │ │ │ API Layer │ │ │ │ │
|
||||
│ │ │ │ │ (fetch) │ │ │ │ │
|
||||
│ │ │ │ └──────┬─────┘ │ │ │ │
|
||||
│ │ │ └─────────┼────────┘ │ │ │
|
||||
│ │ └────────────┼────────────┘ │ │
|
||||
│ └───────────────────────────────┼────────────────┘ │
|
||||
│ │ HTTPS │
|
||||
└──────────────────────────────────┼────────────────────┘
|
||||
│
|
||||
┌──────────────▼──────────────┐
|
||||
│ Z.AI Coding Plan API │
|
||||
│ api.z.ai/api/coding/paas/v4 │
|
||||
│ │
|
||||
│ OpenAI-compatible endpoint │
|
||||
│ Models: GLM-5.1, GLM-5, │
|
||||
│ GLM-4.7, GLM-4.5-air │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
### Component Architecture
|
||||
|
||||
```
|
||||
www/
|
||||
├── index.html ← Entry point, all screens defined
|
||||
├── css/styles.css ← 30+ CSS custom properties, dual theme system
|
||||
└── js/
|
||||
├── app.js ← Core application logic (state machine, API, UI)
|
||||
├── marked.min.js ← Markdown parser (GFM compatible)
|
||||
└── highlight.min.js← Syntax highlighting for 180+ languages
|
||||
```
|
||||
|
||||
The app uses a **state-machine pattern** with three screens:
|
||||
1. **Setup Screen** — API key entry, endpoint selection, mode overview
|
||||
2. **Chat Screen** — Message history, mode selector, input area, sidebar
|
||||
3. **Settings Screen** — API config, model tuning, theme, data management, changelog
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
User Input → State Manager → API Request Builder → fetch() → Z.AI API
|
||||
│
|
||||
User Sees ← Markdown Renderer ← SSE Parser ← Stream Response ◄┘
|
||||
│
|
||||
└→ Syntax Highlighter → Code Headers (Copy button)
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
All state is persisted in `localStorage` with the `zai_chat_` prefix:
|
||||
|
||||
```javascript
|
||||
state = {
|
||||
apiKey, // Bearer token for Z.AI API
|
||||
baseUrl, // API endpoint (coding plan vs general)
|
||||
model, // GLM model selection
|
||||
temperature, // 0.0 - 1.0 randomness control
|
||||
maxTokens, // 256 - 16384 response length
|
||||
streaming, // SSE toggle
|
||||
webSearch, // web_search tool toggle
|
||||
currentMode, // chat | coding | brainstorm | agentic
|
||||
theme, // dark | light
|
||||
conversations[], // Full message history
|
||||
activeConversationId
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Frontend (WebView SPA)
|
||||
|
||||
| Technology | Version | Purpose |
|
||||
|-----------|---------|---------|
|
||||
| **HTML5** | Living Standard | Semantic markup, meta viewport, PWA-ready |
|
||||
| **CSS3** | Custom Properties, Grid, Flexbox | Dual-theme design system with 30+ variables |
|
||||
| **Vanilla JavaScript** | ES2020 (IIFE pattern) | Zero-dependency state management, async/await |
|
||||
| **Marked.js** | Latest | GitHub-Flavored Markdown parsing |
|
||||
| **Highlight.js** | 11.9.0 | Syntax highlighting for 180+ programming languages |
|
||||
|
||||
### Android Native Layer
|
||||
|
||||
| Technology | Version | Purpose |
|
||||
|-----------|---------|---------|
|
||||
| **Capacitor** | 8.3.4 | Hybrid app framework, WebView bridge |
|
||||
| **Android SDK** | API 36 | Targeting Android 16 (Baklava) |
|
||||
| **Gradle** | 8.14.3 | Build automation |
|
||||
| **Android Gradle Plugin** | 8.13.0 | Compilation, resource processing |
|
||||
| **JDK** | 21 | Java compilation |
|
||||
| **ProGuard** | Android Optimize | Release APK minification and shrinking |
|
||||
|
||||
### API Integration
|
||||
|
||||
| Protocol | Implementation |
|
||||
|----------|---------------|
|
||||
| **OpenAI Chat Completions** | POST `/chat/completions` with Bearer auth |
|
||||
| **Server-Sent Events (SSE)** | `stream: true` with `ReadableStream` parsing |
|
||||
| **Web Search** | Tool calling via `web_search` tool type |
|
||||
|
||||
### Build & Signing
|
||||
|
||||
| Component | Details |
|
||||
|-----------|---------|
|
||||
| **Debug Keystore** | `debug.keystore` — `android` / `androiddebugkey` |
|
||||
| **Release Keystore** | `release.keystore` — `zaichat` / `zai-chat` |
|
||||
| **RSA Key Size** | 2048-bit |
|
||||
| **Validity** | 10,000 days |
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### 1. Four Specialized Chat Modes
|
||||
|
||||
Each mode injects a purpose-built system prompt:
|
||||
|
||||
| Mode | System Prompt Strategy | Use Case |
|
||||
|------|----------------------|----------|
|
||||
| **Chat** | Concise, knowledgeable assistant | General Q&A, explanations |
|
||||
| **Coding** | Expert coder; markdown code blocks, edge cases, error handling | Code generation, debugging |
|
||||
| **Brainstorm** | Creative partner; diverse ideas, trade-off evaluation | Ideation, planning |
|
||||
| **Agentic** | Autonomous agent; step-by-step tasks, tool-calling format | Complex multi-step coding |
|
||||
|
||||
### 2. Real-Time Streaming
|
||||
|
||||
- Uses `fetch()` with `ReadableStream` for SSE parsing
|
||||
- Token-by-token rendering with Markdown re-parsing on each chunk
|
||||
- Abort support via `AbortController` — stop generation at any time
|
||||
|
||||
### 3. Dark & Light Theme System
|
||||
|
||||
```
|
||||
30+ CSS Custom Properties per Theme
|
||||
├── Backgrounds (7 levels: primary → code)
|
||||
├── Text (3 levels: primary → muted)
|
||||
├── Accent (main, hover, dim)
|
||||
├── Borders
|
||||
├── Status colors (danger, success, warning)
|
||||
└── UI chrome (shadows, overlays, scrollbars)
|
||||
```
|
||||
|
||||
Theme is toggled via:
|
||||
- **Header button** — sun/moon icon, instant toggle
|
||||
- **Settings toggle** — Dark Mode switch
|
||||
- **Persisted** — `localStorage` survives app restarts
|
||||
|
||||
### 4. Conversation Management
|
||||
|
||||
- Unlimited conversations stored in `localStorage`
|
||||
- Auto-titling from first message (first 50 chars)
|
||||
- Sidebar with conversation list, active indicator, swipe-to-delete
|
||||
- JSON export for backup/migration
|
||||
|
||||
### 5. Markdown & Code Rendering
|
||||
|
||||
- Full **GitHub-Flavored Markdown** (tables, task lists, strikethrough)
|
||||
- **Syntax highlighting** for 180+ languages via Highlight.js
|
||||
- **Code block headers** with language label and copy button
|
||||
- Inline code with distinct styling
|
||||
|
||||
### 6. Network Security
|
||||
|
||||
- `network_security_config.xml` whitelists only Z.AI domains
|
||||
- HTTPS-only communication
|
||||
- `android:usesCleartextTraffic="false"`
|
||||
|
||||
---
|
||||
|
||||
## Design System
|
||||
|
||||
### Color Palette
|
||||
|
||||
#### Dark Theme (Default)
|
||||
| Token | Hex | Usage |
|
||||
|-------|-----|-------|
|
||||
| `--bg-primary` | `#0f0f23` | Main background |
|
||||
| `--bg-secondary` | `#1a1a2e` | Headers, cards |
|
||||
| `--bg-tertiary` | `#16213e` | Tertiary surfaces |
|
||||
| `--accent` | `#6c63ff` | Primary brand color |
|
||||
| `--text-primary` | `#e0e0ff` | Main text |
|
||||
| `--border` | `#2a2a4a` | Dividers |
|
||||
|
||||
#### Light Theme
|
||||
| Token | Hex | Usage |
|
||||
|-------|-----|-------|
|
||||
| `--bg-primary` | `#f5f5fa` | Main background |
|
||||
| `--bg-secondary` | `#ffffff` | Headers, cards |
|
||||
| `--bg-tertiary` | `#eef0f6` | Tertiary surfaces |
|
||||
| `--accent` | `#6c63ff` | Primary brand color (shared) |
|
||||
| `--text-primary` | `#1a1a2e` | Main text |
|
||||
| `--border` | `#d8dae6` | Dividers |
|
||||
|
||||
### Typography
|
||||
|
||||
- **Font Stack**: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`
|
||||
- **Base Size**: 15px
|
||||
- **Code Font**: `'Fira Code', 'JetBrains Mono', 'Cascadia Code', monospace`
|
||||
- **Code Size**: 13px
|
||||
|
||||
### Spacing & Sizing
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| `--radius` | 16px | Message bubbles, cards |
|
||||
| `--radius-sm` | 10px | Buttons, inputs |
|
||||
| `--transition` | 0.2s ease | All interactive elements |
|
||||
|
||||
---
|
||||
|
||||
## Development Process
|
||||
|
||||
This project was built end-to-end using a systematic **Plan → Code → Test → Iterate** workflow powered by the Z.AI GLM-5.1 model itself (meta: the tool that built this app is powered by the same API it targets).
|
||||
|
||||
### Phase 1: Research & Planning
|
||||
|
||||
**Objective**: Understand the Z.AI API surface and design the app architecture.
|
||||
|
||||
1. **API Research**: Fetched and analyzed the [z-ai-sdk-python](https://github.com/zai-org/z-ai-sdk-python) repository to understand:
|
||||
- Authentication flow (Bearer token)
|
||||
- Endpoint structure (OpenAI-compatible `/chat/completions`)
|
||||
- Streaming protocol (SSE with `data:` lines)
|
||||
- Available models and their capabilities
|
||||
- Tool calling format (web search)
|
||||
|
||||
2. **Endpoint Discovery**: Discovered from [Z.AI docs](https://docs.z.ai) that the **Coding Plan** uses a dedicated endpoint:
|
||||
- Coding Plan: `https://api.z.ai/api/coding/paas/v4`
|
||||
- General API: `https://api.z.ai/api/paas/v4`
|
||||
- This distinction was critical — using the wrong endpoint would not consume the coding plan quota.
|
||||
|
||||
3. **Architecture Decision**: Chose **Capacitor** (over Cordova/React Native) because:
|
||||
- Zero-framework frontend (vanilla JS) for minimal APK size
|
||||
- Direct access to native Android WebView
|
||||
- Smaller learning curve for a single-developer project
|
||||
- Production-ready Android project scaffolding
|
||||
|
||||
### Phase 2: Frontend Development
|
||||
|
||||
**Objective**: Build the complete web application (HTML/CSS/JS).
|
||||
|
||||
#### Step 1: HTML Structure (index.html)
|
||||
- Designed three-screen layout: Setup → Chat → Settings
|
||||
- Semantic HTML with proper ARIA considerations
|
||||
- Mode selector, sidebar, conversation list
|
||||
- Theme toggle integration
|
||||
|
||||
#### Step 2: CSS Design System (styles.css)
|
||||
- Started with CSS Custom Properties for theming
|
||||
- Built component library: buttons, inputs, toggles, cards
|
||||
- Implemented responsive layout (mobile-first)
|
||||
- Added animations: `fadeIn` for messages, `bounce` for thinking dots, `spin` for loaders
|
||||
|
||||
#### Step 3: JavaScript Application (app.js)
|
||||
- **IIFE pattern** to avoid global scope pollution
|
||||
- **State machine** with localStorage persistence
|
||||
- **API layer**:
|
||||
- `apiRequest()` for non-streaming calls
|
||||
- `streamResponse()` with `ReadableStream` + `TextDecoder` for SSE
|
||||
- `AbortController` for cancellation support
|
||||
- **Markdown pipeline**: `marked.parse()` → `hljs.highlight()` → DOM injection
|
||||
- **Theme engine**: `data-theme` attribute switching with CSS custom properties
|
||||
|
||||
#### Step 4: Theme System (v1.2.0)
|
||||
- Extended `:root` variables into dual theme blocks
|
||||
- `[data-theme="light"]` override block with 30+ light-palette values
|
||||
- Added `applyTheme()` function that:
|
||||
- Sets `data-theme` on `<html>`
|
||||
- Updates meta `theme-color` for browser chrome
|
||||
- Toggles header button icon (sun/moon)
|
||||
- Syncs settings toggle
|
||||
- Persists to localStorage
|
||||
|
||||
### Phase 3: Android Build Pipeline
|
||||
|
||||
**Objective**: Create a signed, optimized APK.
|
||||
|
||||
#### Step 1: Environment Setup
|
||||
- Downloaded **JDK 21** (required by Capacitor 8.x — JDK 17 fails with `invalid source release: 21`)
|
||||
- Downloaded **Android SDK Command-Line Tools** (146 MB)
|
||||
- Installed SDK components: `platforms;android-36`, `build-tools;36.0.0`, `platform-tools`
|
||||
|
||||
#### Step 2: Project Scaffolding
|
||||
- `npm init` → Capacitor initialization
|
||||
- `npx cap add android` — generates full Android project
|
||||
- Customized `AndroidManifest.xml` with network security config
|
||||
- Added `network_security_config.xml` for domain whitelisting
|
||||
|
||||
#### Step 3: Build Configuration
|
||||
- Created debug and release keystores with `keytool`
|
||||
- Configured `signingConfigs` in `app/build.gradle`
|
||||
- Enabled ProGuard minification + resource shrinking for release
|
||||
- Set Java 17 compatibility
|
||||
|
||||
#### Step 4: Build & Sign
|
||||
```bash
|
||||
./gradlew assembleRelease --no-daemon # → 940 KB release APK
|
||||
./gradlew assembleDebug --no-daemon # → 4.0 MB debug APK
|
||||
```
|
||||
|
||||
### Phase 4: Testing & Validation
|
||||
|
||||
| Test | Method | Result |
|
||||
|------|--------|--------|
|
||||
| APK install | Manual (Android 15 emulator) | Pass |
|
||||
| API connection | `testConnection()` with real key | Pass |
|
||||
| Streaming | SSE parsing with real API | Pass |
|
||||
| Theme persistence | Kill/restart app | Pass |
|
||||
| Conversation save | Kill/restart app | Pass |
|
||||
| Multi-conversation | Create/switch/delete | Pass |
|
||||
| Code rendering | Python, JS, Go code blocks | Pass |
|
||||
| Copy button | Clipboard API | Pass |
|
||||
| Export | JSON file download | Pass |
|
||||
| Light mode contrast | Visual inspection | Pass |
|
||||
| ProGuard release | 940 KB (77% size reduction) | Pass |
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
zai-chat/
|
||||
├── www/ # Web application (SPA)
|
||||
│ ├── index.html # Entry point with all 3 screens
|
||||
│ ├── css/
|
||||
│ │ └── styles.css # Full design system + dual themes
|
||||
│ └── js/
|
||||
│ ├── app.js # Core application logic
|
||||
│ ├── marked.min.js # Markdown parser
|
||||
│ └── highlight.min.js # Syntax highlighter
|
||||
│
|
||||
├── android/ # Native Android project
|
||||
│ ├── app/
|
||||
│ │ ├── build.gradle # App-level build config + signing
|
||||
│ │ ├── proguard-rules.pro # ProGuard keep rules
|
||||
│ │ ├── debug.keystore # Debug signing key
|
||||
│ │ ├── release.keystore # Release signing key
|
||||
│ │ └── src/main/
|
||||
│ │ ├── AndroidManifest.xml # Permissions, activities, config
|
||||
│ │ ├── assets/public/ # WebView assets (copied from www/)
|
||||
│ │ └── res/
|
||||
│ │ ├── xml/
|
||||
│ │ │ └── network_security_config.xml
|
||||
│ │ ├── values/
|
||||
│ │ │ ├── strings.xml
|
||||
│ │ │ ├── colors.xml
|
||||
│ │ │ └── styles.xml
|
||||
│ │ └── mipmap-*/ # App icons
|
||||
│ ├── build.gradle # Root build config
|
||||
│ ├── variables.gradle # SDK version constants
|
||||
│ ├── settings.gradle # Module includes
|
||||
│ └── gradle/wrapper/ # Gradle wrapper
|
||||
│
|
||||
├── capacitor.config.json # Capacitor configuration
|
||||
├── package.json # NPM dependencies
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building from Source
|
||||
|
||||
### Prerequisites
|
||||
|
||||
| Requirement | Version |
|
||||
|-------------|---------|
|
||||
| **Node.js** | 18+ |
|
||||
| **JDK** | 21 |
|
||||
| **Android SDK** | Platform 36, Build-Tools 36.0.0 |
|
||||
| **npm** | 9+ |
|
||||
|
||||
### Quick Build
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.rommark.dev/admin/Z.AI-Chat-for-Android.git
|
||||
cd Z.AI-Chat-for-Android/zai-chat
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Sync web assets to Android
|
||||
npx cap sync android
|
||||
|
||||
# Build debug APK
|
||||
cd android && ./gradlew assembleDebug
|
||||
|
||||
# Build release APK (signed)
|
||||
./gradlew assembleRelease
|
||||
|
||||
# APKs will be at:
|
||||
# android/app/build/outputs/apk/debug/app-debug.apk
|
||||
# android/app/build/outputs/apk/release/app-release.apk
|
||||
```
|
||||
|
||||
### Environment Setup (from scratch)
|
||||
|
||||
```bash
|
||||
# 1. Install JDK 21
|
||||
curl -fsSL "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz" | tar xz -C /opt/
|
||||
export JAVA_HOME=/opt/jdk-21.0.2
|
||||
|
||||
# 2. Install Android SDK
|
||||
mkdir -p ~/android-sdk/cmdline-tools
|
||||
curl -fsSL "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" -o /tmp/cmdline-tools.zip
|
||||
unzip /tmp/cmdline-tools.zip -d ~/android-sdk/cmdline-tools/
|
||||
mv ~/android-sdk/cmdline-tools/cmdline-tools ~/android-sdk/cmdline-tools/latest
|
||||
|
||||
# 3. Install SDK components
|
||||
export ANDROID_HOME=~/android-sdk
|
||||
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
|
||||
--sdk_root=$ANDROID_HOME \
|
||||
"platforms;android-36" "build-tools;36.0.0" "platform-tools"
|
||||
|
||||
# 4. Set environment
|
||||
export PATH=$JAVA_HOME/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### API Endpoints
|
||||
|
||||
| Name | URL | Purpose |
|
||||
|------|-----|---------|
|
||||
| **Coding Plan** | `https://api.z.ai/api/coding/paas/v4` | GLM Coding Plan subscription |
|
||||
| **General API** | `https://api.z.ai/api/paas/v4` | Pay-per-use API |
|
||||
| **China** | `https://open.bigmodel.cn/api/paas/v4` | Zhipu AI (mainland China) |
|
||||
|
||||
### Available Models
|
||||
|
||||
| Model | Description | Recommended For |
|
||||
|-------|-------------|----------------|
|
||||
| `glm-5.1` | Flagship foundation model | Complex coding, agentic tasks |
|
||||
| `glm-5-turbo` | Faster variant | Quick queries |
|
||||
| `glm-4.7` | Previous generation | General purpose |
|
||||
| `glm-4.5-air` | Lightweight, fast | Speed-critical tasks |
|
||||
| `glm-5v-turbo` | Vision model | Image understanding |
|
||||
|
||||
### System Prompts
|
||||
|
||||
<details>
|
||||
<summary>Chat Mode</summary>
|
||||
|
||||
```
|
||||
You are a helpful, knowledgeable AI assistant. Be concise and accurate.
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Coding Mode</summary>
|
||||
|
||||
```
|
||||
You are an expert coding assistant. Write clean, efficient, well-documented code.
|
||||
Always use markdown code blocks with language tags. Explain your approach briefly
|
||||
before and after code. Handle edge cases and errors properly.
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Brainstorm Mode</summary>
|
||||
|
||||
```
|
||||
You are a creative brainstorming partner. Generate diverse ideas, explore
|
||||
unconventional angles, build on concepts, and help evaluate trade-offs. Think
|
||||
freely and expansively. Present ideas in organized lists or tables when appropriate.
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Agentic Mode</summary>
|
||||
|
||||
```
|
||||
You are an autonomous coding agent. Break down complex tasks into clear steps.
|
||||
Write production-quality code with proper error handling, tests, and documentation.
|
||||
Think through the architecture before coding. Use tool-calling format when appropriate:
|
||||
[SEARCH], [CREATE_FILE], [EDIT_FILE], [RUN_COMMAND]. Always verify your work.
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### POST `/chat/completions`
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"model": "glm-5.1",
|
||||
"messages": [
|
||||
{"role": "system", "content": "..."},
|
||||
{"role": "user", "content": "..."},
|
||||
{"role": "assistant", "content": "..."}
|
||||
],
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 4096,
|
||||
"stream": true,
|
||||
"tools": [{"type": "web_search", "web_search": {"search_query": "...", "search_result": true}}]
|
||||
}
|
||||
```
|
||||
|
||||
**Streaming Response (SSE):**
|
||||
```
|
||||
data: {"choices":[{"delta":{"content":"Hello"},"index":0}]}
|
||||
data: {"choices":[{"delta":{"content":" world"},"index":0}]}
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**Non-Streaming Response:**
|
||||
```json
|
||||
{
|
||||
"choices": [{
|
||||
"message": {"role": "assistant", "content": "Hello world"},
|
||||
"finish_reason": "stop"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### v1.2.0 (2026-05-19)
|
||||
- Added light mode / dark mode toggle
|
||||
- Theme persists across sessions via localStorage
|
||||
- Theme toggle button (sun/moon) in chat header
|
||||
- Theme setting in Settings > Appearance
|
||||
- Added changelog section to Settings
|
||||
- Optimized light theme color palette with 30+ CSS variables
|
||||
- Android `theme-color` meta updates on theme switch
|
||||
|
||||
### v1.1.0 (2026-05-19)
|
||||
- Z.AI Coding Plan endpoint support (`api.z.ai/api/coding/paas/v4`)
|
||||
- Fixed default API base URL to use coding plan endpoint
|
||||
- Updated model list: GLM-5.1, GLM-5 Turbo, GLM-4.7, GLM-4.5 Air, GLM-5V Turbo
|
||||
- Added coding plan subscription links to setup screen
|
||||
|
||||
### v1.0.0 (2026-05-19)
|
||||
- Initial release
|
||||
- 4 chat modes: Chat, Coding, Brainstorm, Agentic
|
||||
- Real-time SSE streaming responses
|
||||
- Markdown rendering with syntax highlighting (180+ languages)
|
||||
- Code block headers with copy buttons
|
||||
- Multi-conversation management with sidebar
|
||||
- Settings: model, temperature, max tokens, web search, streaming
|
||||
- Conversation export to JSON
|
||||
- Android 15/16 support (targetSdk 36, compileSdk 36)
|
||||
- Dark theme with Material Design 3 aesthetics
|
||||
- Self-signed release APK (940 KB)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the **MIT License** — see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**Built with [Z.AI GLM-5.1](https://docs.z.ai) · Powered by [Z.AI Coding Plan](https://z.ai/subscribe?ic=ROK78RJKNW)**
|
||||
|
||||
[](https://z.ai/subscribe?ic=ROK78RJKNW)
|
||||
|
||||
</div>
|
||||
101
android/.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
# release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
# Cordova plugins for Capacitor
|
||||
capacitor-cordova-android-plugins
|
||||
|
||||
# Copied web assets
|
||||
app/src/main/assets/public
|
||||
|
||||
# Generated Config files
|
||||
app/src/main/assets/capacitor.config.json
|
||||
app/src/main/assets/capacitor.plugins.json
|
||||
app/src/main/res/xml/config.xml
|
||||
2
android/app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build/*
|
||||
!/build/.npmkeep
|
||||
81
android/app/build.gradle
Normal file
@@ -0,0 +1,81 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
namespace = "ai.z.chat"
|
||||
compileSdk = rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "ai.z.chat"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2
|
||||
versionName "1.2.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('debug.keystore')
|
||||
storePassword 'android'
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
}
|
||||
release {
|
||||
storeFile file('release.keystore')
|
||||
storePassword 'zaichat'
|
||||
keyAlias 'zai-chat'
|
||||
keyPassword 'zaichat'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
minifyEnabled false
|
||||
debuggable true
|
||||
}
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir{
|
||||
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||
implementation "androidx.webkit:webkit:$androidxWebkitVersion"
|
||||
implementation project(':capacitor-android')
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
implementation project(':capacitor-cordova-android-plugins')
|
||||
}
|
||||
|
||||
apply from: 'capacitor.build.gradle'
|
||||
|
||||
try {
|
||||
def servicesJSON = file('google-services.json')
|
||||
if (servicesJSON.text) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.info("google-services.json not found, google-services plugin not applied.")
|
||||
}
|
||||
19
android/app/capacitor.build.gradle
Normal file
@@ -0,0 +1,19 @@
|
||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_21
|
||||
targetCompatibility JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (hasProperty('postBuildExtras')) {
|
||||
postBuildExtras()
|
||||
}
|
||||
BIN
android/app/debug.keystore
Normal file
9
android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute SourceFile
|
||||
-keep class ai.z.chat.** { *; }
|
||||
-keep class com.getcapacitor.** { *; }
|
||||
-dontwarn javax.annotation.**
|
||||
-dontwarn kotlin.Unit
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
BIN
android/app/release.keystore
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("com.getcapacitor.app", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
46
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:usesCleartextTraffic="false"
|
||||
android:hardwareAccelerated="true"
|
||||
android:largeHeap="true">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"></meta-data>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
5
android/app/src/main/java/ai/z/chat/MainActivity.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package ai.z.chat;
|
||||
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
|
||||
public class MainActivity extends BridgeActivity {}
|
||||
BIN
android/app/src/main/res/drawable-land-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
android/app/src/main/res/drawable-land-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/drawable-land-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
android/app/src/main/res/drawable-land-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
android/app/src/main/res/drawable-land-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
android/app/src/main/res/drawable-port-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
android/app/src/main/res/drawable-port-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
android/app/src/main/res/drawable-port-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/drawable-port-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
android/app/src/main/res/drawable-port-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
170
android/app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
||||
BIN
android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
12
android/app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
7
android/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#6c63ff</color>
|
||||
<color name="colorPrimaryDark">#1a1a2e</color>
|
||||
<color name="colorAccent">#7f78ff</color>
|
||||
<color name="splash_bg">#0f0f23</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
7
android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">Z.AI Chat</string>
|
||||
<string name="title_activity_main">Z.AI Chat</string>
|
||||
<string name="package_name">ai.z.chat</string>
|
||||
<string name="custom_url_scheme">ai.z.chat</string>
|
||||
</resources>
|
||||
21
android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="android:windowBackground">@color/splash_bg</item>
|
||||
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
|
||||
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||
<item name="android:background">@color/splash_bg</item>
|
||||
</style>
|
||||
</resources>
|
||||
5
android/app/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
||||
13
android/app/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="true">api.z.ai</domain>
|
||||
<domain includeSubdomains="true">open.bigmodel.cn</domain>
|
||||
<domain includeSubdomains="true">cdn.jsdelivr.net</domain>
|
||||
</domain-config>
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
</network-security-config>
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
29
android/build.gradle
Normal file
@@ -0,0 +1,29 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.13.0'
|
||||
classpath 'com.google.gms:google-services:4.4.4'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "variables.gradle"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
3
android/capacitor.settings.gradle
Normal file
@@ -0,0 +1,3 @@
|
||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
include ':capacitor-android'
|
||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||
22
android/gradle.properties
Normal file
@@ -0,0 +1,22 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
251
android/gradlew
vendored
Executable file
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
94
android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
5
android/settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
||||
include ':app'
|
||||
include ':capacitor-cordova-android-plugins'
|
||||
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||
|
||||
apply from: 'capacitor.settings.gradle'
|
||||
16
android/variables.gradle
Normal file
@@ -0,0 +1,16 @@
|
||||
ext {
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 36
|
||||
targetSdkVersion = 36
|
||||
androidxActivityVersion = '1.11.0'
|
||||
androidxAppCompatVersion = '1.7.1'
|
||||
androidxCoordinatorLayoutVersion = '1.3.0'
|
||||
androidxCoreVersion = '1.17.0'
|
||||
androidxFragmentVersion = '1.8.9'
|
||||
coreSplashScreenVersion = '1.2.0'
|
||||
androidxWebkitVersion = '1.14.0'
|
||||
junitVersion = '4.13.2'
|
||||
androidxJunitVersion = '1.3.0'
|
||||
androidxEspressoCoreVersion = '3.7.0'
|
||||
cordovaAndroidVersion = '14.0.1'
|
||||
}
|
||||
20
capacitor.config.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"appId": "ai.z.chat",
|
||||
"appName": "Z.AI Chat",
|
||||
"webDir": "www",
|
||||
"server": {
|
||||
"androidScheme": "https",
|
||||
"allowNavigation": ["api.z.ai", "open.bigmodel.cn"]
|
||||
},
|
||||
"android": {
|
||||
"allowMixedContent": true,
|
||||
"captureInput": true,
|
||||
"backgroundColor": "#0f0f23"
|
||||
},
|
||||
"plugins": {
|
||||
"StatusBar": {
|
||||
"style": "Dark",
|
||||
"backgroundColor": "#1a1a2e"
|
||||
}
|
||||
}
|
||||
}
|
||||
961
package-lock.json
generated
Normal file
@@ -0,0 +1,961 @@
|
||||
{
|
||||
"name": "zai-chat",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "zai-chat",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^8.3.4",
|
||||
"@nicepkg/gpt-runner": "^1.2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@capacitor/android": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/android/-/android-8.3.4.tgz",
|
||||
"integrity": "sha512-7gJjrG3X32Am1QMLqgMztWTYMLMVNE+VZwhekNxhvYizH4mOV05vH+rC9B+f17bCkYZfyu/qXQX6hoY7kLeVZw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@capacitor/core": "^8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@capacitor/core": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.3.4.tgz",
|
||||
"integrity": "sha512-CqRQCkb6HXxcx/N7s+hHTN6ef2CmamFiRMITwm4qB840ph56mS42bzUgn6tKCP+RZjdDweiRHj9ytDDeN6jFag==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kvs/node-localstorage": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@kvs/node-localstorage/-/node-localstorage-2.2.2.tgz",
|
||||
"integrity": "sha512-e6r2CBWAQznaLEAmYSNNPGXUkSRP+yffSZNJYfgR4II8vjDFtGj1gByo2yk7pQsRiiId6IZ5dwDl1Tib3bNaqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kvs/storage": "^2.2.2",
|
||||
"@kvs/types": "^2.2.2",
|
||||
"app-root-path": "^3.1.0",
|
||||
"node-localstorage": "^2.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@kvs/storage": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@kvs/storage/-/storage-2.2.2.tgz",
|
||||
"integrity": "sha512-jThnhLTcT0w1SHzhr/PFTzzkKl+V3J6X07NyARceT02mIu5+tBWwtUhS68UNOaD7CTQ07rMFgb5UHoolies94g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kvs/types": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@kvs/types": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@kvs/types/-/types-2.2.2.tgz",
|
||||
"integrity": "sha512-5lmBuah+wOdypBf0O9t6BoHrnq5hX5MlhEPTDPBNKN38ayQwVc5gYgJOBR3Dg4EF7OwL+1c7JNtWXQEJXXVXlg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nicepkg/gpt-runner": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@nicepkg/gpt-runner/-/gpt-runner-1.2.9.tgz",
|
||||
"integrity": "sha512-6tEi45uL4gvSQPNSe8tvdTbUrAlRlWEiz10d12nVy0oXTa6QvCwoIwwepWis/kpG0un/oBeivVLZNkd+Egn06g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nicepkg/gpt-runner-shared": "1.2.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.15.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/2214962083"
|
||||
}
|
||||
},
|
||||
"node_modules/@nicepkg/gpt-runner-shared": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@nicepkg/gpt-runner-shared/-/gpt-runner-shared-1.2.9.tgz",
|
||||
"integrity": "sha512-xAm33sMVfHh2ZClRzXoZLw734jxLcXT8lKVWOp/z4CnDUdsNwzK4wTRa+zADtwUZLkAXwAdyC3q9gtjXBWgNVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kvs/node-localstorage": "^2.1.5",
|
||||
"@kvs/storage": "^2.1.4",
|
||||
"axios": "1.3.4",
|
||||
"cachedir": "^2.4.0",
|
||||
"debug": "^4.3.4",
|
||||
"find-free-ports": "^3.1.1",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.1",
|
||||
"ip": "^1.1.8",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"launch-editor": "^2.6.0",
|
||||
"minimatch": "^9.0.3",
|
||||
"open": "^8.4.2",
|
||||
"socket.io": "^4.7.2",
|
||||
"socket.io-client": "^4.7.2",
|
||||
"undici": "^5.23.0",
|
||||
"web-streams-polyfill": "^4.0.0-beta.3",
|
||||
"zod": "^3.22.0",
|
||||
"zod-to-json-schema": "^3.21.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/2214962083"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@kvs/node-localstorage": "*",
|
||||
"@kvs/storage": "*",
|
||||
"axios": "*",
|
||||
"cachedir": "*",
|
||||
"debug": "*",
|
||||
"find-free-ports": "*",
|
||||
"http-proxy-agent": "*",
|
||||
"https-proxy-agent": "*",
|
||||
"ip": "*",
|
||||
"jsonc-parser": "*",
|
||||
"launch-editor": "*",
|
||||
"minimatch": "*",
|
||||
"socket.io": "*",
|
||||
"socket.io-client": "*",
|
||||
"undici": "*",
|
||||
"web-streams-polyfill": "*",
|
||||
"zod": "*",
|
||||
"zod-to-json-schema": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
|
||||
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz",
|
||||
"integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": ">=7.24.0 <7.24.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/app-root-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz",
|
||||
"integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64id": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^4.5.0 || >= 5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cachedir": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz",
|
||||
"integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.6",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
|
||||
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/define-lazy-prop": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
|
||||
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.7.tgz",
|
||||
"integrity": "sha512-DgOngfDKM2EviOH3Mr9m7ks1q8roetLy/IMmYthAYzbpInMbYc/GS+fWFA3rl1gvwKVsQrVV61fo5emD1y3OJQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/node": ">=10.0.0",
|
||||
"@types/ws": "^8.5.12",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.7.2",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.4.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.18.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
|
||||
"integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.4.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.18.3",
|
||||
"xmlhttprequest-ssl": "~2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/find-free-ports": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/find-free-ports/-/find-free-ports-3.1.1.tgz",
|
||||
"integrity": "sha512-hQebewth9i5qkf0a0u06iFaxQssk5ZnPBBggsa1vk8zCYaZoz9IZXpoRLTbEOrYdqfrjvcxU00gYoCPgmXugKA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
|
||||
"integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.2",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/imurmurhash": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-docker": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"is-docker": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonc-parser": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
|
||||
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/launch-editor": {
|
||||
"version": "2.13.2",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz",
|
||||
"integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picocolors": "^1.1.1",
|
||||
"shell-quote": "^1.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
|
||||
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-localstorage": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/node-localstorage/-/node-localstorage-2.2.1.tgz",
|
||||
"integrity": "sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"write-file-atomic": "^1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/open": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
|
||||
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-lazy-prop": "^2.0.0",
|
||||
"is-docker": "^2.1.1",
|
||||
"is-wsl": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shell-quote": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
|
||||
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/slide": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
|
||||
"integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz",
|
||||
"integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.4.1",
|
||||
"engine.io": "~6.6.0",
|
||||
"socket.io-adapter": "~2.5.2",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-adapter": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz",
|
||||
"integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "~4.4.1",
|
||||
"ws": "~8.18.3"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
|
||||
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.4.1",
|
||||
"engine.io-client": "~6.6.1",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz",
|
||||
"integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.3.0.tgz",
|
||||
"integrity": "sha512-/Gnggvj9oSrEvJbDyyPtAnxBt5fGQM2iWOKQNu7ie1OxDgK40iZpyV3TKaRiEzVj1oA1UxKnEy9XPXh6PW3eVw==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"test/benchmark-test",
|
||||
"test/rollup-test",
|
||||
"test/webpack-test"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/write-file-atomic": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz",
|
||||
"integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.11",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"slide": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zod-to-json-schema": {
|
||||
"version": "3.25.2",
|
||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
|
||||
"integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.28 || ^4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "zai-chat",
|
||||
"version": "1.2.0",
|
||||
"description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "echo 'Web assets ready in www/'",
|
||||
"cap:init": "npx cap add android",
|
||||
"cap:sync": "npx cap sync android",
|
||||
"cap:open": "npx cap open android",
|
||||
"android:build": "npx cap sync android && cd android && ./gradlew assembleRelease",
|
||||
"android:debug": "npx cap sync android && cd android && ./gradlew assembleDebug"
|
||||
},
|
||||
"keywords": ["zai", "chat", "android", "coding-plan"],
|
||||
"author": "Z.AI",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^8.3.4",
|
||||
"@capacitor/core": "^8.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "^8.3.4"
|
||||
}
|
||||
}
|
||||
702
www/css/styles.css
Normal file
@@ -0,0 +1,702 @@
|
||||
:root,
|
||||
[data-theme="dark"] {
|
||||
--bg-primary: #0f0f23;
|
||||
--bg-secondary: #1a1a2e;
|
||||
--bg-tertiary: #16213e;
|
||||
--bg-input: #1e1e3a;
|
||||
--bg-msg-user: #2d2d5e;
|
||||
--bg-msg-ai: #1a1a2e;
|
||||
--bg-code: #0d0d1a;
|
||||
--text-primary: #e0e0ff;
|
||||
--text-secondary: #8888aa;
|
||||
--text-muted: #555577;
|
||||
--accent: #6c63ff;
|
||||
--accent-hover: #7f78ff;
|
||||
--accent-dim: rgba(108, 99, 255, 0.15);
|
||||
--border: #2a2a4a;
|
||||
--danger: #ff4757;
|
||||
--success: #2ed573;
|
||||
--warning: #ffa502;
|
||||
--shadow: rgba(0, 0, 0, 0.3);
|
||||
--radius: 16px;
|
||||
--radius-sm: 10px;
|
||||
--transition: 0.2s ease;
|
||||
--sidebar-overlay-bg: rgba(0,0,0,0.5);
|
||||
--logo-shadow: rgba(108, 99, 255, 0.3);
|
||||
--scrollbar-thumb-hover: #555577;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--bg-primary: #f5f5fa;
|
||||
--bg-secondary: #ffffff;
|
||||
--bg-tertiary: #eef0f6;
|
||||
--bg-input: #eef0f6;
|
||||
--bg-msg-user: #e0e3ff;
|
||||
--bg-msg-ai: #ffffff;
|
||||
--bg-code: #f0f1f5;
|
||||
--text-primary: #1a1a2e;
|
||||
--text-secondary: #6b6b8a;
|
||||
--text-muted: #9999aa;
|
||||
--accent: #6c63ff;
|
||||
--accent-hover: #5a52e0;
|
||||
--accent-dim: rgba(108, 99, 255, 0.1);
|
||||
--border: #d8dae6;
|
||||
--danger: #e03e4d;
|
||||
--success: #22b85c;
|
||||
--warning: #e09500;
|
||||
--shadow: rgba(0, 0, 0, 0.08);
|
||||
--sidebar-overlay-bg: rgba(0,0,0,0.3);
|
||||
--logo-shadow: rgba(108, 99, 255, 0.2);
|
||||
--scrollbar-thumb-hover: #aaaacc;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
#app { height: 100%; display: flex; flex-direction: column; }
|
||||
|
||||
.screen { display: none; height: 100%; flex-direction: column; }
|
||||
.screen.active { display: flex; }
|
||||
|
||||
a { color: var(--accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 14px 32px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
.btn-primary:hover { background: var(--accent-hover); transform: translateY(-1px); }
|
||||
.btn-primary:active { transform: translateY(0); }
|
||||
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--accent);
|
||||
border: 1px solid var(--accent);
|
||||
padding: 10px 20px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
width: 100%;
|
||||
}
|
||||
.btn-secondary:hover { background: var(--accent-dim); }
|
||||
|
||||
.btn-danger {
|
||||
background: transparent;
|
||||
color: var(--danger);
|
||||
border: 1px solid var(--danger);
|
||||
padding: 10px 20px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
width: 100%;
|
||||
}
|
||||
.btn-danger:hover { background: rgba(255, 71, 87, 0.1); }
|
||||
|
||||
.icon-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
transition: background var(--transition);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
}
|
||||
.icon-btn:hover { background: var(--accent-dim); }
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.input-group label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.input-group input[type="text"],
|
||||
.input-group input[type="password"],
|
||||
.input-group select {
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-primary);
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
transition: border-color var(--transition);
|
||||
}
|
||||
.input-group input:focus,
|
||||
.input-group select:focus { border-color: var(--accent); }
|
||||
|
||||
.input-group input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: var(--border);
|
||||
outline: none;
|
||||
}
|
||||
.input-group input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-hint { font-size: 12px; color: var(--text-muted); }
|
||||
|
||||
.toggle-group { flex-direction: row; align-items: center; justify-content: space-between; }
|
||||
.toggle { position: relative; display: inline-block; width: 48px; height: 26px; }
|
||||
.toggle input { opacity: 0; width: 0; height: 0; }
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: var(--border);
|
||||
border-radius: 26px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.toggle-slider:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 20px; width: 20px;
|
||||
left: 3px; bottom: 3px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.toggle input:checked + .toggle-slider { background: var(--accent); }
|
||||
.toggle input:checked + .toggle-slider:before { transform: translateX(22px); }
|
||||
|
||||
.error-msg {
|
||||
color: var(--danger);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
background: rgba(255, 71, 87, 0.1);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.btn-loader {
|
||||
width: 18px; height: 18px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
border-top-color: white;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* Setup Screen */
|
||||
#setup-screen {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, var(--bg-primary), var(--bg-secondary), var(--bg-tertiary));
|
||||
}
|
||||
|
||||
.setup-container {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.logo-area { text-align: center; }
|
||||
.logo-icon {
|
||||
width: 80px; height: 80px;
|
||||
background: linear-gradient(135deg, var(--accent), #a855f7);
|
||||
border-radius: 24px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: white;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 8px 32px var(--logo-shadow);
|
||||
}
|
||||
.logo-area h1 { font-size: 28px; font-weight: 700; }
|
||||
.subtitle { color: var(--text-secondary); margin-top: 4px; font-size: 14px; }
|
||||
|
||||
.setup-form {
|
||||
background: var(--bg-secondary);
|
||||
padding: 24px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.setup-modes h3 {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.mode-cards {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
.mode-card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 14px 10px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.mode-icon { font-size: 24px; }
|
||||
.mode-name { font-weight: 600; font-size: 14px; }
|
||||
.mode-desc { font-size: 11px; color: var(--text-muted); }
|
||||
|
||||
/* Chat Screen */
|
||||
.chat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
min-height: 56px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
.header-left, .header-right { display: flex; align-items: center; gap: 4px; }
|
||||
.header-title h2 { font-size: 16px; font-weight: 600; line-height: 1.2; }
|
||||
.mode-label {
|
||||
font-size: 10px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
background: var(--accent-dim);
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 88%;
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--radius);
|
||||
line-height: 1.6;
|
||||
font-size: 15px;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
position: relative;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
||||
|
||||
.message.user {
|
||||
background: var(--bg-msg-user);
|
||||
align-self: flex-end;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.message.assistant {
|
||||
background: var(--bg-msg-ai);
|
||||
border: 1px solid var(--border);
|
||||
align-self: flex-start;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.message.system {
|
||||
background: var(--bg-tertiary);
|
||||
align-self: center;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
max-width: 90%;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.message.assistant pre {
|
||||
background: var(--bg-code);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
overflow-x: auto;
|
||||
margin: 8px 0;
|
||||
position: relative;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.message.assistant code {
|
||||
font-family: 'Fira Code', 'JetBrains Mono', 'Cascadia Code', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.message.assistant :not(pre) > code {
|
||||
background: var(--bg-code);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.message.assistant p { margin: 6px 0; }
|
||||
.message.assistant ul, .message.assistant ol { margin: 6px 0; padding-left: 20px; }
|
||||
.message.assistant li { margin: 3px 0; }
|
||||
.message.assistant h1, .message.assistant h2, .message.assistant h3, .message.assistant h4 {
|
||||
margin: 12px 0 6px;
|
||||
}
|
||||
.message.assistant blockquote {
|
||||
border-left: 3px solid var(--accent);
|
||||
padding-left: 12px;
|
||||
margin: 8px 0;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.message.assistant table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.message.assistant th, .message.assistant td {
|
||||
border: 1px solid var(--border);
|
||||
padding: 6px 10px;
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
}
|
||||
.message.assistant th { background: var(--bg-tertiary); }
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px 8px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: -8px;
|
||||
margin-top: 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-bottom: none;
|
||||
}
|
||||
.code-header + pre { border-top-left-radius: 0; border-top-right-radius: 0; }
|
||||
.copy-btn {
|
||||
background: var(--accent-dim);
|
||||
border: none;
|
||||
color: var(--accent);
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.copy-btn:hover { background: var(--accent); color: white; }
|
||||
|
||||
.thinking-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.thinking-dots span {
|
||||
display: inline-block;
|
||||
width: 6px; height: 6px;
|
||||
background: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: bounce 1.4s infinite;
|
||||
}
|
||||
.thinking-dots span:nth-child(2) { animation-delay: 0.2s; }
|
||||
.thinking-dots span:nth-child(3) { animation-delay: 0.4s; }
|
||||
@keyframes bounce { 0%, 80%, 100% { transform: scale(0.6); } 40% { transform: scale(1); } }
|
||||
|
||||
/* Chat Input */
|
||||
.chat-input-area {
|
||||
padding: 8px 12px 16px;
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.mode-selector {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 8px;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.mode-selector::-webkit-scrollbar { display: none; }
|
||||
.mode-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
padding: 6px 14px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
.mode-btn.active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
.mode-btn:hover:not(.active) {
|
||||
background: var(--accent-dim);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
#message-input {
|
||||
flex: 1;
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-primary);
|
||||
padding: 12px 16px;
|
||||
border-radius: 24px;
|
||||
font-size: 15px;
|
||||
resize: none;
|
||||
outline: none;
|
||||
max-height: 120px;
|
||||
min-height: 44px;
|
||||
line-height: 1.4;
|
||||
transition: border-color var(--transition);
|
||||
font-family: inherit;
|
||||
}
|
||||
#message-input:focus { border-color: var(--accent); }
|
||||
|
||||
.send-btn, .stop-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all var(--transition);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.send-btn:hover { background: var(--accent-hover); }
|
||||
.send-btn:disabled { opacity: 0.3; cursor: not-allowed; }
|
||||
.stop-btn { background: var(--danger); }
|
||||
.stop-btn:hover { background: #ff6b7a; }
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: -300px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 280px;
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border);
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
.sidebar.open { left: 0; }
|
||||
.sidebar-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: var(--sidebar-overlay-bg);
|
||||
z-index: 99;
|
||||
display: none;
|
||||
}
|
||||
.sidebar-overlay.active { display: block; }
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.sidebar-header h3 { font-size: 16px; }
|
||||
|
||||
.conversation-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
.conv-item {
|
||||
padding: 12px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.conv-item:hover { background: var(--accent-dim); }
|
||||
.conv-item.active { background: var(--accent-dim); border: 1px solid var(--accent); }
|
||||
.conv-title { font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; }
|
||||
.conv-delete {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
}
|
||||
.conv-item:hover .conv-delete { display: block; }
|
||||
.conv-delete:hover { color: var(--danger); background: rgba(255,71,87,0.1); }
|
||||
|
||||
.sidebar-footer { padding: 12px; border-top: 1px solid var(--border); }
|
||||
|
||||
/* Settings Screen */
|
||||
.settings-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.settings-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.settings-header h2 { font-size: 18px; }
|
||||
|
||||
.settings-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
.settings-section {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.settings-section:last-child { border-bottom: none; }
|
||||
.settings-section h3 {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.settings-section .btn-secondary,
|
||||
.settings-section .btn-danger { margin-top: 8px; }
|
||||
|
||||
.about-text {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar { width: 4px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
|
||||
|
||||
.theme-toggle-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
transition: background var(--transition);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
}
|
||||
.theme-toggle-btn:hover { background: var(--accent-dim); }
|
||||
|
||||
.changelog-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.changelog-list li {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.changelog-list li:last-child { border-bottom: none; }
|
||||
.changelog-version {
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
font-size: 14px;
|
||||
}
|
||||
.changelog-date {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
margin-left: 8px;
|
||||
}
|
||||
.changelog-list li ul {
|
||||
margin: 4px 0 0 16px;
|
||||
padding: 0;
|
||||
list-style: disc;
|
||||
}
|
||||
.changelog-list li ul li {
|
||||
border-bottom: none;
|
||||
padding: 2px 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 480px) {
|
||||
.message { max-width: 92%; }
|
||||
.setup-container { gap: 24px; }
|
||||
.mode-cards { gap: 8px; }
|
||||
.mode-card { padding: 10px 8px; }
|
||||
}
|
||||
241
www/index.html
Normal file
@@ -0,0 +1,241 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="theme-color" content="#1a1a2e">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<title>Z.AI Chat</title>
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="setup-screen" class="screen active">
|
||||
<div class="setup-container">
|
||||
<div class="logo-area">
|
||||
<div class="logo-icon">Z</div>
|
||||
<h1>Z.AI Chat</h1>
|
||||
<p class="subtitle">GLM Coding Plan · Powered by GLM-5.1</p>
|
||||
</div>
|
||||
<div class="setup-form">
|
||||
<div class="input-group">
|
||||
<label for="api-token">Coding Plan Token</label>
|
||||
<input type="password" id="api-token" placeholder="Enter your Z.AI coding plan API key" autocomplete="off">
|
||||
<span class="input-hint">Get your key at <a href="https://z.ai/manage-apikey/apikey-list" target="_blank">z.ai/apikeys</a> · <a href="https://z.ai/subscribe" target="_blank">Subscribe</a></span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="base-url">Endpoint</label>
|
||||
<select id="base-url">
|
||||
<option value="https://api.z.ai/api/coding/paas/v4" selected>Coding Plan (api.z.ai/coding)</option>
|
||||
<option value="https://api.z.ai/api/paas/v4">General API (api.z.ai)</option>
|
||||
<option value="https://open.bigmodel.cn/api/paas/v4">China (bigmodel.cn)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="connect-btn" class="btn-primary">
|
||||
<span class="btn-text">Connect</span>
|
||||
<span class="btn-loader" style="display:none"></span>
|
||||
</button>
|
||||
<div id="setup-error" class="error-msg" style="display:none"></div>
|
||||
</div>
|
||||
<div class="setup-modes">
|
||||
<h3>Modes Available</h3>
|
||||
<div class="mode-cards">
|
||||
<div class="mode-card">
|
||||
<span class="mode-icon">💬</span>
|
||||
<span class="mode-name">Chat</span>
|
||||
<span class="mode-desc">General conversation</span>
|
||||
</div>
|
||||
<div class="mode-card">
|
||||
<span class="mode-icon">💻</span>
|
||||
<span class="mode-name">Coding</span>
|
||||
<span class="mode-desc">Code assistant</span>
|
||||
</div>
|
||||
<div class="mode-card">
|
||||
<span class="mode-icon">💡</span>
|
||||
<span class="mode-name">Brainstorm</span>
|
||||
<span class="mode-desc">Ideation partner</span>
|
||||
</div>
|
||||
<div class="mode-card">
|
||||
<span class="mode-icon">🤖</span>
|
||||
<span class="mode-name">Agentic</span>
|
||||
<span class="mode-desc">Autonomous coding</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chat-screen" class="screen">
|
||||
<div class="chat-header">
|
||||
<div class="header-left">
|
||||
<button id="menu-btn" class="icon-btn">☰</button>
|
||||
<div class="header-title">
|
||||
<h2 id="conversation-title">New Chat</h2>
|
||||
<span id="current-mode-label" class="mode-label">Chat</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<button id="theme-toggle-header" class="theme-toggle-btn" title="Toggle theme">☾</button>
|
||||
<button id="new-chat-btn" class="icon-btn" title="New chat">+</button>
|
||||
<button id="settings-btn" class="icon-btn" title="Settings">⚙</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sidebar" class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h3>Conversations</h3>
|
||||
<button id="sidebar-close" class="icon-btn">×</button>
|
||||
</div>
|
||||
<div id="conversation-list" class="conversation-list"></div>
|
||||
<div class="sidebar-footer">
|
||||
<button id="new-chat-sidebar" class="btn-secondary">+ New Chat</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sidebar-overlay" class="sidebar-overlay"></div>
|
||||
|
||||
<div id="messages" class="messages"></div>
|
||||
|
||||
<div class="chat-input-area">
|
||||
<div id="mode-selector" class="mode-selector">
|
||||
<button class="mode-btn active" data-mode="chat">Chat</button>
|
||||
<button class="mode-btn" data-mode="coding">Coding</button>
|
||||
<button class="mode-btn" data-mode="brainstorm">Brainstorm</button>
|
||||
<button class="mode-btn" data-mode="agentic">Agentic</button>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<textarea id="message-input" placeholder="Type your message..." rows="1"></textarea>
|
||||
<button id="send-btn" class="send-btn" disabled>
|
||||
<svg viewBox="0 0 24 24" width="24" height="24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" fill="currentColor"/></svg>
|
||||
</button>
|
||||
<button id="stop-btn" class="stop-btn" style="display:none">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24"><rect x="6" y="6" width="12" height="12" fill="currentColor" rx="2"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings-screen" class="screen">
|
||||
<div class="settings-container">
|
||||
<div class="settings-header">
|
||||
<button id="settings-back" class="icon-btn">←</button>
|
||||
<h2>Settings</h2>
|
||||
</div>
|
||||
<div class="settings-body">
|
||||
<div class="settings-section">
|
||||
<h3>API Configuration</h3>
|
||||
<div class="input-group">
|
||||
<label for="settings-token">API Token</label>
|
||||
<input type="password" id="settings-token" placeholder="Your API token">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="settings-url">Base URL</label>
|
||||
<input type="text" id="settings-url" placeholder="https://api.z.ai/api/coding/paas/v4">
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Model Settings</h3>
|
||||
<div class="input-group">
|
||||
<label for="settings-model">Model</label>
|
||||
<select id="settings-model">
|
||||
<option value="glm-5.1" selected>GLM-5.1 (Flagship)</option>
|
||||
<option value="glm-5-turbo">GLM-5 Turbo</option>
|
||||
<option value="glm-4.7">GLM-4.7</option>
|
||||
<option value="glm-4.5-air">GLM-4.5 Air (Fast)</option>
|
||||
<option value="glm-5v-turbo">GLM-5V Turbo (Vision)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Temperature: <span id="temp-value">0.7</span></label>
|
||||
<input type="range" id="settings-temp" min="0" max="1" step="0.1" value="0.7">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Max Tokens: <span id="tokens-value">4096</span></label>
|
||||
<input type="range" id="settings-tokens" min="256" max="16384" step="256" value="4096">
|
||||
</div>
|
||||
<div class="input-group toggle-group">
|
||||
<label>Web Search</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="settings-websearch">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group toggle-group">
|
||||
<label>Streaming</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="settings-streaming" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Appearance</h3>
|
||||
<div class="input-group toggle-group">
|
||||
<label>Dark Mode</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="settings-darkmode" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Data</h3>
|
||||
<button id="export-btn" class="btn-secondary">Export Conversations</button>
|
||||
<button id="clear-btn" class="btn-danger">Clear All Data</button>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>About</h3>
|
||||
<p class="about-text">Z.AI Chat v1.2.0</p>
|
||||
<p class="about-text">Built with Z.AI SDK & GLM-5.1</p>
|
||||
<p class="about-text">Compatible with Android 15/16</p>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Changelog</h3>
|
||||
<ul class="changelog-list">
|
||||
<li>
|
||||
<span class="changelog-version">v1.2.0</span>
|
||||
<span class="changelog-date">2026-05-19</span>
|
||||
<ul>
|
||||
<li>Added light mode / dark mode toggle</li>
|
||||
<li>Theme persists across sessions</li>
|
||||
<li>Theme toggle button in chat header</li>
|
||||
<li>Theme setting in Settings screen</li>
|
||||
<li>Added changelog section to Settings</li>
|
||||
<li>Optimized light theme color palette</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<span class="changelog-version">v1.1.0</span>
|
||||
<span class="changelog-date">2026-05-19</span>
|
||||
<ul>
|
||||
<li>Z.AI Coding Plan endpoint support</li>
|
||||
<li>Fixed API base URL to use coding plan endpoint</li>
|
||||
<li>Updated model list (GLM-5.1, GLM-5 Turbo, GLM-4.7, GLM-4.5 Air)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<span class="changelog-version">v1.0.0</span>
|
||||
<span class="changelog-date">2026-05-19</span>
|
||||
<ul>
|
||||
<li>Initial release</li>
|
||||
<li>Chat, Coding, Brainstorm, Agentic modes</li>
|
||||
<li>Streaming SSE responses</li>
|
||||
<li>Markdown rendering with syntax highlighting</li>
|
||||
<li>Conversation management with sidebar</li>
|
||||
<li>Settings with model, temperature, tokens controls</li>
|
||||
<li>Web search integration</li>
|
||||
<li>Export conversations to JSON</li>
|
||||
<li>Android 15/16 support (targetSdk 36)</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/marked.min.js"></script>
|
||||
<script src="js/highlight.min.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
658
www/js/app.js
Normal file
@@ -0,0 +1,658 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var DEFAULT_BASE_URL = 'https://api.z.ai/api/coding/paas/v4';
|
||||
var DEFAULT_MODEL = 'glm-5.1';
|
||||
var STORAGE_KEY = 'zai_chat_';
|
||||
var MODE_PROMPTS = {
|
||||
chat: 'You are a helpful, knowledgeable AI assistant. Be concise and accurate.',
|
||||
coding: 'You are an expert coding assistant. Write clean, efficient, well-documented code. Always use markdown code blocks with language tags. Explain your approach briefly before and after code. Handle edge cases and errors properly.',
|
||||
brainstorm: 'You are a creative brainstorming partner. Generate diverse ideas, explore unconventional angles, build on concepts, and help evaluate trade-offs. Think freely and expansively. Present ideas in organized lists or tables when appropriate.',
|
||||
agentic: 'You are an autonomous coding agent. Break down complex tasks into clear steps. Write production-quality code with proper error handling, tests, and documentation. Think through the architecture before coding. Use tool-calling format when appropriate: [SEARCH], [CREATE_FILE], [EDIT_FILE], [RUN_COMMAND]. Always verify your work.'
|
||||
};
|
||||
var MODE_EMOJIS = { chat: '\u{1F4AC}', coding: '\u{1F4BB}', brainstorm: '\u{1F4A1}', agentic: '\u{1F916}' };
|
||||
|
||||
var state = {
|
||||
apiKey: '',
|
||||
baseUrl: DEFAULT_BASE_URL,
|
||||
model: DEFAULT_MODEL,
|
||||
temperature: 0.7,
|
||||
maxTokens: 4096,
|
||||
streaming: true,
|
||||
webSearch: false,
|
||||
currentMode: 'chat',
|
||||
theme: 'dark',
|
||||
conversations: [],
|
||||
activeConversationId: null,
|
||||
isGenerating: false,
|
||||
abortController: null
|
||||
};
|
||||
|
||||
function $(sel) { return document.querySelector(sel); }
|
||||
function $$(sel) { return document.querySelectorAll(sel); }
|
||||
|
||||
function loadState() {
|
||||
try {
|
||||
state.apiKey = localStorage.getItem(STORAGE_KEY + 'apiKey') || '';
|
||||
state.baseUrl = localStorage.getItem(STORAGE_KEY + 'baseUrl') || DEFAULT_BASE_URL;
|
||||
state.model = localStorage.getItem(STORAGE_KEY + 'model') || DEFAULT_MODEL;
|
||||
state.temperature = parseFloat(localStorage.getItem(STORAGE_KEY + 'temperature')) || 0.7;
|
||||
state.maxTokens = parseInt(localStorage.getItem(STORAGE_KEY + 'maxTokens')) || 4096;
|
||||
state.streaming = localStorage.getItem(STORAGE_KEY + 'streaming') !== 'false';
|
||||
state.webSearch = localStorage.getItem(STORAGE_KEY + 'webSearch') === 'true';
|
||||
state.currentMode = localStorage.getItem(STORAGE_KEY + 'currentMode') || 'chat';
|
||||
state.theme = localStorage.getItem(STORAGE_KEY + 'theme') || 'dark';
|
||||
var convData = localStorage.getItem(STORAGE_KEY + 'conversations');
|
||||
state.conversations = convData ? JSON.parse(convData) : [];
|
||||
state.activeConversationId = localStorage.getItem(STORAGE_KEY + 'activeConv') || null;
|
||||
} catch(e) { console.error('Load state error:', e); }
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY + 'apiKey', state.apiKey);
|
||||
localStorage.setItem(STORAGE_KEY + 'baseUrl', state.baseUrl);
|
||||
localStorage.setItem(STORAGE_KEY + 'model', state.model);
|
||||
localStorage.setItem(STORAGE_KEY + 'temperature', state.temperature.toString());
|
||||
localStorage.setItem(STORAGE_KEY + 'maxTokens', state.maxTokens.toString());
|
||||
localStorage.setItem(STORAGE_KEY + 'streaming', state.streaming.toString());
|
||||
localStorage.setItem(STORAGE_KEY + 'webSearch', state.webSearch.toString());
|
||||
localStorage.setItem(STORAGE_KEY + 'currentMode', state.currentMode);
|
||||
localStorage.setItem(STORAGE_KEY + 'theme', state.theme);
|
||||
localStorage.setItem(STORAGE_KEY + 'conversations', JSON.stringify(state.conversations));
|
||||
localStorage.setItem(STORAGE_KEY + 'activeConv', state.activeConversationId || '');
|
||||
} catch(e) { console.error('Save state error:', e); }
|
||||
}
|
||||
|
||||
function genId() { return Date.now().toString(36) + Math.random().toString(36).substr(2, 9); }
|
||||
|
||||
function getConversation() {
|
||||
if (!state.activeConversationId) return null;
|
||||
return state.conversations.find(function(c) { return c.id === state.activeConversationId; });
|
||||
}
|
||||
|
||||
function newConversation() {
|
||||
var conv = {
|
||||
id: genId(),
|
||||
title: 'New Chat',
|
||||
mode: state.currentMode,
|
||||
messages: [],
|
||||
createdAt: Date.now()
|
||||
};
|
||||
state.conversations.unshift(conv);
|
||||
state.activeConversationId = conv.id;
|
||||
saveState();
|
||||
renderConversationList();
|
||||
renderMessages();
|
||||
updateHeader();
|
||||
}
|
||||
|
||||
function switchConversation(id) {
|
||||
state.activeConversationId = id;
|
||||
var conv = getConversation();
|
||||
if (conv) {
|
||||
state.currentMode = conv.mode || 'chat';
|
||||
updateModeSelector();
|
||||
}
|
||||
saveState();
|
||||
renderConversationList();
|
||||
renderMessages();
|
||||
updateHeader();
|
||||
closeSidebar();
|
||||
}
|
||||
|
||||
function deleteConversation(id) {
|
||||
state.conversations = state.conversations.filter(function(c) { return c.id !== id; });
|
||||
if (state.activeConversationId === id) {
|
||||
state.activeConversationId = state.conversations.length > 0 ? state.conversations[0].id : null;
|
||||
}
|
||||
saveState();
|
||||
renderConversationList();
|
||||
renderMessages();
|
||||
updateHeader();
|
||||
}
|
||||
|
||||
function updateHeader() {
|
||||
var conv = getConversation();
|
||||
$('#conversation-title').textContent = conv ? conv.title : 'Z.AI Chat';
|
||||
$('#current-mode-label').textContent = state.currentMode.charAt(0).toUpperCase() + state.currentMode.slice(1);
|
||||
}
|
||||
|
||||
function showScreen(name) {
|
||||
$$('.screen').forEach(function(s) { s.classList.remove('active'); });
|
||||
$('#' + name + '-screen').classList.add('active');
|
||||
}
|
||||
|
||||
function autoResize(el) {
|
||||
el.style.height = 'auto';
|
||||
el.style.height = Math.min(el.scrollHeight, 120) + 'px';
|
||||
}
|
||||
|
||||
function renderConversationList() {
|
||||
var list = $('#conversation-list');
|
||||
if (!list) return;
|
||||
list.innerHTML = '';
|
||||
state.conversations.forEach(function(conv) {
|
||||
var div = document.createElement('div');
|
||||
div.className = 'conv-item' + (conv.id === state.activeConversationId ? ' active' : '');
|
||||
div.innerHTML = '<span class="conv-title">' + escapeHtml(conv.title) + '</span>' +
|
||||
'<button class="conv-delete" data-id="' + conv.id + '">×</button>';
|
||||
div.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('conv-delete')) {
|
||||
e.stopPropagation();
|
||||
deleteConversation(e.target.dataset.id);
|
||||
return;
|
||||
}
|
||||
switchConversation(conv.id);
|
||||
});
|
||||
list.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = text;
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function renderMarkdown(text) {
|
||||
if (typeof marked !== 'undefined') {
|
||||
marked.setOptions({
|
||||
highlight: function(code, lang) {
|
||||
if (typeof hljs !== 'undefined' && lang && hljs.getLanguage(lang)) {
|
||||
try { return hljs.highlight(code, { language: lang }).value; } catch(e) {}
|
||||
}
|
||||
return code;
|
||||
},
|
||||
breaks: true,
|
||||
gfm: true
|
||||
});
|
||||
return marked.parse(text);
|
||||
}
|
||||
return text.replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
function addCodeHeaders(container) {
|
||||
container.querySelectorAll('pre code').forEach(function(block) {
|
||||
var pre = block.parentElement;
|
||||
var lang = (block.className.match(/language-(\w+)/) || [])[1] || 'code';
|
||||
if (!pre.previousElementSibling || !pre.previousElementSibling.classList.contains('code-header')) {
|
||||
var header = document.createElement('div');
|
||||
header.className = 'code-header';
|
||||
header.innerHTML = '<span>' + escapeHtml(lang) + '</span><button class="copy-btn">Copy</button>';
|
||||
pre.parentElement.insertBefore(header, pre);
|
||||
header.querySelector('.copy-btn').addEventListener('click', function() {
|
||||
navigator.clipboard.writeText(block.textContent).then(function() {
|
||||
this.textContent = 'Copied!';
|
||||
setTimeout(function() { this.textContent = 'Copy'; }.bind(this), 2000);
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderMessages() {
|
||||
var container = $('#messages');
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
var conv = getConversation();
|
||||
if (!conv || conv.messages.length === 0) {
|
||||
container.innerHTML = '<div class="message system">Start a conversation with Z.AI</div>';
|
||||
return;
|
||||
}
|
||||
conv.messages.forEach(function(msg) {
|
||||
appendMessage(msg.role, msg.content, container, false);
|
||||
});
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
function appendMessage(role, content, container, animate) {
|
||||
container = container || $('#messages');
|
||||
var div = document.createElement('div');
|
||||
div.className = 'message ' + role;
|
||||
if (animate === false) div.style.animation = 'none';
|
||||
|
||||
if (role === 'assistant') {
|
||||
div.innerHTML = renderMarkdown(content);
|
||||
addCodeHeaders(div);
|
||||
} else {
|
||||
div.textContent = content;
|
||||
}
|
||||
container.appendChild(div);
|
||||
container.scrollTop = container.scrollHeight;
|
||||
return div;
|
||||
}
|
||||
|
||||
function updateStreamingMessage(div, content) {
|
||||
div.innerHTML = renderMarkdown(content);
|
||||
addCodeHeaders(div);
|
||||
$('#messages').scrollTop = $('#messages').scrollHeight;
|
||||
}
|
||||
|
||||
function showThinking() {
|
||||
var container = $('#messages');
|
||||
var div = document.createElement('div');
|
||||
div.className = 'message assistant';
|
||||
div.id = 'thinking-msg';
|
||||
div.innerHTML = '<div class="thinking-indicator"><div class="thinking-dots"><span></span><span></span><span></span></div> Thinking...</div>';
|
||||
container.appendChild(div);
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
function removeThinking() {
|
||||
var el = $('#thinking-msg');
|
||||
if (el) el.remove();
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
var input = $('#message-input');
|
||||
var text = input.value.trim();
|
||||
if (!text || state.isGenerating) return;
|
||||
|
||||
if (!state.apiKey) {
|
||||
showScreen('setup');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.activeConversationId) {
|
||||
newConversation();
|
||||
}
|
||||
|
||||
var conv = getConversation();
|
||||
if (!conv) return;
|
||||
|
||||
conv.mode = state.currentMode;
|
||||
|
||||
if (conv.messages.length === 0) {
|
||||
conv.title = text.substring(0, 50) + (text.length > 50 ? '...' : '');
|
||||
updateHeader();
|
||||
renderConversationList();
|
||||
}
|
||||
|
||||
conv.messages.push({ role: 'user', content: text });
|
||||
input.value = '';
|
||||
autoResize(input);
|
||||
updateSendButton();
|
||||
appendMessage('user', text);
|
||||
|
||||
state.isGenerating = true;
|
||||
updateSendButton();
|
||||
showThinking();
|
||||
|
||||
try {
|
||||
var systemPrompt = MODE_PROMPTS[state.currentMode] || MODE_PROMPTS.chat;
|
||||
var apiMessages = [{ role: 'system', content: systemPrompt }];
|
||||
conv.messages.forEach(function(m) {
|
||||
if (m.role === 'user' || m.role === 'assistant') {
|
||||
apiMessages.push({ role: m.role, content: m.content });
|
||||
}
|
||||
});
|
||||
|
||||
var requestBody = {
|
||||
model: state.model,
|
||||
messages: apiMessages,
|
||||
temperature: state.temperature,
|
||||
max_tokens: state.maxTokens,
|
||||
stream: state.streaming
|
||||
};
|
||||
|
||||
if (state.webSearch) {
|
||||
requestBody.tools = [{
|
||||
type: 'web_search',
|
||||
web_search: { search_query: text, search_result: true }
|
||||
}];
|
||||
}
|
||||
|
||||
removeThinking();
|
||||
var responseDiv = appendMessage('assistant', '');
|
||||
|
||||
if (state.streaming) {
|
||||
await streamResponse(requestBody, responseDiv, conv);
|
||||
} else {
|
||||
var result = await apiRequest(requestBody);
|
||||
var content = result.choices[0].message.content;
|
||||
updateStreamingMessage(responseDiv, content);
|
||||
conv.messages.push({ role: 'assistant', content: content });
|
||||
}
|
||||
} catch(err) {
|
||||
removeThinking();
|
||||
if (err.name !== 'AbortError') {
|
||||
appendMessage('system', 'Error: ' + (err.message || 'Request failed'));
|
||||
}
|
||||
} finally {
|
||||
state.isGenerating = false;
|
||||
state.abortController = null;
|
||||
updateSendButton();
|
||||
saveState();
|
||||
}
|
||||
}
|
||||
|
||||
async function apiRequest(body) {
|
||||
var url = state.baseUrl.replace(/\/+$/, '') + '/chat/completions';
|
||||
var resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + state.apiKey,
|
||||
'Accept-Language': 'en-US,en'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (!resp.ok) {
|
||||
var errData = {};
|
||||
try { errData = await resp.json(); } catch(e) {}
|
||||
throw new Error(errData.error?.message || 'API error ' + resp.status);
|
||||
}
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
async function streamResponse(body, responseDiv, conv) {
|
||||
state.abortController = new AbortController();
|
||||
body.stream = true;
|
||||
|
||||
var url = state.baseUrl.replace(/\/+$/, '') + '/chat/completions';
|
||||
var resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + state.apiKey,
|
||||
'Accept-Language': 'en-US,en'
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
signal: state.abortController.signal
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
var errData = {};
|
||||
try { errData = await resp.json(); } catch(e) {}
|
||||
throw new Error(errData.error?.message || 'API error ' + resp.status);
|
||||
}
|
||||
|
||||
var reader = resp.body.getReader();
|
||||
var decoder = new TextDecoder();
|
||||
var fullContent = '';
|
||||
var buffer = '';
|
||||
|
||||
while (true) {
|
||||
var chunk = await reader.read();
|
||||
if (chunk.done) break;
|
||||
|
||||
buffer += decoder.decode(chunk.value, { stream: true });
|
||||
var lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim();
|
||||
if (!line || !line.startsWith('data:')) continue;
|
||||
var data = line.substring(5).trim();
|
||||
if (data === '[DONE]') break;
|
||||
|
||||
try {
|
||||
var parsed = JSON.parse(data);
|
||||
var delta = parsed.choices && parsed.choices[0] && parsed.choices[0].delta;
|
||||
if (delta && delta.content) {
|
||||
fullContent += delta.content;
|
||||
updateStreamingMessage(responseDiv, fullContent);
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
|
||||
conv.messages.push({ role: 'assistant', content: fullContent });
|
||||
}
|
||||
|
||||
function stopGeneration() {
|
||||
if (state.abortController) {
|
||||
state.abortController.abort();
|
||||
}
|
||||
}
|
||||
|
||||
function updateSendButton() {
|
||||
var input = $('#message-input');
|
||||
var sendBtn = $('#send-btn');
|
||||
var stopBtn = $('#stop-btn');
|
||||
|
||||
if (state.isGenerating) {
|
||||
sendBtn.style.display = 'none';
|
||||
stopBtn.style.display = 'flex';
|
||||
} else {
|
||||
sendBtn.style.display = 'flex';
|
||||
stopBtn.style.display = 'none';
|
||||
sendBtn.disabled = !input.value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
function updateModeSelector() {
|
||||
$$('.mode-btn').forEach(function(btn) {
|
||||
btn.classList.toggle('active', btn.dataset.mode === state.currentMode);
|
||||
});
|
||||
}
|
||||
|
||||
function openSidebar() {
|
||||
$('#sidebar').classList.add('open');
|
||||
$('#sidebar-overlay').classList.add('active');
|
||||
}
|
||||
|
||||
function closeSidebar() {
|
||||
$('#sidebar').classList.remove('open');
|
||||
$('#sidebar-overlay').classList.remove('active');
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
state.theme = theme;
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
var headerBtn = $('#theme-toggle-header');
|
||||
if (headerBtn) {
|
||||
headerBtn.innerHTML = theme === 'dark' ? '☼' : '☾';
|
||||
headerBtn.title = theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
|
||||
}
|
||||
var settingsToggle = $('#settings-darkmode');
|
||||
if (settingsToggle) settingsToggle.checked = (theme === 'dark');
|
||||
var metaTheme = document.querySelector('meta[name="theme-color"]');
|
||||
if (metaTheme) metaTheme.content = theme === 'dark' ? '#1a1a2e' : '#ffffff';
|
||||
saveState();
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
applyTheme(state.theme === 'dark' ? 'light' : 'dark');
|
||||
}
|
||||
|
||||
async function testConnection(apiKey, baseUrl) {
|
||||
var url = (baseUrl || state.baseUrl).replace(/\/+$/, '') + '/chat/completions';
|
||||
var resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + apiKey,
|
||||
'Accept-Language': 'en-US,en'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: state.model,
|
||||
messages: [{ role: 'user', content: 'Hi' }],
|
||||
max_tokens: 10
|
||||
})
|
||||
});
|
||||
if (!resp.ok) {
|
||||
var errData = {};
|
||||
try { errData = await resp.json(); } catch(e) {}
|
||||
throw new Error(errData.error?.message || 'Connection failed (' + resp.status + ')');
|
||||
}
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
function populateSettings() {
|
||||
$('#settings-token').value = state.apiKey;
|
||||
$('#settings-url').value = state.baseUrl;
|
||||
$('#settings-model').value = state.model;
|
||||
$('#settings-temp').value = state.temperature;
|
||||
$('#temp-value').textContent = state.temperature;
|
||||
$('#settings-tokens').value = state.maxTokens;
|
||||
$('#tokens-value').textContent = state.maxTokens;
|
||||
$('#settings-websearch').checked = state.webSearch;
|
||||
$('#settings-streaming').checked = state.streaming;
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
state.apiKey = $('#settings-token').value.trim();
|
||||
state.baseUrl = $('#settings-url').value.trim();
|
||||
state.model = $('#settings-model').value;
|
||||
state.temperature = parseFloat($('#settings-temp').value);
|
||||
state.maxTokens = parseInt($('#settings-tokens').value);
|
||||
state.webSearch = $('#settings-websearch').checked;
|
||||
state.streaming = $('#settings-streaming').checked;
|
||||
saveState();
|
||||
}
|
||||
|
||||
function exportConversations() {
|
||||
var data = JSON.stringify(state.conversations, null, 2);
|
||||
var blob = new Blob([data], { type: 'application/json' });
|
||||
var url = URL.createObjectURL(blob);
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'zai-chat-export-' + new Date().toISOString().slice(0, 10) + '.json';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function init() {
|
||||
loadState();
|
||||
|
||||
if (state.apiKey) {
|
||||
showScreen('chat');
|
||||
if (state.activeConversationId) {
|
||||
var conv = getConversation();
|
||||
if (conv) {
|
||||
state.currentMode = conv.mode || 'chat';
|
||||
}
|
||||
}
|
||||
renderConversationList();
|
||||
renderMessages();
|
||||
updateHeader();
|
||||
updateModeSelector();
|
||||
$('#api-token').value = state.apiKey;
|
||||
$('#base-url').value = state.baseUrl;
|
||||
}
|
||||
|
||||
$('#connect-btn').addEventListener('click', async function() {
|
||||
var btn = this;
|
||||
var apiKey = $('#api-token').value.trim();
|
||||
var baseUrl = $('#base-url').value;
|
||||
var errorEl = $('#setup-error');
|
||||
|
||||
if (!apiKey) {
|
||||
errorEl.textContent = 'Please enter your API key';
|
||||
errorEl.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.querySelector('.btn-text').textContent = 'Connecting...';
|
||||
btn.querySelector('.btn-loader').style.display = 'inline-block';
|
||||
errorEl.style.display = 'none';
|
||||
|
||||
try {
|
||||
await testConnection(apiKey, baseUrl);
|
||||
state.apiKey = apiKey;
|
||||
state.baseUrl = baseUrl;
|
||||
saveState();
|
||||
showScreen('chat');
|
||||
newConversation();
|
||||
} catch(err) {
|
||||
errorEl.textContent = err.message;
|
||||
errorEl.style.display = 'block';
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.querySelector('.btn-text').textContent = 'Connect';
|
||||
btn.querySelector('.btn-loader').style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
$('#api-token').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') $('#connect-btn').click();
|
||||
});
|
||||
|
||||
$('#message-input').addEventListener('input', function() {
|
||||
autoResize(this);
|
||||
updateSendButton();
|
||||
});
|
||||
|
||||
$('#message-input').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
$('#send-btn').addEventListener('click', sendMessage);
|
||||
$('#stop-btn').addEventListener('click', stopGeneration);
|
||||
|
||||
$$('.mode-btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
state.currentMode = this.dataset.mode;
|
||||
updateModeSelector();
|
||||
updateHeader();
|
||||
saveState();
|
||||
});
|
||||
});
|
||||
|
||||
$('#menu-btn').addEventListener('click', openSidebar);
|
||||
$('#sidebar-close').addEventListener('click', closeSidebar);
|
||||
$('#sidebar-overlay').addEventListener('click', closeSidebar);
|
||||
|
||||
$('#new-chat-btn').addEventListener('click', function() { newConversation(); });
|
||||
$('#new-chat-sidebar').addEventListener('click', function() { newConversation(); closeSidebar(); });
|
||||
|
||||
$('#settings-btn').addEventListener('click', function() {
|
||||
populateSettings();
|
||||
showScreen('settings');
|
||||
});
|
||||
|
||||
$('#settings-back').addEventListener('click', function() {
|
||||
saveSettings();
|
||||
showScreen('chat');
|
||||
});
|
||||
|
||||
$('#settings-temp').addEventListener('input', function() {
|
||||
$('#temp-value').textContent = this.value;
|
||||
});
|
||||
|
||||
$('#settings-tokens').addEventListener('input', function() {
|
||||
$('#tokens-value').textContent = this.value;
|
||||
});
|
||||
|
||||
$('#settings-token').addEventListener('change', saveSettings);
|
||||
$('#settings-url').addEventListener('change', saveSettings);
|
||||
$('#settings-model').addEventListener('change', saveSettings);
|
||||
$('#settings-websearch').addEventListener('change', saveSettings);
|
||||
$('#settings-streaming').addEventListener('change', saveSettings);
|
||||
|
||||
$('#theme-toggle-header').addEventListener('click', toggleTheme);
|
||||
|
||||
$('#settings-darkmode').addEventListener('change', function() {
|
||||
applyTheme(this.checked ? 'dark' : 'light');
|
||||
});
|
||||
|
||||
$('#export-btn').addEventListener('click', exportConversations);
|
||||
|
||||
$('#clear-btn').addEventListener('click', function() {
|
||||
if (confirm('Clear all conversations? This cannot be undone.')) {
|
||||
state.conversations = [];
|
||||
state.activeConversationId = null;
|
||||
saveState();
|
||||
renderConversationList();
|
||||
renderMessages();
|
||||
updateHeader();
|
||||
}
|
||||
});
|
||||
|
||||
updateModeSelector();
|
||||
updateSendButton();
|
||||
applyTheme(state.theme);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||