├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── .gitmodules
├── README.md
├── androidApp
├── build.gradle.kts
└── src
│ └── androidMain
│ ├── AndroidManifest.xml
│ ├── kotlin
│ └── org
│ │ └── succlz123
│ │ └── deepco
│ │ └── app
│ │ ├── MainActivity.kt
│ │ └── MainApplication.kt
│ └── res
│ ├── drawable
│ └── ic_launcher_foreground.xml
│ ├── mipmap
│ └── ic_launcher.png
│ ├── values-night
│ └── styles.xml
│ └── values
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle.kts
├── desktopApp
├── build.gradle.kts
├── icon.icns
├── icon.ico
├── icon.png
└── src
│ └── jvmMain
│ └── kotlin
│ └── org
│ └── succlz123
│ └── deepco
│ └── main.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── iosApp
├── Configuration
│ └── Config.xcconfig
├── iosApp.xcodeproj
│ └── project.pbxproj
└── iosApp
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── ContentView.swift
│ ├── Info.plist
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ └── iOSApp.swift
├── screenshots
├── 1.jpg
├── 2.jpg
├── 3.jpg
├── 4.jpg
├── 5.jpg
├── 6.jpg
└── 7.jpg
├── settings.gradle.kts
└── shared
├── build.gradle.kts
└── src
├── androidMain
├── AndroidManifest.xml
└── kotlin
│ ├── main.android.kt
│ └── org
│ └── succlz123
│ ├── deepco
│ └── app
│ │ ├── mcp
│ │ └── MCPClient.android.kt
│ │ └── tts
│ │ └── TTS.android.kt
│ └── lib
│ ├── common
│ ├── Url.android.kt
│ └── platform.android.kt
│ ├── context
│ └── Context.kt
│ ├── file
│ └── File.android.kt
│ ├── image
│ └── AsyncImage.android.kt
│ ├── init
│ └── Init.android.kt
│ ├── modifier
│ └── Shadow.android.kt
│ └── setting
│ └── Settings.android.kt
├── commonMain
├── composeResources
│ ├── drawable
│ │ ├── ic_back.png
│ │ ├── ic_chat.png
│ │ ├── ic_chat_setting.png
│ │ ├── ic_close.png
│ │ ├── ic_confirm.png
│ │ ├── ic_copy.png
│ │ ├── ic_down.png
│ │ ├── ic_elapsed.png
│ │ ├── ic_export.png
│ │ ├── ic_home.png
│ │ ├── ic_info.png
│ │ ├── ic_launcher.png
│ │ ├── ic_local_dir.png
│ │ ├── ic_menu.png
│ │ ├── ic_model.png
│ │ ├── ic_modify.png
│ │ ├── ic_more_detail.png
│ │ ├── ic_my.png
│ │ ├── ic_no.png
│ │ ├── ic_origin.png
│ │ ├── ic_person.png
│ │ ├── ic_prompt.png
│ │ ├── ic_qq.png
│ │ ├── ic_remove.png
│ │ ├── ic_select.png
│ │ ├── ic_send.png
│ │ ├── ic_show_more.png
│ │ ├── ic_speaker.png
│ │ ├── ic_text.png
│ │ ├── ic_token_down.png
│ │ ├── ic_token_total.png
│ │ ├── ic_token_up.png
│ │ ├── ic_tokens.png
│ │ ├── ic_tool.png
│ │ ├── ic_yes.png
│ │ ├── import.png
│ │ ├── logo_deepseek.png
│ │ ├── logo_google.png
│ │ ├── logo_grok.png
│ │ ├── logo_llm.png
│ │ └── logo_st.jpg
│ └── files
│ │ ├── material-theme-blue.json
│ │ ├── material-theme-green.json
│ │ ├── material-theme-red.json
│ │ └── material-theme-yellow.json
└── kotlin
│ └── org
│ └── succlz123
│ ├── deepco
│ └── app
│ │ ├── AppManifest.kt
│ │ ├── api
│ │ └── AppApiService.kt
│ │ ├── base
│ │ ├── AppDialog.kt
│ │ ├── AppTable.kt
│ │ ├── BaseBizViewModel.kt
│ │ ├── BaseDialogCard.kt
│ │ ├── BaseView.kt
│ │ ├── CustomEdit.kt
│ │ ├── CustomExposedDropdownMenu.kt
│ │ ├── GlobalFocusViewModel.kt
│ │ ├── LocalDirStorage.kt
│ │ ├── LocalStorage.kt
│ │ └── MainTitleLayout.kt
│ │ ├── character
│ │ ├── Png.kt
│ │ └── TavernCardV2.kt
│ │ ├── chat
│ │ ├── msg
│ │ │ └── ChatMessage.kt
│ │ ├── prompt
│ │ │ └── PromptInfo.kt
│ │ ├── role
│ │ │ └── ChatRole.kt
│ │ └── user
│ │ │ └── ChatUser.kt
│ │ ├── i18n
│ │ └── String.kt
│ │ ├── json
│ │ └── AppJson.kt
│ │ ├── llm
│ │ ├── ChatResponse.kt
│ │ ├── deepseek
│ │ │ ├── DeepSeekApiService.kt
│ │ │ ├── DeepSeekRequest.kt
│ │ │ └── DeepSeekResponse.kt
│ │ ├── gemini
│ │ │ └── Gemini.kt
│ │ └── grok
│ │ │ └── Grok.kt
│ │ ├── main
│ │ ├── MainActivity.kt
│ │ ├── MainScreen.kt
│ │ └── MainViewModel.kt
│ │ ├── mcp
│ │ ├── MCPClient.kt
│ │ └── biz
│ │ │ ├── McpConfig.kt
│ │ │ ├── Tool.kt
│ │ │ ├── ToolConfig.kt
│ │ │ ├── ToolRequest.kt
│ │ │ └── ToolUse.kt
│ │ ├── theme
│ │ ├── AppThemeConfig.kt
│ │ ├── ColorResource.kt
│ │ └── ThemeJson.kt
│ │ ├── tts
│ │ └── TTS.kt
│ │ ├── ui
│ │ ├── chat
│ │ │ ├── ChatModeConfig.kt
│ │ │ ├── ChatModeConfigDialog.kt
│ │ │ ├── LLMEmptyView.kt
│ │ │ ├── MainChatTab.kt
│ │ │ └── MainChatViewModel.kt
│ │ ├── llm
│ │ │ ├── LLM.kt
│ │ │ ├── LLMConfigDialog.kt
│ │ │ ├── MainLLMTab.kt
│ │ │ └── MainLLMViewModel.kt
│ │ ├── mcp
│ │ │ ├── MCPAddDialog.kt
│ │ │ ├── MainMcpTab.kt
│ │ │ └── MainMcpViewModel.kt
│ │ ├── prompt
│ │ │ ├── MainPromptTab.kt
│ │ │ ├── MainPromptViewModel.kt
│ │ │ ├── PromptAddDialog.kt
│ │ │ ├── PromptCharacterAddDialog.kt
│ │ │ ├── PromptDetailDialog.kt
│ │ │ └── PromptSelectedDialog.kt
│ │ ├── resize
│ │ │ ├── ResizablePanel.kt
│ │ │ └── ResizablePanelTabView.kt
│ │ ├── setting
│ │ │ ├── MainSettingTab.kt
│ │ │ └── MainSettingViewModel.kt
│ │ └── user
│ │ │ ├── ChatUserConfigDialog.kt
│ │ │ ├── ChatUserSelectDialog.kt
│ │ │ ├── MainUserTab.kt
│ │ │ ├── MainUserViewModel.kt
│ │ │ └── UserAvatarView.kt
│ │ └── util
│ │ ├── LayoutModifiers.kt
│ │ ├── Loadable.kt
│ │ └── VerticalSplittable.kt
│ └── lib
│ ├── click
│ └── ClickableKtx.kt
│ ├── common
│ ├── Url.kt
│ └── platform.kt
│ ├── file
│ └── File.kt
│ ├── filedownloader
│ └── core
│ │ ├── DownloadRequest.kt
│ │ ├── DownloadState.kt
│ │ ├── FileDownLoader.kt
│ │ ├── http
│ │ └── HttpConnectionClient.kt
│ │ └── utils
│ │ └── FileDownloadLoaderLogger.kt
│ ├── focus
│ ├── FocusKtx.kt
│ └── FocusNode.kt
│ ├── fps
│ └── FpsMonitor.kt
│ ├── image
│ └── AsyncImage.kt
│ ├── init
│ └── Init.kt
│ ├── list
│ └── LazyListKtx.kt
│ ├── logger
│ └── Logger.kt
│ ├── modifier
│ └── Shadow.kt
│ ├── network
│ └── HttpKtx.kt
│ ├── result
│ └── ScreenResultExtension.kt
│ ├── scroll
│ ├── AppScrollbar.kt
│ ├── ScrollContext.kt
│ └── ScrollbarStyle.kt
│ ├── setting
│ └── Settings.kt
│ ├── thread
│ └── TaskManager.kt
│ ├── time
│ └── Time.kt
│ └── vm
│ ├── BaseViewModel.kt
│ └── ScreenPageViewModel.kt
├── desktopMain
└── kotlin
│ └── org
│ └── succlz123
│ ├── deepco
│ └── app
│ │ ├── base
│ │ └── BaseWindow.kt
│ │ ├── main
│ │ └── MainWindow.kt
│ │ ├── mcp
│ │ └── MCPClient.desktop.kt
│ │ ├── tts
│ │ ├── TTS.desktop.kt
│ │ └── TTSClient.kt
│ │ └── window
│ │ ├── AppApplicationState.kt
│ │ └── AppWindow.kt
│ └── lib
│ ├── common
│ ├── Url.desktop.kt
│ └── platform.desktop.kt
│ ├── file
│ └── File.desktop.kt
│ ├── image
│ └── AsyncImage.desktop.kt
│ ├── init
│ └── Init.desktop.kt
│ ├── modifier
│ └── Shadow.desktop.kt
│ └── setting
│ └── Settings.desktop.kt
├── iosMain
└── kotlin
│ ├── main.ios.kt
│ └── org
│ └── jetbrains
│ └── codeviewer
│ └── platform
│ ├── File.kt
│ ├── Mouse.kt
│ └── Scrollbar.kt
└── jvmMain
└── kotlin
└── org
└── succlz123
├── deepco
└── app
│ ├── llm
│ ├── gemini
│ │ └── Gemini.kt
│ └── grok
│ │ └── Grok.kt
│ └── mcp
│ └── MCPClient.kt
└── lib
├── file
└── JvmFile.kt
└── network
└── ProxyClient.kt
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Build
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | build:
10 | strategy:
11 | matrix:
12 | os: [ubuntu-latest, windows-latest, macos-latest]
13 | runs-on: ${{ matrix.os }}
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 | with:
18 | submodules: 'recursive'
19 | fetch-depth: 0
20 |
21 | - name: Set up JDK
22 | uses: actions/setup-java@v4
23 | with:
24 | distribution: 'zulu'
25 | java-version: '21'
26 |
27 | - name: Grant execute permission for gradlew
28 | if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'
29 | run: chmod +x gradlew
30 |
31 | - name: Build for current OS
32 | run: ./gradlew :desktop:packageDistributionForCurrentOS
33 |
34 | - name: Upload artifacts
35 | uses: actions/upload-artifact@v4
36 | with:
37 | name: deepco-${{ matrix.os }}
38 | path: |
39 | ${{ matrix.os == 'macos-latest' && 'desktopApp/build/compose/binaries/main/dmg' ||
40 | matrix.os == 'windows-latest' && 'desktopApp/build/compose/binaries/main/msi' ||
41 | matrix.os == 'ubuntu-latest' && 'desktopApp/build/compose/binaries/main/' }}
42 | release:
43 | needs: build
44 | runs-on: ubuntu-latest
45 | permissions:
46 | contents: write
47 | id-token: write
48 |
49 | steps:
50 | - uses: actions/download-artifact@v4
51 | with:
52 | path: artifacts
53 |
54 | - name: Create Release
55 | uses: softprops/action-gh-release@v2
56 | with:
57 | files: |
58 | artifacts/deepco-ubuntu-latest/**/*
59 | artifacts/deepco-windows-latest/**/*
60 | artifacts/deepco-macos-latest/**/*
61 | env:
62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .kotlin
3 | build/
4 | !gradle/wrapper/gradle-wrapper.jar
5 | !**/src/main/**/build/
6 | !**/src/test/**/build/
7 |
8 | ### IntelliJ IDEA ###
9 | .idea/modules.xml
10 | .idea/jarRepositories.xml
11 | .idea/compiler.xml
12 | .idea/libraries/
13 | *.iws
14 | *.iml
15 | *.ipr
16 | out/
17 | !**/src/main/**/out/
18 | !**/src/test/**/out/
19 |
20 | ### Eclipse ###
21 | .apt_generated
22 | .classpath
23 | .factorypath
24 | .project
25 | .settings
26 | .springBeans
27 | .sts4-cache
28 | bin/
29 | !**/src/main/**/bin/
30 | !**/src/test/**/bin/
31 |
32 | ### NetBeans ###
33 | /nbproject/private/
34 | /nbbuild/
35 | /dist/
36 | /nbdist/
37 | /.nb-gradle/
38 |
39 | ### VS Code ###
40 | .vscode/
41 |
42 | ### Mac OS ###
43 | .DS_Store
44 |
45 | jcef-bundle
46 | imageCache
47 | file-download
48 | .idea/
49 | .run
50 | run
51 |
52 | local.properties
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "compose-screen"]
2 | path = compose-screen
3 | url = git@github.com:succlz123/compose-screen.git
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deep-Co
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | A Chat Client for LLMs, written in Compose Multiplatform. Target supports API providers such as OpenRouter, Anthropic, Grok, OpenAI, DeepSeek,
26 | Coze, Dify, Google Gemini, etc. You can also configure any OpenAI-compatible API or use native models via LM Studio/Ollama.
27 |
28 |
29 | ## Release
30 |
31 | [v1.0.6](https://github.com/succlz123/DeepCo/releases)
32 |
33 |
34 | ## Feature
35 |
36 | - [x] Desktop Platform Support(Windows/MacOS/Linux)
37 | - [ ] Mobile Platform Support(Android/iOS)
38 | - [x] Chat(Stream&Complete) / Chat History
39 | - [ ] Chat Messages Export / Chat Translate Server
40 | - [x] Prompt Management / User Define
41 | - [x] SillyTavern Character Adaptation(PNG&JSON)
42 | - [x] DeepSeek LLM / Grok LLM / Google Gemini LLM
43 | - [ ] Claude LLM / OpenAI LLM / OLLama LLM
44 | - [ ] Online API polling
45 | - [x] MCP Support
46 | - [ ] MCP Server Market
47 | - [ ] RAG
48 | - [x] TTS(Edge API)
49 | - [x] i18n(Chinese/English) / App Color Theme / App Dark&Light Theme
50 |
51 | ### Chat With LLMs
52 |
53 | 
54 |
55 | ### Config Your LLMs API Key
56 |
57 | 
58 |
59 | ### Prompt Management
60 |
61 | 
62 |
63 | ### Chat With Tavern Character
64 |
65 | 
66 |
67 | ### User Management
68 |
69 | 
70 |
71 | ### Config MCP Servers
72 |
73 | 
74 |
75 | ### Setting
76 |
77 | 
78 |
79 |
80 | ## Model Context Protocol (MCP) ENV
81 |
82 | ### MacOS
83 |
84 | ```
85 | brew install uv
86 | brew install node
87 | ```
88 |
89 | ### windows
90 |
91 | ```
92 | winget install --id=astral-sh.uv -e
93 | winget install OpenJS.NodeJS.LTS
94 | ```
95 |
96 | ## Build
97 |
98 | ### Run desktop via Gradle
99 |
100 | ```
101 | ./gradlew :desktopApp:run
102 | ```
103 |
104 | ### Building desktop distribution
105 |
106 | ```
107 | ./gradlew :desktop:packageDistributionForCurrentOS
108 | # outputs are written to desktopApp/build/compose/binaries
109 | ```
110 |
111 | ### Run Android via Gradle
112 |
113 | ```
114 | ./gradlew :androidApp:installDebug
115 | ```
116 |
117 | ### Building Android distribution
118 |
119 | ```
120 | ./gradlew clean :androidApp:assembleRelease
121 | # outputs are written to androidApp/build/outputs/apk/release
122 | ```
123 |
124 | ## Thanks
125 |
126 | - [Compose-Multiplatform](https://github.com/JetBrains/compose-multiplatform)
127 | - [MCP-Kotlin-SDK](https://github.com/modelcontextprotocol/kotlin-sdk)
128 | - [DeepSeek](https://api-docs.deepseek.com/zh-cn/)
129 | - [HyperChat](https://github.com/BigSweetPotatoStudio/HyperChat)
130 | - [SillyTavern](https://github.com/SillyTavern/SillyTavern)
--------------------------------------------------------------------------------
/androidApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("multiplatform")
3 | kotlin("plugin.compose")
4 | id("com.android.application")
5 | id("org.jetbrains.compose")
6 | }
7 |
8 | kotlin {
9 | androidTarget()
10 | sourceSets {
11 | val androidMain by getting {
12 | dependencies {
13 | implementation(project(":shared"))
14 | }
15 | }
16 | }
17 | }
18 |
19 | android {
20 | compileSdk = 35
21 | namespace = "org.succlz123.deepco.app"
22 | defaultConfig {
23 | applicationId = "org.succlz123.deepco"
24 | minSdk = 26
25 | targetSdk = 34
26 | versionCode = 1
27 | versionName = "1.0"
28 | }
29 | compileOptions {
30 | sourceCompatibility = JavaVersion.VERSION_21
31 | targetCompatibility = JavaVersion.VERSION_21
32 | }
33 | packaging {
34 | resources {
35 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
36 | excludes += "/META-INF/INDEX.LIST"
37 | excludes += "/META-INF/DEPENDENCIES"
38 | excludes += "mozilla/public-suffix-list.txt"
39 | }
40 | }
41 | kotlin {
42 | jvmToolchain(21)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/androidApp/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/androidApp/src/androidMain/kotlin/org/succlz123/deepco/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app
2 |
3 | import MainAndroidActivity
4 | import android.os.Bundle
5 | import android.view.MotionEvent
6 | import android.view.WindowManager
7 | import androidx.activity.compose.setContent
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.WindowInsets
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.layout.statusBars
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.platform.LocalDensity
16 | import androidx.compose.ui.unit.dp
17 | import org.succlz123.deepco.app.theme.AppTheme
18 | import org.succlz123.lib.screen.ScreenContainer
19 |
20 | class MainActivity : AppCompatActivity() {
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
25 | setContent {
26 | ScreenContainer(this, this, this, this) {
27 | AppTheme {
28 | Box(
29 | modifier = Modifier
30 | .fillMaxSize()
31 | .padding(top = WindowInsets.statusBars.getTop(LocalDensity.current).dp)
32 | ) {
33 | MainAndroidActivity()
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
40 | override fun onTouchEvent(event: MotionEvent?): Boolean {
41 | return super.onTouchEvent(event)
42 | }
43 | }
--------------------------------------------------------------------------------
/androidApp/src/androidMain/kotlin/org/succlz123/deepco/app/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app
2 |
3 | import android.app.Application
4 |
5 | class MainApplication : Application() {
6 |
7 | override fun onCreate() {
8 | super.onCreate()
9 | }
10 | }
--------------------------------------------------------------------------------
/androidApp/src/androidMain/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/androidApp/src/androidMain/res/mipmap/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/androidApp/src/androidMain/res/mipmap/ic_launcher.png
--------------------------------------------------------------------------------
/androidApp/src/androidMain/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/androidApp/src/androidMain/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3C3F41
4 |
--------------------------------------------------------------------------------
/androidApp/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Deep Co
3 |
--------------------------------------------------------------------------------
/androidApp/src/androidMain/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | // this is necessary to avoid the plugins to be loaded multiple times
3 | // in each subproject's classloader
4 | kotlin("jvm") apply false
5 | kotlin("multiplatform") apply false
6 | kotlin("plugin.compose") apply false
7 | kotlin("android") apply false
8 | id("com.android.application") apply false
9 | id("com.android.library") apply false
10 | id("org.jetbrains.compose") apply false
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | google()
16 | maven("https://maven.aliyun.com/repository/public")
17 | mavenCentral()
18 | maven("https://jitpack.io")
19 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
20 | mavenLocal()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/desktopApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat
2 |
3 | plugins {
4 | kotlin("multiplatform")
5 | kotlin("plugin.compose")
6 | id("org.jetbrains.compose")
7 | }
8 |
9 | kotlin {
10 | jvmToolchain(21)
11 | jvm {}
12 | sourceSets {
13 | val jvmMain by getting {
14 | dependencies {
15 | implementation(compose.desktop.currentOs)
16 | implementation(project(":shared"))
17 | }
18 | }
19 | }
20 | }
21 |
22 | compose.desktop {
23 | application {
24 | mainClass = "org.succlz123.deepco.MainKt"
25 | nativeDistributions {
26 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb, TargetFormat.Rpm)
27 | packageName = "DeepCo"
28 | packageVersion = "1.0.6"
29 |
30 | macOS {
31 | iconFile.set(project.file("icon.icns"))
32 | }
33 | windows {
34 | iconFile.set(project.file("icon.ico"))
35 | }
36 | linux {
37 | iconFile.set(project.file("icon.png"))
38 | }
39 |
40 | modules("java.sql", "jdk.unsupported")
41 |
42 | windows {
43 | menu = true
44 | // see https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html
45 | upgradeUuid = "AF792DA6-2EA3-495A-95E5-C3C6CBCB9948"
46 | }
47 |
48 | macOS {
49 | // Use -Pcompose.desktop.mac.sign=true to sign and notarize.
50 | bundleID = "org.succlz123.deepco.desktop"
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/desktopApp/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/desktopApp/icon.icns
--------------------------------------------------------------------------------
/desktopApp/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/desktopApp/icon.ico
--------------------------------------------------------------------------------
/desktopApp/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/desktopApp/icon.png
--------------------------------------------------------------------------------
/desktopApp/src/jvmMain/kotlin/org/succlz123/deepco/main.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco
2 |
3 | import org.succlz123.deepco.app.window.LocalAppApplicationState
4 | import androidx.compose.runtime.CompositionLocalProvider
5 | import androidx.compose.runtime.key
6 | import androidx.compose.ui.window.application
7 | import org.succlz123.lib.init.initComposeMultiplatform
8 | import org.succlz123.deepco.app.main.MainWindow
9 | import org.succlz123.deepco.app.window.rememberApplicationState
10 |
11 |
12 | fun main() = application {
13 | initComposeMultiplatform()
14 | val applicationState = rememberApplicationState {
15 | MainWindow(it)
16 | }
17 | for (window in applicationState.windows) {
18 | key(window) {
19 | CompositionLocalProvider(LocalAppApplicationState provides applicationState) {
20 | window.creator.invoke(window)
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | xcodeproj=./iosApp
3 | android.useAndroidX=true
4 | org.gradle.jvmargs=-Xmx8g
5 | org.gradle.configuration-cache=true
6 | org.gradle.caching=true
7 | org.jetbrains.compose.experimental.jscanvas.enabled=true
8 | org.jetbrains.compose.experimental.macos.enabled=true
9 | kotlin.native.useEmbeddableCompilerJar=true
10 | kotlin.mpp.androidSourceSetLayoutVersion=2
11 | # Enable kotlin/native experimental memory model
12 | kotlin.native.binary.memoryModel=experimental
13 | kotlin.version=2.1.20
14 | agp.version=8.6.0
15 | compose.version=1.7.3
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if %ERRORLEVEL% equ 0 goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if %ERRORLEVEL% equ 0 goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | set EXIT_CODE=%ERRORLEVEL%
84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
86 | exit /b %EXIT_CODE%
87 |
88 | :mainEnd
89 | if "%OS%"=="Windows_NT" endlocal
90 |
91 | :omega
92 |
--------------------------------------------------------------------------------
/iosApp/Configuration/Config.xcconfig:
--------------------------------------------------------------------------------
1 | TEAM_ID=
2 | BUNDLE_ID=org.succlz123.deepco
3 | APP_NAME=DeepCo
4 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/ContentView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 | import shared
4 |
5 | struct ContentView: View {
6 | var body: some View {
7 | ComposeView()
8 | .ignoresSafeArea(.all)
9 | }
10 | }
11 |
12 | struct ComposeView: UIViewControllerRepresentable {
13 | func makeUIViewController(context: Context) -> UIViewController {
14 | Main_iosKt.MainViewController()
15 | }
16 |
17 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
18 | }
19 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | CADisableMinimumFrameDurationOnPhone
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/iOSApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @main
4 | struct iOSApp: App {
5 | var body: some Scene {
6 | WindowGroup {
7 | ContentView()
8 | }
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/screenshots/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/screenshots/1.jpg
--------------------------------------------------------------------------------
/screenshots/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/screenshots/2.jpg
--------------------------------------------------------------------------------
/screenshots/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/screenshots/3.jpg
--------------------------------------------------------------------------------
/screenshots/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/screenshots/4.jpg
--------------------------------------------------------------------------------
/screenshots/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/screenshots/5.jpg
--------------------------------------------------------------------------------
/screenshots/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/screenshots/6.jpg
--------------------------------------------------------------------------------
/screenshots/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/screenshots/7.jpg
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
5 | maven("https://maven.aliyun.com/repository/public")
6 | google()
7 | mavenLocal()
8 | }
9 |
10 | plugins {
11 | val kotlinVersion = extra["kotlin.version"] as String
12 | val agpVersion = extra["agp.version"] as String
13 | val composeVersion = extra["compose.version"] as String
14 |
15 | kotlin("jvm").version(kotlinVersion)
16 | kotlin("multiplatform").version(kotlinVersion)
17 | kotlin("plugin.compose").version(kotlinVersion)
18 | kotlin("plugin.serialization").version(kotlinVersion)
19 | kotlin("android").version(kotlinVersion)
20 | id("com.android.base").version(agpVersion)
21 | id("com.android.application").version(agpVersion)
22 | id("com.android.library").version(agpVersion)
23 | id("org.jetbrains.compose").version(composeVersion)
24 | }
25 | }
26 |
27 | plugins {
28 | id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0")
29 | }
30 |
31 | rootProject.name = "deep-co"
32 |
33 | include(":androidApp")
34 | include(":shared")
35 | include(":desktopApp")
36 | include(":compose-screen:compose-screen")
37 |
--------------------------------------------------------------------------------
/shared/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("OPT_IN_IS_NOT_ENABLED")
2 |
3 | plugins {
4 | kotlin("multiplatform")
5 | kotlin("plugin.compose")
6 | id("com.android.library")
7 | id("org.jetbrains.compose")
8 | kotlin("plugin.serialization")
9 | }
10 |
11 | version = "1.0-SNAPSHOT"
12 |
13 | kotlin {
14 | jvmToolchain(21)
15 |
16 | androidTarget()
17 |
18 | jvm("desktop")
19 |
20 | sourceSets {
21 | all {
22 | languageSettings {
23 | optIn("org.jetbrains.compose.resources.ExperimentalResourceApi")
24 | }
25 | }
26 | commonMain.dependencies {
27 | implementation(compose.runtime)
28 | implementation(compose.foundation)
29 | implementation(compose.material3)
30 | implementation(compose.components.resources)
31 |
32 | api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
33 | api("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5")
34 |
35 | api("io.ktor:ktor-client-core:3.1.0")
36 | api("io.ktor:ktor-client-logging:3.1.0")
37 | api("io.ktor:ktor-client-content-negotiation:3.1.0")
38 | api("io.ktor:ktor-serialization-kotlinx-json:3.1.0")
39 | api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3")
40 | api("io.ktor:ktor-client-okhttp:3.1.0")
41 |
42 | api(project(":compose-screen:compose-screen"))
43 | }
44 | val androidMain by getting {
45 | kotlin.srcDirs("src/jvmMain/kotlin")
46 | }
47 | androidMain.dependencies {
48 | api("androidx.activity:activity-compose:1.10.1")
49 | api("androidx.appcompat:appcompat:1.7.0")
50 | api("androidx.core:core-ktx:1.16.0")
51 |
52 | api("androidx.compose.ui:ui-geometry-android:1.7.8")
53 | api("androidx.compose.ui:ui-graphics-android:1.7.8")
54 |
55 | api("io.modelcontextprotocol:kotlin-sdk:0.4.0")
56 | api("com.anthropic:anthropic-java:1.1.0")
57 | api("com.google.genai:google-genai:0.3.0")
58 |
59 | api("io.coil-kt.coil3:coil-compose:3.1.0")
60 | api("io.coil-kt.coil3:coil-network-okhttp:3.1.0")
61 | }
62 | val desktopMain by getting {
63 | kotlin.srcDirs("src/jvmMain/kotlin")
64 | }
65 | desktopMain.dependencies {
66 | implementation(compose.desktop.common)
67 | implementation(fileTree(mapOf("dir" to "${projectDir}/libs", "include" to listOf("**.jar", "**.aar"))))
68 |
69 | implementation("com.google.code.gson:gson:2.11.0")
70 | implementation("io.github.succlz123:compose-imageloader-desktop:0.0.2")
71 |
72 | implementation("io.modelcontextprotocol:kotlin-sdk:0.4.0")
73 | implementation("com.anthropic:anthropic-java:1.1.0")
74 | implementation("com.google.genai:google-genai:0.3.0")
75 |
76 | // implementation("de.dfki.mary:voice-cmu-slt-hsmm:5.2.1")
77 | implementation("io.github.ikfly:java-tts:1.0.2")
78 | }
79 | }
80 | }
81 |
82 | android {
83 | compileSdk = 35
84 | namespace = "org.succlz123.deepco.app"
85 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
86 | defaultConfig {
87 | minSdk = 26
88 | }
89 | compileOptions {
90 | sourceCompatibility = JavaVersion.VERSION_21
91 | targetCompatibility = JavaVersion.VERSION_21
92 | }
93 | kotlin {
94 | jvmToolchain(21)
95 | }
96 | }
97 |
98 |
--------------------------------------------------------------------------------
/shared/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/main.android.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.runtime.Composable
2 | import org.succlz123.deepco.app.main.MainActivity
3 |
4 | @Composable
5 | fun MainAndroidActivity() {
6 | MainActivity()
7 | }
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/org/succlz123/deepco/app/mcp/MCPClient.android.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.mcp
2 |
3 | import org.succlz123.deepco.app.mcp.biz.Tool
4 | import org.succlz123.deepco.app.mcp.biz.ToolUse
5 |
6 | actual suspend fun connectToServer(serverName: String, command: String?, args: List?): List {
7 | return emptyList()
8 | }
9 |
10 | actual suspend fun callServerTool(serverName: String, toolName: String, args: Map): ToolUse? {
11 | return null
12 | }
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/org/succlz123/deepco/app/tts/TTS.android.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.tts
2 |
3 | actual fun speak(inputText: String) {
4 | }
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/org/succlz123/lib/common/Url.android.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.common
2 |
3 | import android.content.Intent
4 | import androidx.core.net.toUri
5 | import org.succlz123.lib.context.getApplicationContext
6 |
7 | actual fun openURLByBrowser(url: String) {
8 | try {
9 | val context = getApplicationContext() ?: return
10 | val parsedUri = url.toUri()
11 | val intent = Intent(Intent.ACTION_VIEW, parsedUri)
12 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
13 | if (intent.resolveActivity(context.packageManager) != null) {
14 | context.startActivity(intent, null)
15 | }
16 | } catch (e: Exception) {
17 | e.printStackTrace()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/org/succlz123/lib/common/platform.android.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.common
2 |
3 | actual fun getPlatformName(): String {
4 | return "Android"
5 | }
6 |
7 | actual fun isAndroid(): Boolean {
8 | return true
9 | }
10 |
11 | actual fun isDesktop(): Boolean {
12 | return false
13 | }
14 |
15 | actual fun isWindows(): Boolean {
16 | return false
17 | }
18 |
19 | actual fun isLinux(): Boolean {
20 | return false
21 | }
22 |
23 | actual fun isMac(): Boolean {
24 | return false
25 | }
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/org/succlz123/lib/context/Context.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.context
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import androidx.annotation.MainThread
6 |
7 | private var applicationContext: Context? = null
8 |
9 | @SuppressLint("PrivateApi")
10 | @MainThread
11 | fun getApplicationContext(): Context? {
12 | if (applicationContext != null) {
13 | return applicationContext
14 | }
15 | return try {
16 | val clazz = Class.forName("android.app.ActivityThread")
17 | val method = clazz.getMethod("currentApplication")
18 | val ctx = method.invoke(null) as? Context
19 | applicationContext = ctx
20 | ctx
21 | } catch (e: Exception) {
22 | null
23 | }
24 | }
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/org/succlz123/lib/file/File.android.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.file
2 |
3 | lateinit var _HomeFolder: java.io.File
4 |
5 | actual val HomeFolder: File get() = _HomeFolder.toProjectFile()
6 |
7 | actual fun choseFile(suffix: List): String? {
8 | return ""
9 | }
10 |
11 | actual fun choseImgFile(): String? {
12 | return ""
13 | }
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/org/succlz123/lib/image/AsyncImage.android.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.image
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.layout.ContentScale
6 | import androidx.compose.ui.platform.LocalContext
7 | import coil3.request.ImageRequest
8 | import coil3.compose.AsyncImage
9 | import androidx.core.net.toUri
10 |
11 | @Composable
12 | actual fun AsyncImageUrlMultiPlatform(modifier: Modifier, url: String, contentScale: ContentScale) {
13 | if (url.startsWith("http") || url.startsWith("/")) {
14 | AsyncImage(modifier = modifier, model = url, contentDescription = null, contentScale = contentScale)
15 | } else {
16 | val context = LocalContext.current
17 | AsyncImage(
18 | modifier = modifier,
19 | model = ImageRequest.Builder(LocalContext.current)
20 | // .data(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/assets/" + url))
21 | .data("file:///android_asset/$url".toUri())
22 | .build(),
23 | contentDescription = null,
24 | contentScale = contentScale
25 | )
26 | }
27 | }
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/org/succlz123/lib/init/Init.android.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.init
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | actual fun initComposeMultiplatform() {
7 | }
8 |
9 | actual fun destructionComposeMultiplatform() {
10 | }
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/org/succlz123/lib/modifier/Shadow.android.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.modifier
2 |
3 | import android.graphics.BlurMaskFilter
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.draw.drawBehind
6 | import androidx.compose.ui.graphics.Color
7 | import androidx.compose.ui.graphics.Paint
8 | import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
9 | import androidx.compose.ui.graphics.toArgb
10 | import androidx.compose.ui.unit.Dp
11 | import androidx.compose.ui.unit.dp
12 |
13 | actual fun Modifier.shadow(
14 | color: Color,
15 | borderRadius: Dp,
16 | blurRadius: Dp,
17 | offsetY: Dp,
18 | offsetX: Dp,
19 | spread: Dp,
20 | modifier: Modifier
21 | ) = this.then(
22 | modifier.drawBehind {
23 | this.drawIntoCanvas {
24 | val paint = Paint()
25 | val frameworkPaint = paint.asFrameworkPaint()
26 | val spreadPixel = spread.toPx()
27 | val leftPixel = (0f - spreadPixel) + offsetX.toPx()
28 | val topPixel = (0f - spreadPixel) + offsetY.toPx()
29 | val rightPixel = (this.size.width + spreadPixel)
30 | val bottomPixel = (this.size.height + spreadPixel)
31 |
32 | if (blurRadius != 0.dp) {
33 | frameworkPaint.maskFilter =
34 | (BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL))
35 | }
36 |
37 | frameworkPaint.color = color.toArgb()
38 | it.drawRoundRect(
39 | left = leftPixel,
40 | top = topPixel,
41 | right = rightPixel,
42 | bottom = bottomPixel,
43 | radiusX = borderRadius.toPx(),
44 | radiusY = borderRadius.toPx(),
45 | paint
46 | )
47 | }
48 | }
49 | )
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/org/succlz123/lib/setting/Settings.android.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.setting
2 |
3 | actual fun getAppDirPath(dirName: Array): String {
4 | return ""
5 | }
6 |
7 | actual fun openConfigDir(dirName: String) {
8 | }
9 |
10 | actual fun saveConfig2AppDir(dirName: String, fileName: String, content: String) {
11 | }
12 |
13 | actual fun getConfigFromAppDir(dirName: String, fileName: String): String? {
14 | return null
15 | }
16 |
17 | actual fun getAllConfigFromAppDir(dirName: String): List? {
18 | return null
19 | }
20 |
21 | actual fun removeFile(filePath: String) {
22 | }
23 |
24 | actual fun copyFile2ConfigDir(filePath: String, dirName: String, destName: String): String {
25 | return ""
26 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_back.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_chat.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_chat_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_chat_setting.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_close.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_confirm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_confirm.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_copy.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_down.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_elapsed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_elapsed.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_export.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_export.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_home.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_info.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_launcher.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_local_dir.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_local_dir.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_menu.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_model.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_modify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_modify.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_more_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_more_detail.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_my.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_my.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_no.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_no.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_origin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_origin.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_person.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_person.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_prompt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_prompt.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_qq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_qq.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_remove.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_remove.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_select.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_send.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_send.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_show_more.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_show_more.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_speaker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_speaker.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_text.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_token_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_token_down.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_token_total.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_token_total.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_token_up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_token_up.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_tokens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_tokens.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_tool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_tool.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/ic_yes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/ic_yes.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/import.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/import.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/logo_deepseek.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/logo_deepseek.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/logo_google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/logo_google.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/logo_grok.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/logo_grok.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/logo_llm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/logo_llm.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/logo_st.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/succlz123/DeepCo/a995024f6f9df4b4f8d4bc8ffb4a034291474777/shared/src/commonMain/composeResources/drawable/logo_st.jpg
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/AppManifest.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app
2 |
3 | object AppBuildConfig {
4 | var isDebug = true
5 | var showFPS = false
6 |
7 | val APP = "deepco"
8 | val gitHubUrl = "https://github.com/succlz123/DeepCo"
9 |
10 | var versionName = "1.0.6"
11 | var versionCode = 6
12 | }
13 |
14 | object Manifest {
15 | const val MainScreen = "MainScreen"
16 |
17 | const val ChatModeConfigPopupScreen = "ChatConfigPopupScreen"
18 | const val LLMConfigPopupScreen = "LLMAddPopupScreen"
19 | const val MCPAddPopupScreen = "MCPAddPopupScreen"
20 | const val PromptAddPopupScreen = "PromptAddPopupScreen"
21 | const val PromptCharacterAddPopupScreen = "PromptCharacterAddPopupScreen"
22 | const val PromptDetailPopupScreen = "PromptDetailPopupScreen"
23 | const val PromptSelectedPopupScreen = "ChatUserDetailPopupScreen"
24 | const val ChatUserConfigPopupScreen = "ChatUserAddPopupScreen"
25 | const val ChatUserSelectPopupScreen = "ChatUserSelectPopupScreen"
26 | }
27 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/api/AppApiService.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.api
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.engine.okhttp.OkHttp
5 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
6 | import io.ktor.client.plugins.defaultRequest
7 | import io.ktor.client.plugins.logging.LogLevel
8 | import io.ktor.client.plugins.logging.Logger
9 | import io.ktor.client.plugins.logging.Logging
10 | import io.ktor.client.plugins.sse.SSE
11 | import io.ktor.serialization.kotlinx.json.json
12 | import org.succlz123.deepco.app.AppBuildConfig
13 | import org.succlz123.deepco.app.json.appJson
14 | import java.security.SecureRandom
15 | import java.security.cert.X509Certificate
16 | import javax.net.ssl.SSLContext
17 | import javax.net.ssl.X509TrustManager
18 |
19 | object AppApiService {
20 |
21 | val httpClient = HttpClient(OkHttp) {
22 | defaultRequest {
23 | }
24 | engine {
25 | this.config {
26 | val sslContext = SSLContext.getInstance("SSL")
27 | sslContext.init(null, arrayOf(trustAllManager), null)
28 | sslSocketFactory(sslContext.socketFactory, trustAllManager)
29 | }
30 | }
31 | install(Logging) {
32 | level = LogLevel.HEADERS
33 | logger = object : Logger {
34 | override fun log(message: String) {
35 | if (AppBuildConfig.isDebug) {
36 | println("HTTP Client: message: $message")
37 | }
38 | }
39 | }
40 | }
41 | install(SSE) {
42 | showCommentEvents()
43 | showRetryEvents()
44 | }
45 | install(ContentNegotiation) {
46 | json(appJson)
47 | }
48 | }
49 |
50 | private fun getSLLContext(): SSLContext? {
51 | var sslContext: SSLContext? = null
52 | try {
53 | sslContext = SSLContext.getInstance("TLS")
54 | sslContext.init(null, arrayOf(trustAllManager), SecureRandom())
55 | } catch (e: Exception) {
56 | e.printStackTrace()
57 | }
58 | return sslContext
59 | }
60 |
61 | private val trustAllManager = object : X509TrustManager {
62 | override fun checkClientTrusted(chain: Array?, authType: String?) {}
63 | override fun checkServerTrusted(chain: Array?, authType: String?) {}
64 | override fun getAcceptedIssuers(): Array = arrayOf()
65 | }
66 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/base/AppTable.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.base
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.layout.width
7 | import androidx.compose.foundation.lazy.LazyColumn
8 | import androidx.compose.foundation.lazy.itemsIndexed
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.mutableStateMapOf
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.layout.onSizeChanged
17 | import androidx.compose.ui.platform.LocalDensity
18 | import androidx.compose.ui.unit.dp
19 |
20 | interface TabIndexGet {
21 |
22 | fun getFieldByIndex(index: Int): String?
23 | }
24 |
25 | class TableTitleItem(val title: String, val width: Int, val weight: Float)
26 |
27 |
28 | @Composable
29 | fun AppTable(modifier: Modifier, titleList: List, content: List, indexInterceptor: @Composable ((index: Int, item: TabIndexGet) -> Boolean)? = null) {
30 | val titleSizeMap = remember { mutableStateMapOf() }
31 | LazyColumn(modifier = modifier) {
32 | item {
33 | Row(
34 | modifier = Modifier.background(MaterialTheme.colorScheme.surfaceContainer, shape = MaterialTheme.shapes.medium)
35 | ) {
36 | titleList.forEachIndexed { index, titleItem ->
37 | Text(
38 | text = titleItem.title, modifier = Modifier.then(
39 | if (titleItem.width != 0) {
40 | Modifier.width(titleItem.width.dp)
41 | } else {
42 | Modifier.weight(1f)
43 | }
44 | ).onSizeChanged {
45 | titleSizeMap[index] = it.width
46 | }.padding(horizontal = 12.dp, vertical = 6.dp), style = MaterialTheme.typography.headlineSmall
47 | )
48 | }
49 | }
50 | }
51 | itemsIndexed(content, key = { index, item ->
52 | item.toString()
53 | }) { index, item ->
54 | Row(modifier = Modifier.padding(horizontal = 12.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically) {
55 | titleList.forEachIndexed { index, titleItem ->
56 | if (indexInterceptor != null && indexInterceptor.invoke(index, item)) {
57 | } else {
58 | Text(
59 | text = item.getFieldByIndex(index).orEmpty(),
60 | modifier = Modifier.width(((titleSizeMap[index] ?: 1) / LocalDensity.current.density).dp),
61 | style = MaterialTheme.typography.bodyMedium
62 | )
63 | }
64 | }
65 | }
66 | AppHorizontalDivider()
67 | }
68 | }
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/base/BaseBizViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.base
2 |
3 | import org.succlz123.lib.vm.BaseViewModel
4 |
5 | open class BaseBizViewModel : BaseViewModel() {
6 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/base/BaseDialogCard.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.base
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.ColumnScope
8 | import androidx.compose.foundation.layout.Row
9 | import androidx.compose.foundation.layout.Spacer
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.layout.size
14 | import androidx.compose.foundation.rememberScrollState
15 | import androidx.compose.foundation.verticalScroll
16 | import androidx.compose.material3.Card
17 | import androidx.compose.material3.CardDefaults
18 | import androidx.compose.material3.MaterialTheme
19 | import androidx.compose.material3.Text
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.ui.Alignment
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.graphics.Color
24 | import androidx.compose.ui.text.font.FontWeight
25 | import androidx.compose.ui.unit.dp
26 | import deep_co.shared.generated.resources.Res
27 | import deep_co.shared.generated.resources.ic_close
28 | import org.jetbrains.compose.resources.painterResource
29 | import org.succlz123.deepco.app.theme.LocalAppDialogPadding
30 | import org.succlz123.deepco.app.theme.LocalContentPadding
31 | import org.succlz123.lib.click.noRippleClick
32 | import org.succlz123.lib.modifier.shadow
33 | import org.succlz123.lib.screen.LocalScreenNavigator
34 |
35 | @Composable
36 | fun BaseDialogCard(
37 | content: @Composable ColumnScope.() -> Unit
38 | ) {
39 | Box(modifier = Modifier.fillMaxSize().background(Color.Black.copy(0.2f)).padding(LocalAppDialogPadding.current).noRippleClick {}, contentAlignment = Alignment.Center) {
40 | Card(
41 | modifier = Modifier.fillMaxSize().align(Alignment.Center).shadow(),
42 | elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
43 | colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
44 | content = content
45 | )
46 | }
47 | }
48 |
49 | @Composable
50 | fun BaseDialogCardWithTitleColumnScroll(
51 | title: String,
52 | bottomFixedContent: @Composable (ColumnScope.() -> Unit)? = null,
53 | content: @Composable ColumnScope.() -> Unit
54 | ) {
55 | val screenNavigator = LocalScreenNavigator.current
56 | BaseDialogCard {
57 | Column(modifier = Modifier.padding(LocalContentPadding.current)) {
58 | Row {
59 | Text(
60 | modifier = Modifier,
61 | text = title,
62 | style = MaterialTheme.typography.headlineMedium,
63 | maxLines = 1
64 | )
65 | Spacer(modifier = Modifier.weight(1f))
66 | Image(
67 | modifier = Modifier.size(14.dp).noRippleClick {
68 | screenNavigator.pop()
69 | },
70 | contentDescription = null,
71 | painter = painterResource(resource = Res.drawable.ic_close),
72 | )
73 | }
74 | Spacer(modifier = Modifier.height(16.dp))
75 | AppHorizontalDivider()
76 | Column(modifier = Modifier.verticalScroll(state = rememberScrollState()).weight(1f).padding(vertical = 16.dp)) {
77 | content()
78 | }
79 | if (bottomFixedContent != null) {
80 | Spacer(modifier = Modifier.height(16.dp))
81 | bottomFixedContent()
82 | }
83 | }
84 | }
85 | }
86 |
87 | @Composable
88 | fun BaseDialogCardWithTitleNoneScroll(
89 | title: String,
90 | content: @Composable ColumnScope.() -> Unit
91 | ) {
92 | val screenNavigator = LocalScreenNavigator.current
93 | BaseDialogCard {
94 | Column(modifier = Modifier.padding(LocalContentPadding.current)) {
95 | Row {
96 | Text(
97 | modifier = Modifier,
98 | text = title,
99 | style = MaterialTheme.typography.headlineMedium,
100 | fontWeight = FontWeight.Normal,
101 | maxLines = 1
102 | )
103 | Spacer(modifier = Modifier.weight(1f))
104 | Image(
105 | modifier = Modifier.size(14.dp).noRippleClick {
106 | screenNavigator.pop()
107 | },
108 | contentDescription = null,
109 | painter = painterResource(resource = Res.drawable.ic_close),
110 | )
111 | }
112 | Spacer(modifier = Modifier.height(16.dp))
113 | AppHorizontalDivider()
114 | Spacer(modifier = Modifier.height(16.dp))
115 | content()
116 | }
117 | }
118 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/base/CustomExposedDropdownMenu.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.base
2 |
3 | import androidx.compose.foundation.layout.heightIn
4 | import androidx.compose.material3.DropdownMenuItem
5 | import androidx.compose.material3.ExperimentalMaterial3Api
6 | import androidx.compose.material3.ExposedDropdownMenuBox
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.Text
9 | import androidx.compose.material3.TextField
10 | import androidx.compose.material3.TextFieldDefaults
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.LaunchedEffect
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.runtime.mutableStateOf
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.runtime.setValue
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.unit.dp
19 |
20 | open class DropdownMenuDes(val showName: String, val tag: Any)
21 |
22 |
23 | @OptIn(ExperimentalMaterial3Api::class)
24 | @Composable
25 | fun CustomExposedDropdownMenu(
26 | options: List,
27 | labelStr: String,
28 | defaultName: String? = null,
29 | onSelect: (DropdownMenuDes) -> Unit
30 | ) {
31 | var expanded by remember {
32 | mutableStateOf(false)
33 | }
34 | var selectedOptionText by remember { mutableStateOf("") }
35 | LaunchedEffect(defaultName) {
36 | val found = options.find { it.showName == defaultName }
37 | if (found != null) {
38 | selectedOptionText = found.showName
39 | onSelect(found)
40 | }
41 | }
42 | ExposedDropdownMenuBox(
43 | expanded = expanded,
44 | onExpandedChange = {
45 | expanded = !expanded
46 | }, modifier = Modifier
47 | ) {
48 | TextField(
49 | value = selectedOptionText,
50 | modifier = Modifier.menuAnchor(),
51 | onValueChange = { v: String -> selectedOptionText = v },
52 | label = {
53 | Text(labelStr, style = MaterialTheme.typography.bodyMedium)
54 | },
55 | singleLine = true,
56 | colors = TextFieldDefaults.colors(
57 | cursorColor = MaterialTheme.colorScheme.primary,
58 | focusedTextColor = MaterialTheme.colorScheme.primary,
59 | focusedLabelColor = MaterialTheme.colorScheme.tertiary,
60 | disabledLabelColor = MaterialTheme.colorScheme.outlineVariant
61 | ),
62 | readOnly = true,
63 | )
64 | ExposedDropdownMenu(
65 | expanded = expanded,
66 | onDismissRequest = { expanded = false },
67 | modifier = Modifier.heightIn(max = 620.dp)
68 | ) {
69 | options.forEach { option ->
70 | DropdownMenuItem(text = { Text(option.showName) }, onClick = {
71 | selectedOptionText = option.showName
72 | expanded = false
73 | onSelect(option)
74 | })
75 | }
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/base/GlobalFocusViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.base
2 |
3 | import androidx.compose.ui.focus.FocusRequester
4 | import kotlinx.coroutines.flow.MutableStateFlow
5 | import org.succlz123.lib.vm.BaseViewModel
6 |
7 | class GlobalFocusViewModel : BaseViewModel() {
8 |
9 | val curFocusRequester = MutableStateFlow(null)
10 |
11 | val curFocusRequesterParent = MutableStateFlow(null)
12 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/base/LocalDirStorage.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.base
2 |
3 | import org.succlz123.deepco.app.AppBuildConfig
4 | import org.succlz123.deepco.app.json.appJson
5 | import org.succlz123.lib.setting.getAllConfigFromAppDir
6 | import org.succlz123.lib.setting.getConfigFromAppDir
7 | import org.succlz123.lib.setting.removeFile
8 | import org.succlz123.lib.setting.saveConfig2AppDir
9 | import java.io.File
10 |
11 | class LocalDirStorage(val dir: String) {
12 |
13 | inline fun getAllFileDir(noinline default: (() -> List?)? = null): List? {
14 | val list = getAllConfigFromAppDir(AppBuildConfig.APP + File.separator + dir)
15 | if (list.isNullOrEmpty()) {
16 | return default?.invoke()
17 | }
18 | return list.mapNotNull {
19 | try {
20 | appJson.decodeFromString(it)
21 | } catch (_: Exception) {
22 | null
23 | }
24 | }
25 | }
26 |
27 | inline fun put(any: S, fileName: String) {
28 | saveConfig2AppDir(AppBuildConfig.APP + File.separator + dir, "$fileName.json", appJson.encodeToString(any))
29 | }
30 |
31 | fun remove(fileName: String) {
32 | removeFile(AppBuildConfig.APP + File.separator + dir + File.separator + "$fileName.json")
33 | }
34 |
35 | inline fun get(fileName: String, noinline default: (() -> S?)? = null): S? {
36 | return try {
37 | val l = getConfigFromAppDir(AppBuildConfig.APP + File.separator + dir, "$fileName.json")
38 | if (l.isNullOrEmpty()) {
39 | return default?.invoke()
40 | }
41 | return appJson.decodeFromString(l)
42 | } catch (_: Exception) {
43 | null
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/base/LocalStorage.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.base
2 |
3 | import org.succlz123.deepco.app.AppBuildConfig
4 | import org.succlz123.deepco.app.json.appJson
5 | import org.succlz123.lib.setting.getConfigFromAppDir
6 | import org.succlz123.lib.setting.saveConfig2AppDir
7 |
8 | const val DIR_HISTORY = "history"
9 | const val JSON_CHAT_MODE = "chatMode.json"
10 | const val JSON_LLM = "llm.json"
11 | const val JSON_MCP = "mcp.json"
12 | const val JSON_PROMPT = "prompt.json"
13 | const val JSON_USER = "user.json"
14 | const val JSON_SETTING = "setting.json"
15 |
16 | class LocalStorage(val localFileName: String) {
17 |
18 | companion object {
19 |
20 | fun stringToUniqueIntId(input: String): Int {
21 | var hashCode = input.hashCode()
22 | if (hashCode < 0) {
23 | hashCode = -hashCode
24 | }
25 | val offset = input.length * 1000
26 | hashCode += offset
27 | return hashCode
28 | }
29 | }
30 |
31 | inline fun put(any: S) {
32 | saveConfig2AppDir(AppBuildConfig.APP, localFileName, appJson.encodeToString(any))
33 | }
34 |
35 | inline fun get(noinline default: (() -> S?)? = null): S? {
36 | return try {
37 | val l = getConfigFromAppDir(AppBuildConfig.APP, localFileName)
38 | if (l.isNullOrEmpty()) {
39 | return default?.invoke()
40 | }
41 | return appJson.decodeFromString(l)
42 | } catch (e: Exception) {
43 | null
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/base/MainTitleLayout.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.base
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.ColumnScope
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.foundation.layout.width
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.material3.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.unit.dp
17 |
18 | @Composable
19 | fun MainTitleLayout(
20 | modifier: Modifier = Modifier,
21 | text: String,
22 | subText: String = "",
23 | topRightContent: @Composable (() -> Unit)? = null,
24 | content: @Composable ColumnScope.() -> Unit
25 | ) {
26 | Column(modifier = modifier) {
27 | Row(
28 | modifier = Modifier.padding(16.dp, 10.dp, 16.dp, 10.dp).fillMaxWidth(),
29 | verticalAlignment = Alignment.CenterVertically
30 | ) {
31 | Text(text = text, style = MaterialTheme.typography.headlineMedium)
32 | Spacer(modifier = Modifier.height(1.dp).width(12.dp))
33 | Text(text = subText, style = MaterialTheme.typography.titleMedium)
34 | Spacer(modifier = Modifier.height(1.dp).weight(1f))
35 | topRightContent?.invoke()
36 | }
37 | AppHorizontalDivider()
38 | content()
39 | }
40 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/character/TavernCardV2.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.character
2 |
3 | import kotlinx.serialization.Serializable
4 | import kotlinx.serialization.json.JsonElement
5 |
6 | // https://github.com/malfoyslastname/character-card-spec-v2
7 | // https://github.com/bradennapier/character-cards-v2?tab=readme-ov-file
8 | @Serializable
9 | data class TavernCardV2(
10 | val spec: String = "chara_card_v2",
11 | val spec_version: String = "2.0",
12 | val data: TavernCardData
13 | )
14 |
15 | @Serializable
16 | data class TavernCardData(
17 | val name: String,
18 | val description: String,
19 | val personality: String? = null,
20 | val scenario: String? = null,
21 | val first_mes: String? = null,
22 | val mes_example: String? = null,
23 |
24 | val creator_notes: String? = null,
25 | val system_prompt: String? = null,
26 | val post_history_instructions: String? = null,
27 | val alternate_greetings: List? = null,
28 | val character_book: CharacterBook? = null,
29 |
30 | val tags: List? = null,
31 | val creator: String? = null,
32 | val character_version: String? = null,
33 | val extensions: Map? = null
34 | )
35 |
36 | @Serializable
37 | data class CharacterBook(
38 | val name: String? = null,
39 | val description: String? = null,
40 | val scan_depth: Int? = null,
41 | val token_budget: Int? = null,
42 | val recursive_scanning: Boolean? = null,
43 | val extensions: Map = emptyMap(),
44 | val entries: List? = null,
45 | ) {
46 | companion object {
47 | const val POSITION_BEFORE_CHAR = "before_char"
48 | const val POSITION_AFTER_CHAR = "after_char"
49 | }
50 | }
51 |
52 | @Serializable
53 | data class CharacterBookEntry(
54 | val keys: List? = null,
55 | val content: String? = null,
56 | val extensions: Map = emptyMap(),
57 | val enabled: Boolean? = null,
58 | val insertion_order: Int? = null,
59 | val case_sensitive: Boolean? = null,
60 |
61 | val name: String? = null,
62 | val priority: Int? = null,
63 |
64 | val id: Int? = null,
65 | val comment: String? = null,
66 | val selective: Boolean? = null,
67 | val secondary_keys: List? = null,
68 | val constant: Boolean? = null,
69 | val position: String? = null // "before_char" or "after_char"
70 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/chat/msg/ChatMessage.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.chat.msg
2 |
3 | import androidx.compose.runtime.MutableState
4 | import androidx.compose.runtime.mutableStateOf
5 | import kotlinx.serialization.KSerializer
6 | import kotlinx.serialization.Serializable
7 | import kotlinx.serialization.descriptors.PrimitiveKind
8 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
9 | import kotlinx.serialization.encoding.Decoder
10 | import kotlinx.serialization.encoding.Encoder
11 | import org.succlz123.lib.time.hhMMssSSS
12 |
13 | @Serializable
14 | data class ChatMessageData(
15 | val id: Long = NON_ID,
16 | val list: List = emptyList()
17 | ) {
18 |
19 | companion object {
20 | const val NON_ID = 0L
21 | }
22 | }
23 |
24 | object MutableStateStringSerializer : KSerializer> {
25 | override val descriptor = PrimitiveSerialDescriptor("MutableStateString", PrimitiveKind.STRING)
26 |
27 | override fun serialize(encoder: Encoder, value: MutableState) {
28 | encoder.encodeString(value.value)
29 | }
30 |
31 | override fun deserialize(decoder: Decoder): MutableState {
32 | return mutableStateOf(decoder.decodeString())
33 | }
34 | }
35 |
36 | @Serializable
37 | data class ChatMessage(
38 | val createTime: Long = System.currentTimeMillis(),
39 | val updateTime: Long = System.currentTimeMillis(),
40 | var id: Long = createTime,
41 | @Serializable(with = MutableStateStringSerializer::class)
42 | val content: MutableState = mutableStateOf(""),
43 | @Serializable(with = MutableStateStringSerializer::class)
44 | val reasoningContent: MutableState = mutableStateOf(""),
45 | val isFromMe: Boolean = false,
46 |
47 | var model: String = "",
48 | var elapsedTime: Long = 0,
49 | var promptTokens: Int = 0,
50 | var completionTokens: Int = 0,
51 |
52 | @Serializable(with = MutableStateStringSerializer::class)
53 | val toolCall: MutableState = mutableStateOf(""),
54 | val confirmCallTool: Boolean? = null,
55 |
56 | val agentKey: String = "",
57 | val isCalled: Boolean = false,
58 | val isTask: Boolean = false,
59 | val requestType: String? = null,
60 | val isSend: Boolean = false,
61 | ) {
62 |
63 | companion object {
64 | const val TYPE_LOADING = -999L
65 | }
66 |
67 | fun changeContent(label: String) {
68 | content.value = label
69 | }
70 |
71 | fun changeReasoningContent(label: String) {
72 | reasoningContent.value = label
73 | }
74 |
75 | fun changeToolCall(label: String) {
76 | toolCall.value = label
77 | }
78 |
79 | fun isLoading(): Boolean {
80 | return id == TYPE_LOADING
81 | }
82 |
83 | fun createTimeFormatted(): String {
84 | return createTime.hhMMssSSS()
85 | }
86 |
87 | fun elapsedTimeFormatted(): String {
88 | return "%.2f".format(elapsedTime / 1000f) + "s"
89 | }
90 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/chat/prompt/PromptInfo.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.chat.prompt
2 |
3 | import kotlinx.serialization.Serializable
4 | import org.succlz123.deepco.app.character.TavernCardV2
5 |
6 | @Serializable
7 | data class PromptInfo(
8 | val id: Long,
9 | val createTime: Long,
10 | val updateTime: Long,
11 | val type: PromptType,
12 | val name: String,
13 | val description: String,
14 | val avatar: String? = null,
15 | val tavernCardV2: TavernCardV2? = null,
16 | val links: List = emptyList(),
17 | val isDefault: Boolean = false,
18 | )
19 |
20 | @Serializable
21 | enum class PromptType {
22 | ROLE,
23 | TAVERN_CARD_V2,
24 | NORMAL
25 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/chat/user/ChatUser.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.chat.user
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ChatUser(
7 | val id: Long,
8 | val avatar: String,
9 | val name: String,
10 | val description: String,
11 |
12 | val isDefault: Boolean = false,
13 | val isSelected: Boolean = false,
14 |
15 | val createTime: Long = System.currentTimeMillis(),
16 | val updateTime: Long = System.currentTimeMillis()
17 | )
18 |
19 | val DEFAULT_CHAT_USER = ChatUser(
20 | id = 0, avatar = "", name = "User", description = "This is the default user.", isDefault = true, isSelected = true
21 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/json/AppJson.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.json
2 |
3 | import kotlinx.serialization.json.Json
4 |
5 | val appJson = Json {
6 | ignoreUnknownKeys = true
7 | prettyPrint = true
8 | isLenient = true
9 | encodeDefaults = true
10 | }
11 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/llm/ChatResponse.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.llm
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ChatResponse(
7 | val text: String? = null,
8 | val thinking: String? = null,
9 | val created: String? = null,
10 | val id: String? = null,
11 | val model: String? = null,
12 | var errorMsg: String? = null
13 | ) {
14 | var elapsedTime: Long = 0
15 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/llm/deepseek/DeepSeekRequest.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.llm.deepseek
2 |
3 | import kotlinx.serialization.Serializable
4 | import org.succlz123.deepco.app.mcp.biz.ToolRequest
5 |
6 | @Serializable
7 | data class DeepSeekRequest(
8 | val frequency_penalty: Float? = null,
9 | val logprobs: Boolean? = null,
10 | val max_tokens: Int? = null,
11 | val messages: List? = null,
12 | val model: String? = null,
13 | val presence_penalty: Float? = null,
14 | val response_format: ResponseFormat? = null,
15 | val stop: Boolean? = null,
16 | val stream: Boolean? = null,
17 | val stream_options: StreamOption? = null,
18 | val temperature: Float? = null,
19 | val tool_choice: String? = null,
20 | val tools: List? = null,
21 | val top_logprobs: Boolean? = null,
22 | val top_p: Float? = null,
23 | val top_k: Float? = null
24 | )
25 |
26 | @Serializable
27 | data class RequestMessage(
28 | val content: String? = null,
29 | val role: String? = null
30 | )
31 |
32 | @Serializable
33 | data class ResponseFormat(
34 | val type: String? = null
35 | )
36 |
37 | @Serializable
38 | data class StreamOption(
39 | val include_usage: Boolean? = null
40 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/llm/deepseek/DeepSeekResponse.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.llm.deepseek
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class DeepSeekResponse(
7 | val choices: List? = null,
8 | val created: Int? = null,
9 | val id: String? = null,
10 | val model: String? = null,
11 | val `object`: String? = null,
12 | val system_fingerprint: String? = null,
13 | val error: Error? = null,
14 | val usage: Usage? = null
15 | ) {
16 | var errorMsg: String = ""
17 | }
18 |
19 | @Serializable
20 | data class Choice(
21 | val finish_reason: String? = null,
22 | val index: Int? = null,
23 | val logprobs: String? = null,
24 | val message: Message? = null,
25 | val delta: Delta? = null
26 | )
27 |
28 | @Serializable
29 | data class Usage(
30 | val completion_tokens: Int? = null,
31 | val prompt_cache_hit_tokens: Int? = null,
32 | val prompt_cache_miss_tokens: Int? = null,
33 | val prompt_tokens: Int? = null,
34 | val prompt_tokens_details: PromptTokensDetails? = null,
35 | val total_tokens: Int? = null
36 | )
37 |
38 | @Serializable
39 | data class PromptTokensDetails(
40 | val cached_tokens: Int? = null
41 | )
42 |
43 | @Serializable
44 | data class Error(
45 | val message: String? = null,
46 | val param: String? = null,
47 | val code: String? = null,
48 | val type: String? = null
49 | )
50 |
51 | @Serializable
52 | data class Delta(
53 | val content: String? = null,
54 | val reasoning_content: String? = null
55 | )
56 |
57 |
58 | @Serializable
59 | data class Message(
60 | val content: String? = null,
61 | val reasoning_content: String? = null,
62 | val content_attached: Boolean? = null,
63 | val content_attachment: List? = null,
64 | val content_context: ContentContext? = null,
65 | val content_date: Long? = null,
66 | val content_error: String? = null,
67 | val content_status: String? = null,
68 | val content_usage: ContentUsage? = null,
69 | val role: String? = null,
70 | val tool_call_id: String? = null,
71 | val tool_calls: List? = null
72 | )
73 |
74 | @Serializable
75 | class ContentContext
76 |
77 | @Serializable
78 | data class ContentUsage(
79 | val completion_tokens: Int? = null,
80 | val prompt_tokens: Int? = null,
81 | val total_tokens: Int? = null
82 | )
83 |
84 | @Serializable
85 | data class ToolCall(
86 | val index: Int? = null,
87 | val id: String? = null,
88 | val type: String? = null,
89 | val function: Function? = null,
90 | val origin_name: String? = null,
91 | val restore_name: String? = null,
92 | )
93 |
94 | @Serializable
95 | data class Function(
96 | val arguments: String? = null,
97 | val argumentsJSON: ArgumentsJSON? = null,
98 | val name: String? = null
99 | )
100 |
101 | @Serializable
102 | data class ArgumentsJSON(
103 | val districtId: String? = null
104 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/llm/gemini/Gemini.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.llm.gemini
2 |
3 | import org.succlz123.deepco.app.llm.ChatResponse
4 | import org.succlz123.deepco.app.chat.msg.ChatMessage
5 | import org.succlz123.deepco.app.ui.chat.ChatModeConfig
6 |
7 |
8 | expect suspend fun geminiChat(key: String, model: String, prompt: String, modeConfig: ChatModeConfig, content: List): ChatResponse
9 |
10 | expect suspend fun geminiChatStream(key: String, model: String, prompt: String, modeConfig: ChatModeConfig, content: List, cb: (ChatResponse, Boolean) -> Unit)
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/llm/grok/Grok.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.llm.grok
2 |
3 | import org.succlz123.deepco.app.llm.ChatResponse
4 | import org.succlz123.deepco.app.chat.msg.ChatMessage
5 | import org.succlz123.deepco.app.ui.chat.ChatModeConfig
6 |
7 | expect suspend fun grokChat(key: String, model: String, prompt: String, modeConfig: ChatModeConfig, content: List): ChatResponse
8 |
9 | expect suspend fun grokChatStream(key: String, model: String, prompt: String, modeConfig: ChatModeConfig, content: List, cb: (ChatResponse, Boolean) -> Unit)
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/main/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.main
2 |
3 | import androidx.compose.runtime.Composable
4 | import org.succlz123.deepco.app.Manifest
5 | import org.succlz123.deepco.app.ui.chat.ChatModeConfigDialog
6 | import org.succlz123.deepco.app.ui.llm.LLMConfigDialog
7 | import org.succlz123.deepco.app.ui.mcp.MCPAddDialog
8 | import org.succlz123.deepco.app.ui.prompt.PromptAddDialog
9 | import org.succlz123.deepco.app.ui.prompt.PromptCharacterAddDialog
10 | import org.succlz123.deepco.app.ui.prompt.PromptDetailDialog
11 | import org.succlz123.deepco.app.ui.user.ChatUserConfigDialog
12 | import org.succlz123.deepco.app.ui.prompt.PromptSelectedDialog
13 | import org.succlz123.deepco.app.ui.user.ChatUserSelectDialog
14 | import org.succlz123.lib.screen.ScreenHost
15 | import org.succlz123.lib.screen.rememberScreenNavigator
16 |
17 | @Composable
18 | fun MainActivity() {
19 | val screenNavigator = rememberScreenNavigator()
20 | ScreenHost(screenNavigator = screenNavigator, rootScreenName = Manifest.MainScreen) {
21 | groupScreen(screenName = (Manifest.MainScreen)) {
22 | MainScreen()
23 | }
24 | itemScreen(screenName = (Manifest.LLMConfigPopupScreen)) {
25 | LLMConfigDialog()
26 | }
27 | itemScreen(screenName = (Manifest.ChatModeConfigPopupScreen)) {
28 | ChatModeConfigDialog()
29 | }
30 | itemScreen(screenName = (Manifest.MCPAddPopupScreen)) {
31 | MCPAddDialog()
32 | }
33 | itemScreen(screenName = (Manifest.PromptAddPopupScreen)) {
34 | PromptAddDialog()
35 | }
36 | itemScreen(screenName = (Manifest.PromptCharacterAddPopupScreen)) {
37 | PromptCharacterAddDialog()
38 | }
39 | itemScreen(screenName = (Manifest.PromptDetailPopupScreen)) {
40 | PromptDetailDialog()
41 | }
42 | itemScreen(screenName = (Manifest.PromptSelectedPopupScreen)) {
43 | PromptSelectedDialog()
44 | }
45 | itemScreen(screenName = (Manifest.ChatUserConfigPopupScreen)) {
46 | ChatUserConfigDialog()
47 | }
48 | itemScreen(screenName = (Manifest.ChatUserSelectPopupScreen)) {
49 | ChatUserSelectDialog()
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/main/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.main
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.sharp.Add
5 | import androidx.compose.material.icons.sharp.Build
6 | import androidx.compose.material.icons.sharp.Home
7 | import androidx.compose.material.icons.sharp.Person
8 | import androidx.compose.material.icons.sharp.Settings
9 | import androidx.compose.material.icons.sharp.Star
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.graphics.vector.ImageVector
12 | import kotlinx.coroutines.flow.MutableStateFlow
13 | import org.succlz123.deepco.app.i18n.strings
14 | import org.succlz123.deepco.app.ui.chat.MainChatTab
15 | import org.succlz123.deepco.app.ui.llm.MainLLMTab
16 | import org.succlz123.deepco.app.ui.mcp.MainMcpTab
17 | import org.succlz123.deepco.app.ui.prompt.MainPromptTab
18 | import org.succlz123.deepco.app.ui.setting.MainSettingTab
19 | import org.succlz123.deepco.app.ui.user.MainUserTab
20 | import org.succlz123.lib.common.isDesktop
21 | import org.succlz123.lib.vm.BaseViewModel
22 |
23 | data class MainSelectItem(val name: @Composable () -> String, val icon: ImageVector, val content: @Composable () -> Unit)
24 |
25 | class MainViewModel : BaseViewModel() {
26 |
27 | companion object {
28 |
29 | val MAIN_TITLE = buildList {
30 | add(MainSelectItem(@Composable {
31 | strings().tabChat
32 | }, Icons.Sharp.Home, @Composable {
33 | MainChatTab()
34 | }))
35 | add(MainSelectItem(@Composable { strings().tabLLM }, Icons.Sharp.Star, @Composable {
36 | MainLLMTab()
37 | }))
38 | add(MainSelectItem(@Composable { strings().tabPrompt }, Icons.Sharp.Add, @Composable {
39 | MainPromptTab()
40 | }))
41 | add(MainSelectItem(@Composable { strings().tabUser }, Icons.Sharp.Person, @Composable {
42 | MainUserTab()
43 | }))
44 | if (isDesktop()) {
45 | add(MainSelectItem(@Composable { strings().tabMCP }, Icons.Sharp.Build, @Composable {
46 | MainMcpTab()
47 | }))
48 | }
49 | add(MainSelectItem(@Composable { strings().tabSetting }, Icons.Sharp.Settings, @Composable {
50 | MainSettingTab()
51 | }))
52 | }
53 | }
54 |
55 | val selectItem = MutableStateFlow(MAIN_TITLE.first())
56 | }
57 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/mcp/MCPClient.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.mcp
2 |
3 | import org.succlz123.deepco.app.mcp.biz.Tool
4 | import org.succlz123.deepco.app.mcp.biz.ToolUse
5 |
6 | expect suspend fun connectToServer(serverName: String, command: String?, args: List?): List
7 |
8 | expect suspend fun callServerTool(serverName: String, toolName: String, args: Map): ToolUse?
9 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/mcp/biz/McpConfig.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.mcp.biz
2 |
3 | import kotlinx.serialization.Serializable
4 | import org.succlz123.deepco.app.base.TabIndexGet
5 |
6 | @Serializable
7 | data class McpConfig(
8 | val name: String?,
9 | val command: String?,
10 | val args: List?,
11 | val disabled: Boolean
12 | ) : TabIndexGet {
13 |
14 | override fun getFieldByIndex(index: Int): String? {
15 | return if (index == 0) {
16 | name
17 | } else if (index == 1) {
18 | command
19 | } else if (index == 2) {
20 | args.orEmpty().joinToString(separator = " ")
21 | } else if (index == 3) {
22 | disabled.toString()
23 | } else {
24 | null
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/mcp/biz/Tool.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.mcp.biz
2 |
3 | import kotlinx.serialization.Serializable
4 | import kotlinx.serialization.json.JsonObject
5 |
6 |
7 | @Serializable
8 | data class Tool(
9 | val name: String? = null,
10 | val description: String? = null,
11 | val input_schema: InputSchema? = null,
12 | )
13 |
14 | @Serializable
15 | data class InputSchema(
16 | val properties: JsonObject? = null,
17 | val required: List? = null,
18 | val type: String? = null
19 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/mcp/biz/ToolConfig.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.mcp.biz
2 |
3 | enum class ToolConfig {
4 | Automatic, Manual;
5 |
6 | companion object {
7 | fun names(): List = entries.map { it.name }
8 | }
9 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/mcp/biz/ToolRequest.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.mcp.biz
2 |
3 | import kotlinx.serialization.Serializable
4 | import kotlinx.serialization.json.JsonObject
5 |
6 |
7 | @Serializable
8 | data class ToolRequest(
9 | val type: String = "function",
10 | val function: ToolFunction? = null
11 | )
12 |
13 | @Serializable
14 | data class ToolFunction(
15 | val name: String? = null,
16 | val description: String? = null,
17 | val parameters: ToolParameters? = null,
18 | )
19 |
20 | @Serializable
21 | data class ToolParameters(
22 | val properties: JsonObject? = null,
23 | val required: List? = null,
24 | val type: String? = null
25 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/mcp/biz/ToolUse.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.mcp.biz
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 |
6 | @Serializable
7 | data class ToolUse(
8 | val toolName: String? = null,
9 | val toolResult: String? = null,
10 | )
11 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/theme/ColorResource.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | object ColorResource {
6 | val deepseek = Color(0xFF4B92F7)
7 | }
8 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/theme/ThemeJson.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.theme
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ThemeJson(
7 | val coreColors: CoreColors,
8 | val description: String,
9 | val extendedColors: List = emptyList(),
10 | val palettes: Palettes? = null,
11 | val schemes: Schemes = Schemes(),
12 | val seed: String = ""
13 | )
14 |
15 | @Serializable
16 | data class CoreColors(
17 | val neutral: String? = null,
18 | val neutralVariant: String? = null,
19 | val primary: String? = null,
20 | val secondary: String? = null,
21 | val tertiary: String? = null
22 | )
23 |
24 | @Serializable
25 | data class ExtendedColor(
26 | val color: String? = null,
27 | val description: String? = null,
28 | val harmonized: Boolean? = null,
29 | val name: String? = null
30 | )
31 |
32 | @Serializable
33 | data class Palettes(
34 | val neutral: Percentage? = null,
35 | val `neutral-variant`: Percentage? = null,
36 | val primary: Percentage? = null,
37 | val secondary: Percentage? = null,
38 | val tertiary: Percentage? = null
39 | )
40 |
41 | @Serializable
42 | data class Schemes(
43 | val dark: ColorTheme? = null,
44 | val `dark-high-contrast`: ColorTheme? = null,
45 | val `dark-medium-contrast`: ColorTheme? = null,
46 | val light: ColorTheme? = null,
47 | val `light-high-contrast`: ColorTheme? = null,
48 | val `light-medium-contrast`: ColorTheme? = null
49 | )
50 |
51 | @Serializable
52 | data class Percentage(
53 | val `0`: String? = null,
54 | val `5`: String? = null,
55 | val `10`: String? = null,
56 | val `15`: String? = null,
57 | val `20`: String? = null,
58 | val `25`: String? = null,
59 | val `30`: String? = null,
60 | val `35`: String? = null,
61 | val `40`: String? = null,
62 | val `50`: String? = null,
63 | val `60`: String? = null,
64 | val `70`: String? = null,
65 | val `80`: String? = null,
66 | val `90`: String? = null,
67 | val `95`: String? = null,
68 | val `98`: String? = null,
69 | val `99`: String? = null,
70 | val `100`: String? = null,
71 | )
72 |
73 | @Serializable
74 | data class ColorTheme(
75 | val background: String? = null,
76 | val error: String? = null,
77 | val errorContainer: String? = null,
78 | val inverseOnSurface: String? = null,
79 | val inversePrimary: String? = null,
80 | val inverseSurface: String? = null,
81 | val onBackground: String? = null,
82 | val onError: String? = null,
83 | val onErrorContainer: String? = null,
84 | val onPrimary: String? = null,
85 | val onPrimaryContainer: String? = null,
86 | val onPrimaryFixed: String? = null,
87 | val onPrimaryFixedVariant: String? = null,
88 | val onSecondary: String? = null,
89 | val onSecondaryContainer: String? = null,
90 | val onSecondaryFixed: String? = null,
91 | val onSecondaryFixedVariant: String? = null,
92 | val onSurface: String? = null,
93 | val onSurfaceVariant: String? = null,
94 | val onTertiary: String? = null,
95 | val onTertiaryContainer: String? = null,
96 | val onTertiaryFixed: String? = null,
97 | val onTertiaryFixedVariant: String? = null,
98 | val outline: String? = null,
99 | val outlineVariant: String? = null,
100 | val primary: String? = null,
101 | val primaryContainer: String? = null,
102 | val primaryFixed: String? = null,
103 | val primaryFixedDim: String? = null,
104 | val scrim: String? = null,
105 | val secondary: String? = null,
106 | val secondaryContainer: String? = null,
107 | val secondaryFixed: String? = null,
108 | val secondaryFixedDim: String? = null,
109 | val shadow: String? = null,
110 | val surface: String? = null,
111 | val surfaceBright: String? = null,
112 | val surfaceContainer: String? = null,
113 | val surfaceContainerHigh: String? = null,
114 | val surfaceContainerHighest: String? = null,
115 | val surfaceContainerLow: String? = null,
116 | val surfaceContainerLowest: String? = null,
117 | val surfaceDim: String? = null,
118 | val surfaceTint: String? = null,
119 | val surfaceVariant: String? = null,
120 | val tertiary: String? = null,
121 | val tertiaryContainer: String? = null,
122 | val tertiaryFixed: String? = null,
123 | val tertiaryFixedDim: String? = null
124 | )
125 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/tts/TTS.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.tts
2 |
3 | expect fun speak(inputText: String)
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/chat/ChatModeConfig.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.chat
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ChatModeConfig(
7 | val maxOutTokens: Int = 4096, val temperature: Float = 1f, val topP: Float = 1f, val topK: Float = 0f, val frequencyPenalty: Float = 0f, val presencePenalty: Float = 0f
8 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/chat/LLMEmptyView.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.chat
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material3.Icon
8 | import androidx.compose.material3.LocalContentColor
9 | import androidx.compose.material3.Text
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.filled.AddCircle
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.unit.dp
17 | import org.succlz123.deepco.app.i18n.strings
18 |
19 | @Composable
20 | fun LLMEmptyView() = Box(Modifier.fillMaxSize()) {
21 | Column(Modifier.align(Alignment.Center)) {
22 | Icon(
23 | Icons.Default.AddCircle,
24 | contentDescription = null,
25 | tint = LocalContentColor.current.copy(alpha = 0.60f),
26 | modifier = Modifier.align(Alignment.CenterHorizontally)
27 | )
28 | Text(
29 | strings().errorLLMProviderIsEmpty,
30 | color = LocalContentColor.current.copy(alpha = 0.60f),
31 | style = MaterialTheme.typography.displayMedium,
32 | modifier = Modifier.align(Alignment.CenterHorizontally).padding(16.dp)
33 | )
34 | }
35 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/llm/LLM.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.llm
2 |
3 | import kotlinx.serialization.Serializable
4 | import org.succlz123.deepco.app.base.TabIndexGet
5 | import org.succlz123.lib.time.hhMMssSSS
6 |
7 | @Serializable
8 | data class LLM(
9 | val provider: String,
10 | val name: String,
11 | val modes: List,
12 | val apiKey: String,
13 | val baseUrl: String,
14 | val id: Long,
15 | val createTime: Long,
16 | val updateTime: Long,
17 | private val defaultMode: String? = null,
18 | ) : TabIndexGet {
19 |
20 | fun createDataFmt(): String {
21 | return createTime.hhMMssSSS()
22 | }
23 |
24 | fun getMaskedKey(): String {
25 | return if (apiKey.isEmpty()) {
26 | ""
27 | } else {
28 | apiKey.replaceRange(3, apiKey.length - 3, "****")
29 | }
30 | }
31 |
32 | fun getSelectedModeMode(): String {
33 | return if (defaultMode.isNullOrEmpty()) {
34 | return modes.firstOrNull().orEmpty()
35 | } else {
36 | defaultMode
37 | }
38 | }
39 |
40 | override fun getFieldByIndex(index: Int): String? {
41 | return if (index == 0) {
42 | provider
43 | } else if (index == 1) {
44 | name
45 | } else if (index == 2) {
46 | modes.joinToString(separator = "\n")
47 | } else if (index == 3) {
48 | baseUrl
49 | } else if (index == 4) {
50 | getMaskedKey()
51 | } else if (index == 5) {
52 | createDataFmt()
53 | } else {
54 | null
55 | }
56 | }
57 | }
58 |
59 | @Serializable
60 | data class LLMLocalConfig(
61 | val llmList: List,
62 | val selectedName: String?
63 | )
64 |
65 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/mcp/MCPAddDialog.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.mcp
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.text.KeyboardOptions
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.text.TextStyle
14 | import androidx.compose.ui.text.input.KeyboardType
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.ui.unit.sp
17 | import org.succlz123.deepco.app.base.AppConfirmButton
18 | import org.succlz123.deepco.app.base.AsteriskText
19 | import org.succlz123.deepco.app.base.BaseDialogCardWithTitleColumnScroll
20 | import org.succlz123.deepco.app.base.CustomEdit
21 | import org.succlz123.deepco.app.i18n.strings
22 | import org.succlz123.lib.screen.LocalScreenNavigator
23 | import org.succlz123.lib.screen.viewmodel.globalViewModel
24 |
25 | @Composable
26 | fun MCPAddDialog() {
27 | val screenNavigator = LocalScreenNavigator.current
28 | val vm = globalViewModel {
29 | MainMcpViewModel()
30 | }
31 | val name = remember { mutableStateOf("") }
32 | val command = remember { mutableStateOf("") }
33 | val args = remember { mutableStateOf("") }
34 | BaseDialogCardWithTitleColumnScroll(strings().mcpConfig, bottomFixedContent = {
35 | Box(modifier = Modifier.fillMaxWidth()) {
36 | AppConfirmButton(Modifier.align(Alignment.BottomEnd), onClick = {
37 | vm.add(name.value, command.value, args.value.split(" "))
38 | screenNavigator.pop()
39 | })
40 | }
41 | }) {
42 | AsteriskText(text = strings().name)
43 | Spacer(modifier = Modifier.height(12.dp))
44 | CustomEdit(
45 | name.value,
46 | textStyle = TextStyle.Default.copy(fontSize = 14.sp),
47 | keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
48 | hint = "",
49 | onValueChange = {
50 | name.value = it
51 | }, modifier = Modifier.fillMaxWidth()
52 | )
53 | Spacer(modifier = Modifier.height(12.dp))
54 | AsteriskText(text = strings().command)
55 | Spacer(modifier = Modifier.height(12.dp))
56 | CustomEdit(
57 | command.value,
58 | textStyle = TextStyle.Default.copy(fontSize = 14.sp),
59 | hint = "npx",
60 | keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
61 | onValueChange = {
62 | command.value = it
63 | }, modifier = Modifier.fillMaxWidth()
64 | )
65 | Spacer(modifier = Modifier.height(12.dp))
66 | AsteriskText(text = strings().args)
67 | Spacer(modifier = Modifier.height(12.dp))
68 | CustomEdit(
69 | args.value,
70 | textStyle = TextStyle.Default.copy(fontSize = 14.sp),
71 | hint = "-y @modelcontextprotocol/server-filesystem ~/Downloads",
72 | keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
73 | onValueChange = {
74 | args.value = it
75 | }, modifier = Modifier.fillMaxWidth()
76 | )
77 | }
78 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/prompt/MainPromptViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.prompt
2 |
3 | import kotlinx.coroutines.flow.MutableStateFlow
4 | import org.succlz123.deepco.app.base.BaseBizViewModel
5 | import org.succlz123.deepco.app.base.JSON_PROMPT
6 | import org.succlz123.deepco.app.base.LocalStorage
7 | import org.succlz123.deepco.app.character.TavernCardV2
8 | import org.succlz123.deepco.app.chat.prompt.PromptInfo
9 | import org.succlz123.deepco.app.chat.prompt.PromptType
10 | import org.succlz123.deepco.app.chat.role.ChatRoleDefine
11 |
12 | class MainPromptViewModel : BaseBizViewModel() {
13 |
14 | companion object {
15 | val CHARACTER = listOf(
16 | "https://stchub.vip/",
17 | "https://pygmalion.chat/explore",
18 | "https://realm.risuai.net/",
19 | "https://janitorai.com/search/hero",
20 | "https://www.chub.ai/"
21 | )
22 | }
23 |
24 | val promptLocalStorage = LocalStorage(JSON_PROMPT)
25 |
26 | val prompt = MutableStateFlow>(emptyList())
27 |
28 | init {
29 | val local = promptLocalStorage.get>().orEmpty()
30 | if (local.find { it.isDefault } == null) {
31 | promptLocalStorage.put(local.toMutableList().apply {
32 | addAll(ChatRoleDefine.roles)
33 | })
34 | prompt.value = ChatRoleDefine.roles
35 | } else {
36 | prompt.value = local
37 | }
38 | }
39 |
40 | fun changePrompt(promptInfo: PromptInfo, avatar: String, name: String, description: String) {
41 | prompt.value = prompt.value.toMutableList().apply {
42 | replaceAll {
43 | if (it.id == promptInfo.id) {
44 | promptInfo.copy(avatar = avatar, name = name, description = description, updateTime = System.currentTimeMillis())
45 | } else {
46 | it
47 | }
48 | }
49 | }
50 | promptLocalStorage.put(prompt.value)
51 | }
52 |
53 | fun add(type: PromptType, name: String, description: String) {
54 | val id = System.currentTimeMillis()
55 | val pro = PromptInfo(id, id, id, type, name, description)
56 | prompt.value = prompt.value.toMutableList().apply {
57 | if (this.find { it.name == name } == null) {
58 | add(pro)
59 | }
60 | }
61 | promptLocalStorage.put(prompt.value)
62 | }
63 |
64 | fun addTavernCard(id: Long, avatar: String, tavernCardV2: TavernCardV2) {
65 | val pro = PromptInfo(
66 | id, id, id, PromptType.TAVERN_CARD_V2, tavernCardV2.data.name, tavernCardV2.data.description,
67 | avatar = avatar,
68 | tavernCardV2 = tavernCardV2
69 | )
70 | prompt.value = prompt.value.toMutableList().apply {
71 | if (this.find { it.name == tavernCardV2.data.name } == null) {
72 | add(pro)
73 | }
74 | }
75 | promptLocalStorage.put(prompt.value)
76 | }
77 |
78 | fun remove(info: PromptInfo) {
79 | prompt.value = prompt.value.toMutableList().apply {
80 | removeIf {
81 | it.id == info.id
82 | }
83 | }
84 | promptLocalStorage.put(prompt.value)
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/prompt/PromptAddDialog.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.prompt
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.text.KeyboardOptions
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.text.TextStyle
14 | import androidx.compose.ui.text.input.KeyboardType
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.ui.unit.sp
17 | import org.succlz123.deepco.app.base.AppConfirmButton
18 | import org.succlz123.deepco.app.base.AsteriskText
19 | import org.succlz123.deepco.app.base.BaseDialogCardWithTitleColumnScroll
20 | import org.succlz123.deepco.app.base.CustomEdit
21 | import org.succlz123.deepco.app.base.CustomExposedDropdownMenu
22 | import org.succlz123.deepco.app.base.DropdownMenuDes
23 | import org.succlz123.deepco.app.chat.prompt.PromptType
24 | import org.succlz123.deepco.app.i18n.strings
25 | import org.succlz123.lib.screen.LocalScreenNavigator
26 | import org.succlz123.lib.screen.viewmodel.globalViewModel
27 |
28 | @Composable
29 | fun PromptAddDialog() {
30 | val screenNavigator = LocalScreenNavigator.current
31 | val vm = globalViewModel {
32 | MainPromptViewModel()
33 | }
34 | val name = remember { mutableStateOf("") }
35 | val description = remember { mutableStateOf("") }
36 | val selectedType = remember { mutableStateOf(PromptType.NORMAL) }
37 | val strings = strings()
38 | BaseDialogCardWithTitleColumnScroll(strings.promptConfiguration, bottomFixedContent = {
39 | Box(modifier = Modifier.fillMaxWidth()) {
40 | AppConfirmButton(modifier = Modifier.align(Alignment.CenterEnd)) {
41 | if (name.value.isEmpty()) {
42 | screenNavigator.toast(strings.errorNameIsEmpty)
43 | } else if (description.value.isEmpty()) {
44 | screenNavigator.toast(strings.errorDescriptionIsEmpty)
45 | } else {
46 | val found = vm.prompt.value.find { it.name == name.value }
47 | if (found != null) {
48 | screenNavigator.toast(strings.errorNameIsDuplicate)
49 | } else {
50 | vm.add(selectedType.value, name.value, description.value)
51 | screenNavigator.pop()
52 | }
53 | }
54 | }
55 | }
56 | }) {
57 | AsteriskText(text = strings.type)
58 | Spacer(modifier = Modifier.height(12.dp))
59 | CustomExposedDropdownMenu(listOf(PromptType.NORMAL, PromptType.ROLE).map {
60 | DropdownMenuDes(it.name, it)
61 | }, strings.selectType) { item ->
62 | selectedType.value = item.tag as PromptType
63 | }
64 | Spacer(modifier = Modifier.height(12.dp))
65 | AsteriskText(text = strings.name)
66 | Spacer(modifier = Modifier.height(12.dp))
67 | CustomEdit(
68 | name.value,
69 | textStyle = TextStyle.Default.copy(fontSize = 14.sp),
70 | keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
71 | hint = "123",
72 | onValueChange = {
73 | name.value = it
74 | }, modifier = Modifier.fillMaxWidth()
75 | )
76 | Spacer(modifier = Modifier.height(12.dp))
77 | AsteriskText(text = strings.description)
78 | Spacer(modifier = Modifier.height(12.dp))
79 | CustomEdit(
80 | description.value,
81 | textStyle = TextStyle.Default.copy(fontSize = 14.sp),
82 | hint = "321",
83 | singleLine = false,
84 | scrollHeight = 250.dp,
85 | keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
86 | onValueChange = {
87 | description.value = it
88 | }, modifier = Modifier.fillMaxWidth()
89 | )
90 | }
91 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/prompt/PromptDetailDialog.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.prompt
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.text.KeyboardOptions
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.text.TextStyle
16 | import androidx.compose.ui.text.input.KeyboardType
17 | import androidx.compose.ui.unit.dp
18 | import androidx.compose.ui.unit.sp
19 | import org.succlz123.deepco.app.AppBuildConfig
20 | import org.succlz123.deepco.app.base.AppConfirmButton
21 | import org.succlz123.deepco.app.base.AsteriskText
22 | import org.succlz123.deepco.app.base.BaseDialogCardWithTitleColumnScroll
23 | import org.succlz123.deepco.app.base.CustomEdit
24 | import org.succlz123.deepco.app.chat.prompt.PromptInfo
25 | import org.succlz123.deepco.app.i18n.strings
26 | import org.succlz123.deepco.app.theme.ColorResource
27 | import org.succlz123.deepco.app.ui.user.UserAvatarView
28 | import org.succlz123.lib.click.noRippleClick
29 | import org.succlz123.lib.file.choseImgFile
30 | import org.succlz123.lib.screen.LocalScreenNavigator
31 | import org.succlz123.lib.screen.LocalScreenRecord
32 | import org.succlz123.lib.screen.value
33 | import org.succlz123.lib.screen.viewmodel.globalViewModel
34 | import java.io.File
35 |
36 | @Composable
37 | fun PromptDetailDialog() {
38 | val screenNavigator = LocalScreenNavigator.current
39 | val promptInfo = LocalScreenRecord.current.arguments.value("item")
40 | val strings = strings()
41 | if (promptInfo == null) {
42 | screenNavigator.toast(strings.errorPromptInfoIsNull)
43 | screenNavigator.pop()
44 | return
45 | }
46 | val vm = globalViewModel {
47 | MainPromptViewModel()
48 | }
49 | val id = remember { mutableStateOf(promptInfo.id) }
50 | val avatar = remember { mutableStateOf(promptInfo.avatar.orEmpty()) }
51 | val name = remember { mutableStateOf(promptInfo.name) }
52 | val description = remember { mutableStateOf(promptInfo.description) }
53 | BaseDialogCardWithTitleColumnScroll(strings.promptConfiguration, bottomFixedContent = {
54 | Box(modifier = Modifier.fillMaxWidth()) {
55 | AppConfirmButton(modifier = Modifier.align(Alignment.CenterEnd)) {
56 | if (avatar.value != promptInfo.avatar || name.value != promptInfo.name || description.value != promptInfo.description) {
57 | val savedPath = if (avatar.value == promptInfo.avatar) {
58 | avatar.value
59 | } else {
60 | org.succlz123.lib.setting.copyFile2ConfigDir(avatar.value, AppBuildConfig.APP + File.separator + "avatar", "${id.value}")
61 | }
62 | vm.changePrompt(promptInfo, savedPath, name.value, description.value)
63 | }
64 | screenNavigator.pop()
65 | }
66 | }
67 | }) {
68 | Box(modifier = Modifier.fillMaxWidth().noRippleClick {
69 | val choseFile = choseImgFile()
70 | if (choseFile != null) {
71 | avatar.value = choseFile
72 | }
73 | }) {
74 | UserAvatarView(Modifier.align(Alignment.Center), 82.dp, avatar.value)
75 | }
76 | Spacer(modifier = Modifier.height(12.dp))
77 | AsteriskText(text = strings.name)
78 | Spacer(modifier = Modifier.height(12.dp))
79 | CustomEdit(
80 | name.value,
81 | textStyle = TextStyle.Default.copy(fontSize = 14.sp),
82 | keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
83 | onValueChange = {
84 | name.value = it
85 | }, modifier = Modifier.fillMaxWidth()
86 | )
87 | Spacer(modifier = Modifier.height(12.dp))
88 | AsteriskText(text = strings.description)
89 | Spacer(modifier = Modifier.height(12.dp))
90 | CustomEdit(
91 | description.value,
92 | textStyle = TextStyle.Default.copy(fontSize = 14.sp),
93 | singleLine = false,
94 | maxLines = 10,
95 | onValueChange = {
96 | description.value = it
97 | }, scrollHeight = 250.dp, modifier = Modifier.fillMaxWidth()
98 | )
99 | }
100 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/resize/ResizablePanel.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.resize
2 |
3 | import androidx.compose.animation.core.Spring.StiffnessLow
4 | import androidx.compose.animation.core.SpringSpec
5 | import androidx.compose.animation.core.animateFloatAsState
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.width
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
12 | import androidx.compose.material.icons.automirrored.filled.ArrowForward
13 | import androidx.compose.material3.Icon
14 | import androidx.compose.material3.LocalContentColor
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.runtime.getValue
17 | import androidx.compose.runtime.mutableStateOf
18 | import androidx.compose.runtime.setValue
19 | import androidx.compose.ui.Alignment
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.graphics.graphicsLayer
22 | import androidx.compose.ui.unit.dp
23 | import org.succlz123.deepco.app.util.SplitterState
24 | import org.succlz123.lib.click.noRippleClick
25 |
26 | class PanelState {
27 | val collapsedSize = 24.dp
28 | var expandedSize by mutableStateOf(120.dp)
29 | val expandedSizeMin = 80.dp
30 | var isExpanded by mutableStateOf(true)
31 | val splitter = SplitterState()
32 | }
33 |
34 | @Composable
35 | fun ResizablePanel(
36 | modifier: Modifier,
37 | state: PanelState,
38 | content: @Composable () -> Unit,
39 | ) {
40 | val alpha by animateFloatAsState(if (state.isExpanded) 1f else 0f, SpringSpec(stiffness = StiffnessLow))
41 | Box(modifier) {
42 | Box(Modifier.fillMaxSize().graphicsLayer(alpha = alpha)) {
43 | content()
44 | }
45 | Icon(
46 | if (state.isExpanded) Icons.AutoMirrored.Filled.ArrowBack else Icons.AutoMirrored.Filled.ArrowForward,
47 | contentDescription = if (state.isExpanded) "Collapse" else "Expand",
48 | tint = LocalContentColor.current,
49 | modifier = Modifier.width(24.dp).noRippleClick {
50 | state.isExpanded = !state.isExpanded
51 | }.padding(4.dp).align(Alignment.TopEnd)
52 | )
53 | }
54 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/resize/ResizablePanelTabView.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.resize
2 |
3 | import androidx.compose.foundation.layout.Row
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 |
12 | @Composable
13 | fun ResizablePanelTabView(text: String) {
14 | Row(
15 | verticalAlignment = Alignment.CenterVertically
16 | ) {
17 | Text(
18 | text,
19 | style = MaterialTheme.typography.bodyMedium,
20 | modifier = Modifier.padding(horizontal = 6.dp)
21 | )
22 | }
23 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/setting/MainSettingViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.setting
2 |
3 | import kotlinx.coroutines.flow.MutableStateFlow
4 | import kotlinx.serialization.Serializable
5 | import org.succlz123.deepco.app.base.BaseBizViewModel
6 | import org.succlz123.deepco.app.base.JSON_SETTING
7 | import org.succlz123.deepco.app.base.LocalStorage
8 | import org.succlz123.deepco.app.i18n.LocaleLanguage
9 | import org.succlz123.deepco.app.i18n.changeLanguage
10 | import org.succlz123.deepco.app.mcp.biz.ToolConfig
11 | import org.succlz123.deepco.app.theme.AppDarkThemeConfig
12 | import org.succlz123.deepco.app.theme.AppThemeConfig
13 | import org.succlz123.deepco.app.theme.changeAppDarkTheme
14 | import org.succlz123.deepco.app.theme.changeAppTheme
15 |
16 | @Serializable
17 | data class SettingLocal(
18 | val llmToolMode: String = ToolConfig.Manual.name,
19 | val languageMode: String = LocaleLanguage.ZH.name,
20 | val appThemeConfig: String = AppThemeConfig.Blue.name,
21 | val appDarkThemeConfig: String = AppDarkThemeConfig.Sys.name
22 | ) {
23 | }
24 |
25 | class MainSettingViewModel : BaseBizViewModel() {
26 |
27 | companion object {
28 | val settingLocalStorage = LocalStorage(JSON_SETTING)
29 |
30 | fun getSettingLocal(): SettingLocal {
31 | return settingLocalStorage.get() ?: SettingLocal()
32 | }
33 |
34 | fun language(name: String = getSettingLocal().languageMode): LocaleLanguage {
35 | return when (name) {
36 | LocaleLanguage.ZH.name -> LocaleLanguage.ZH
37 | LocaleLanguage.EN.name -> LocaleLanguage.EN
38 | else -> LocaleLanguage.ZH
39 | }
40 | }
41 |
42 | fun appTheme(name: String = getSettingLocal().appThemeConfig): AppThemeConfig {
43 | return when (name) {
44 | AppThemeConfig.Blue.name -> AppThemeConfig.Blue
45 | AppThemeConfig.Green.name -> AppThemeConfig.Green
46 | AppThemeConfig.Red.name -> AppThemeConfig.Red
47 | AppThemeConfig.Yellow.name -> AppThemeConfig.Yellow
48 | else -> AppThemeConfig.Blue
49 | }
50 | }
51 |
52 | fun appDarkTheme(name: String = getSettingLocal().appDarkThemeConfig): AppDarkThemeConfig {
53 | return when (name) {
54 | AppDarkThemeConfig.Sys.name -> AppDarkThemeConfig.Sys
55 | AppDarkThemeConfig.Dark.name -> AppDarkThemeConfig.Dark
56 | AppDarkThemeConfig.Light.name -> AppDarkThemeConfig.Light
57 | else -> AppDarkThemeConfig.Sys
58 | }
59 | }
60 | }
61 |
62 | val settingLocal = MutableStateFlow(getSettingLocal())
63 |
64 | fun change(
65 | llmToolMode: String? = null,
66 | languageMode: String? = null,
67 | appTheme: String? = null,
68 | appThemeDark: String? = null
69 | ) {
70 | if (llmToolMode.isNullOrEmpty() && languageMode.isNullOrEmpty() && appTheme.isNullOrEmpty() && appThemeDark.isNullOrEmpty()) {
71 | return
72 | }
73 | val setting = settingLocal.value
74 | val s = setting.copy(
75 | llmToolMode = if (llmToolMode.isNullOrEmpty()) {
76 | setting.llmToolMode
77 | } else {
78 | llmToolMode
79 | },
80 | languageMode = if (languageMode.isNullOrEmpty()) {
81 | setting.languageMode
82 | } else {
83 | changeLanguage(languageMode)
84 | languageMode
85 | },
86 | appThemeConfig = if (appTheme.isNullOrEmpty()) {
87 | setting.appThemeConfig
88 | } else {
89 | changeAppTheme(appTheme)
90 | appTheme
91 | },
92 | appDarkThemeConfig = if (appThemeDark.isNullOrEmpty()) {
93 | setting.appDarkThemeConfig
94 | } else {
95 | changeAppDarkTheme(appThemeDark)
96 | appThemeDark
97 | }
98 | )
99 | settingLocal.value = s
100 | settingLocalStorage.put(s)
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/user/ChatUserConfigDialog.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.user
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.text.KeyboardOptions
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.text.TextStyle
14 | import androidx.compose.ui.text.input.KeyboardType
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.ui.unit.sp
17 | import org.succlz123.deepco.app.AppBuildConfig
18 | import org.succlz123.deepco.app.base.AppConfirmButton
19 | import org.succlz123.deepco.app.base.AsteriskText
20 | import org.succlz123.deepco.app.base.BaseDialogCardWithTitleColumnScroll
21 | import org.succlz123.deepco.app.base.CustomEdit
22 | import org.succlz123.deepco.app.chat.user.ChatUser
23 | import org.succlz123.deepco.app.i18n.strings
24 | import org.succlz123.lib.click.noRippleClick
25 | import org.succlz123.lib.file.choseImgFile
26 | import org.succlz123.lib.screen.LocalScreenNavigator
27 | import org.succlz123.lib.screen.LocalScreenRecord
28 | import org.succlz123.lib.screen.value
29 | import org.succlz123.lib.screen.viewmodel.globalViewModel
30 | import java.io.File
31 |
32 | @Composable
33 | fun ChatUserConfigDialog() {
34 | val screenNavigator = LocalScreenNavigator.current
35 | val vm = globalViewModel {
36 | MainUserViewModel()
37 | }
38 | val chatUser = LocalScreenRecord.current.arguments.value("item")
39 | val avatar = remember { mutableStateOf(chatUser?.avatar.orEmpty()) }
40 | val id = remember {
41 | mutableStateOf(
42 | if (chatUser == null) {
43 | System.currentTimeMillis()
44 | } else {
45 | chatUser.id
46 | }
47 | )
48 | }
49 | val name = remember { mutableStateOf(chatUser?.name.orEmpty()) }
50 | val description = remember { mutableStateOf(chatUser?.description.orEmpty()) }
51 | val strings = strings()
52 | BaseDialogCardWithTitleColumnScroll(strings.chatUserConfig, bottomFixedContent = {
53 | Box(modifier = Modifier.fillMaxWidth()) {
54 | AppConfirmButton(Modifier.align(Alignment.BottomEnd), onClick = {
55 | if (name.value.isNullOrEmpty()) {
56 | screenNavigator.toast(strings.errorNameIsEmpty)
57 | } else if (description.value.isNullOrEmpty()) {
58 | screenNavigator.toast(strings.errorDescriptionIsEmpty)
59 | } else {
60 | if (chatUser == null && vm.chatUsers.value.find { it.name == name.value } != null) {
61 | screenNavigator.toast(strings.errorNameIsDuplicate)
62 | } else {
63 | val savedPath = if (avatar.value == chatUser?.avatar) {
64 | avatar.value
65 | } else {
66 | org.succlz123.lib.setting.copyFile2ConfigDir(avatar.value, AppBuildConfig.APP + File.separator + "avatar", "${id.value}")
67 | }
68 | vm.set(chatUser != null, id.value, savedPath, name.value, description.value)
69 | screenNavigator.pop()
70 | }
71 | }
72 | })
73 | }
74 | }) {
75 | Box(modifier = Modifier.fillMaxWidth().noRippleClick {
76 | val choseFile = choseImgFile()
77 | if (choseFile != null) {
78 | avatar.value = choseFile
79 | }
80 | }) {
81 | UserAvatarView(Modifier.align(Alignment.Center), 82.dp, avatar.value)
82 | }
83 | Spacer(modifier = Modifier.height(12.dp))
84 | AsteriskText(strings().name)
85 | Spacer(modifier = Modifier.height(12.dp))
86 | CustomEdit(
87 | name.value,
88 | textStyle = TextStyle.Default.copy(fontSize = 14.sp),
89 | keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
90 | hint = "123",
91 | onValueChange = {
92 | name.value = it
93 | }, modifier = Modifier.fillMaxWidth()
94 | )
95 | Spacer(modifier = Modifier.height(12.dp))
96 | AsteriskText(strings().description)
97 | Spacer(modifier = Modifier.height(12.dp))
98 | CustomEdit(
99 | description.value,
100 | textStyle = TextStyle.Default.copy(fontSize = 14.sp),
101 | hint = "321",
102 | singleLine = false,
103 | scrollHeight = 160.dp,
104 | keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
105 | onValueChange = {
106 | description.value = it
107 | }, modifier = Modifier.fillMaxWidth()
108 | )
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/user/ChatUserSelectDialog.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.user
2 |
3 | import androidx.compose.foundation.BorderStroke
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.border
6 | import androidx.compose.foundation.layout.Arrangement
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.Spacer
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.layout.size
14 | import androidx.compose.foundation.lazy.grid.GridCells
15 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
16 | import androidx.compose.foundation.lazy.grid.items
17 | import androidx.compose.material3.MaterialTheme
18 | import androidx.compose.material3.Text
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.graphics.ColorFilter
23 | import androidx.compose.ui.unit.dp
24 | import deep_co.shared.generated.resources.Res
25 | import deep_co.shared.generated.resources.ic_select
26 | import org.jetbrains.compose.resources.painterResource
27 | import org.succlz123.deepco.app.base.BaseDialogCardWithTitleNoneScroll
28 | import org.succlz123.deepco.app.i18n.strings
29 | import org.succlz123.lib.click.noRippleClick
30 | import org.succlz123.lib.screen.LocalScreenNavigator
31 | import org.succlz123.lib.screen.viewmodel.globalViewModel
32 |
33 | @Composable
34 | fun ChatUserSelectDialog() {
35 | val screenNavigator = LocalScreenNavigator.current
36 | val vm = globalViewModel {
37 | MainUserViewModel()
38 | }
39 | BaseDialogCardWithTitleNoneScroll(strings().selectAChatUser) {
40 | LazyVerticalGrid(
41 | columns = GridCells.Fixed(4),
42 | modifier = Modifier.fillMaxWidth(),
43 | verticalArrangement = Arrangement.spacedBy(12.dp),
44 | horizontalArrangement = Arrangement.spacedBy(12.dp)
45 | ) {
46 | items(vm.chatUsers.value) { item ->
47 | Column(
48 | modifier = Modifier.fillMaxWidth().noRippleClick {
49 | vm.changeDefault(item)
50 | screenNavigator.pop()
51 | }.border(BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant), shape = MaterialTheme.shapes.large)
52 | .padding(horizontal = 12.dp, vertical = 12.dp)
53 | ) {
54 | Box(modifier = Modifier.fillMaxWidth()) {
55 | UserAvatarView(Modifier.align(Alignment.Center), 82.dp, item.avatar)
56 | if (item.isSelected) {
57 | Image(
58 | modifier = Modifier.size(24.dp).align(Alignment.TopEnd),
59 | contentDescription = null,
60 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primaryContainer),
61 | painter = painterResource(resource = Res.drawable.ic_select),
62 | )
63 | }
64 | }
65 | Spacer(modifier = Modifier.height(6.dp))
66 | Text(text = item.name, modifier = Modifier, style = MaterialTheme.typography.bodyMedium)
67 | Spacer(modifier = Modifier.height(6.dp))
68 | Text(text = item.description, modifier = Modifier, maxLines = 3, minLines = 3, style = MaterialTheme.typography.bodySmall)
69 | }
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/user/MainUserViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.user
2 |
3 | import kotlinx.coroutines.flow.MutableStateFlow
4 | import org.succlz123.deepco.app.base.BaseBizViewModel
5 | import org.succlz123.deepco.app.base.JSON_USER
6 | import org.succlz123.deepco.app.base.LocalStorage
7 | import org.succlz123.deepco.app.chat.user.ChatUser
8 | import org.succlz123.deepco.app.chat.user.DEFAULT_CHAT_USER
9 |
10 | class MainUserViewModel : BaseBizViewModel() {
11 |
12 | val userLocalStorage = LocalStorage(JSON_USER)
13 |
14 | val chatUsers = MutableStateFlow>(emptyList())
15 |
16 | init {
17 | val local = userLocalStorage.get>().orEmpty()
18 | if (local.find { it.isDefault } == null) {
19 | userLocalStorage.put(local.toMutableList().apply {
20 | add(DEFAULT_CHAT_USER)
21 | })
22 | chatUsers.value = listOf(DEFAULT_CHAT_USER)
23 | } else {
24 | chatUsers.value = local
25 | }
26 | if (chatUsers.value.all { !it.isSelected }) {
27 | chatUsers.value = chatUsers.value.toMutableList().apply {
28 | val f = this.firstOrNull()
29 | if (f != null) {
30 | remove(f)
31 | add(f.copy(isSelected = true))
32 | }
33 | }
34 | }
35 | }
36 |
37 | fun changeUser(user: ChatUser, avatar: String, name: String, description: String) {
38 | chatUsers.value = chatUsers.value.toMutableList().apply {
39 | replaceAll {
40 | if (it.id == user.id) {
41 | user.copy(avatar = avatar, name = name, description = description, updateTime = System.currentTimeMillis())
42 | } else {
43 | it
44 | }
45 | }
46 | }
47 | userLocalStorage.put(chatUsers.value)
48 | }
49 |
50 | fun changeDefault(user: ChatUser) {
51 | chatUsers.value = chatUsers.value.map {
52 | it.copy(isSelected = user.id == it.id)
53 | }
54 | userLocalStorage.put(chatUsers.value)
55 | }
56 |
57 | fun set(changeMode: Boolean, id: Long, avatar: String, name: String, description: String) {
58 | chatUsers.value = chatUsers.value.toMutableList().apply {
59 | if (changeMode) {
60 | replaceAll {
61 | if (it.id == id) {
62 | it.copy(avatar = avatar, name = name, description = description, updateTime = System.currentTimeMillis())
63 | } else {
64 | it
65 | }
66 | }
67 | } else {
68 | if (this.find { it.name == name } == null) {
69 | add(ChatUser(id, avatar, name, description))
70 | }
71 | }
72 | }
73 | userLocalStorage.put(chatUsers.value)
74 | }
75 |
76 | fun remove(user: ChatUser) {
77 | chatUsers.value = chatUsers.value.toMutableList().apply {
78 | removeIf {
79 | it.id == user.id
80 | }
81 | }
82 | userLocalStorage.put(chatUsers.value)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/ui/user/UserAvatarView.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.ui.user
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.foundation.shape.RoundedCornerShape
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.draw.clip
13 | import androidx.compose.ui.graphics.ColorFilter
14 | import androidx.compose.ui.unit.Dp
15 | import deep_co.shared.generated.resources.Res
16 | import deep_co.shared.generated.resources.ic_my
17 | import org.jetbrains.compose.resources.painterResource
18 | import org.succlz123.lib.image.AsyncImageUrlMultiPlatform
19 |
20 | @Composable
21 | fun UserAvatarView(modifier: Modifier = Modifier, size: Dp, avatar: String?) {
22 | Box(
23 | modifier = modifier.size(size).clip(RoundedCornerShape(size)).then(
24 | if (avatar.isNullOrEmpty()) {
25 | Modifier.background(MaterialTheme.colorScheme.primary)
26 | } else {
27 | Modifier
28 | }
29 | ), contentAlignment = Alignment.Center
30 | ) {
31 | if (avatar.isNullOrEmpty()) {
32 | Image(
33 | modifier = Modifier.size(size / 2),
34 | contentDescription = null,
35 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimary),
36 | painter = painterResource(resource = Res.drawable.ic_my),
37 | )
38 | } else {
39 | AsyncImageUrlMultiPlatform(modifier = Modifier.size(size), avatar)
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/util/LayoutModifiers.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.util
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.layout.layout
5 |
6 | fun Modifier.withoutWidthConstraints() = layout { measurable, constraints ->
7 | val placeable = measurable.measure(constraints.copy(maxWidth = Int.MAX_VALUE))
8 | layout(constraints.maxWidth, placeable.height) {
9 | placeable.place(0, 0)
10 | }
11 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/util/Loadable.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.util
2 |
3 | import androidx.compose.runtime.*
4 | import kotlinx.coroutines.CancellationException
5 | import kotlinx.coroutines.CoroutineScope
6 |
7 | @Composable
8 | fun loadable(load: () -> T): MutableState {
9 | return loadableScoped() { load() }
10 | }
11 |
12 | private val loadingKey = Any()
13 |
14 | @Composable
15 | fun loadableScoped(load: CoroutineScope.() -> T?): MutableState {
16 | val state: MutableState = remember { mutableStateOf(null) }
17 | LaunchedEffect(loadingKey) {
18 | try {
19 | state.value = load()
20 | } catch (e: CancellationException) {
21 | // ignore
22 | } catch (e: Exception) {
23 | e.printStackTrace()
24 | }
25 | }
26 | return state
27 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/deepco/app/util/VerticalSplittable.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.util
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.gestures.Orientation
5 | import androidx.compose.foundation.gestures.draggable
6 | import androidx.compose.foundation.gestures.rememberDraggableState
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.fillMaxHeight
9 | import androidx.compose.foundation.layout.width
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.runtime.setValue
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.layout.Layout
18 | import androidx.compose.ui.platform.LocalDensity
19 | import androidx.compose.ui.unit.Constraints
20 | import androidx.compose.ui.unit.Dp
21 | import androidx.compose.ui.unit.dp
22 |
23 | @Composable
24 | fun VerticalSplittable(
25 | modifier: Modifier,
26 | splitterState: SplitterState,
27 | onResize: (delta: Dp) -> Unit,
28 | children: @Composable () -> Unit
29 | ) = Layout({
30 | children()
31 | VerticalSplitter(splitterState, onResize)
32 | }, modifier, measurePolicy = { measurables, constraints ->
33 | require(measurables.size == 3)
34 | val firstPlaceable = measurables[0].measure(constraints.copy(minWidth = 0))
35 | val secondWidth = constraints.maxWidth - firstPlaceable.width
36 | val secondPlaceable = measurables[1].measure(
37 | Constraints(
38 | minWidth = secondWidth,
39 | maxWidth = secondWidth,
40 | minHeight = constraints.maxHeight,
41 | maxHeight = constraints.maxHeight
42 | )
43 | )
44 | val splitterPlaceable = measurables[2].measure(constraints)
45 | layout(constraints.maxWidth, constraints.maxHeight) {
46 | firstPlaceable.place(0, 0)
47 | secondPlaceable.place(firstPlaceable.width, 0)
48 | splitterPlaceable.place(firstPlaceable.width, 0)
49 | }
50 | })
51 |
52 | class SplitterState {
53 | var isResizing by mutableStateOf(false)
54 | var isResizeEnabled by mutableStateOf(true)
55 | }
56 |
57 | @Composable
58 | fun VerticalSplitter(
59 | splitterState: SplitterState,
60 | onResize: (delta: Dp) -> Unit,
61 | color: Color = MaterialTheme.colorScheme.surfaceContainer
62 | ) = Box {
63 | val density = LocalDensity.current
64 | Box(
65 | Modifier
66 | .width(8.dp)
67 | .fillMaxHeight()
68 | .run {
69 | if (splitterState.isResizeEnabled) {
70 | this.draggable(
71 | state = rememberDraggableState {
72 | with(density) {
73 | onResize(it.toDp())
74 | }
75 | },
76 | orientation = Orientation.Horizontal,
77 | startDragImmediately = true,
78 | onDragStarted = { splitterState.isResizing = true },
79 | onDragStopped = { splitterState.isResizing = false }
80 | )
81 | } else {
82 | this
83 | }
84 | }
85 | )
86 |
87 | Box(
88 | Modifier
89 | .width(1.dp)
90 | .fillMaxHeight()
91 | .background(color)
92 | )
93 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/click/ClickableKtx.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.click
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.gestures.detectTapGestures
5 | import androidx.compose.foundation.interaction.HoverInteraction
6 | import androidx.compose.foundation.interaction.Interaction
7 | import androidx.compose.foundation.interaction.MutableInteractionSource
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.composed
11 | import androidx.compose.ui.input.pointer.pointerInput
12 | import androidx.compose.ui.platform.LocalClipboardManager
13 | import androidx.compose.ui.text.AnnotatedString
14 | import kotlinx.coroutines.channels.BufferOverflow
15 | import kotlinx.coroutines.flow.MutableSharedFlow
16 | import org.succlz123.deepco.app.i18n.strings
17 | import org.succlz123.lib.common.openURLByBrowser
18 | import org.succlz123.lib.screen.LocalScreenNavigator
19 |
20 | fun Modifier.noRippleClick(onClick: () -> Unit): Modifier = composed {
21 | clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) {
22 | onClick()
23 | }
24 | }
25 |
26 | fun Modifier.noDoubleClick(onClick: () -> Unit): Modifier = composed {
27 | pointerInput(Unit) {
28 | detectTapGestures(onDoubleTap = {
29 | onClick.invoke()
30 | })
31 | }
32 | }
33 |
34 | fun Modifier.noDoubleClickAndCopyStr(str: String, showToast: Boolean = false): Modifier = composed {
35 | val clipboardManager = LocalClipboardManager.current
36 | val screenNavigator = LocalScreenNavigator.current
37 | val strings = strings()
38 | pointerInput(Unit) {
39 | detectTapGestures(onDoubleTap = {
40 | clipboardManager.setText(AnnotatedString(str))
41 | if (showToast) {
42 | screenNavigator.toast(strings.copied)
43 | }
44 | })
45 | }
46 | }
47 |
48 | fun Modifier.onClickAndCopyStr(str: String, showToast: Boolean = false): Modifier = composed {
49 | val clipboardManager = LocalClipboardManager.current
50 | val screenNavigator = LocalScreenNavigator.current
51 | val strings = strings()
52 | pointerInput(Unit) {
53 | detectTapGestures(onTap = {
54 | clipboardManager.setText(AnnotatedString(str))
55 | if (showToast) {
56 | screenNavigator.toast(strings.copied)
57 | }
58 | })
59 | }
60 | }
61 |
62 | fun Modifier.onClickAndSpeakStr(str: String): Modifier = composed {
63 | val screenNavigator = LocalScreenNavigator.current
64 | val strings = strings()
65 | pointerInput(Unit) {
66 | detectTapGestures(onTap = {
67 | screenNavigator.toast(strings.speaking)
68 | org.succlz123.deepco.app.tts.speak(str)
69 | })
70 | }
71 | }
72 |
73 | fun Modifier.onClickUrl(url: String): Modifier = composed {
74 | pointerInput(Unit) {
75 | detectTapGestures(onTap = {
76 | openURLByBrowser(url)
77 | })
78 | }
79 | }
80 |
81 | fun Modifier.soundClick(onClick: () -> Unit): Modifier = composed {
82 | val soundInteraction = remember {
83 | // val mediaPlayer = CallbackMediaPlayerComponent().mediaPlayer()
84 | // mediaPlayer.media()?.prepare("")
85 | val interaction = object : MutableInteractionSource {
86 |
87 | override val interactions = MutableSharedFlow(
88 | extraBufferCapacity = 16,
89 | onBufferOverflow = BufferOverflow.DROP_OLDEST,
90 | )
91 |
92 | override suspend fun emit(interaction: Interaction) {
93 | interactions.emit(interaction)
94 | if (interaction is HoverInteraction.Enter) {
95 | // mediaPlayer.controls().play()
96 | }
97 | }
98 |
99 | override fun tryEmit(interaction: Interaction): Boolean {
100 | return interactions.tryEmit(interaction)
101 | }
102 | }
103 | interaction
104 | }
105 | clickable(indication = null, interactionSource = soundInteraction) {
106 | onClick()
107 | }
108 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/common/Url.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.common
2 |
3 |
4 | expect fun openURLByBrowser(url:String)
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/common/platform.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.common
2 |
3 | expect fun getPlatformName(): String
4 |
5 | expect fun isAndroid(): Boolean
6 |
7 | expect fun isDesktop(): Boolean
8 |
9 | expect fun isWindows(): Boolean
10 |
11 | expect fun isMac(): Boolean
12 |
13 | expect fun isLinux(): Boolean
14 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/file/File.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.file
2 |
3 | expect val HomeFolder: File
4 |
5 | interface File {
6 | val name: String
7 | val absolutePath: String
8 | val isDirectory: Boolean
9 | val children: List
10 | val hasChildren: Boolean
11 | }
12 |
13 | expect fun choseFile(suffix: List): String?
14 |
15 | expect fun choseImgFile(): String?
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/filedownloader/core/DownloadRequest.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.filedownloader.core;
2 |
3 | import java.security.MessageDigest
4 | import java.security.NoSuchAlgorithmException
5 |
6 | class DownloadRequest(
7 | val id: String? = null,
8 | val title: String? = null,
9 | val image: String? = null,
10 | val url: String? = null,
11 | val tag: String? = null
12 | ) {
13 |
14 | var createTime: Long = 0
15 |
16 | var downloadFilePath: String? = null
17 |
18 | var m3u8FilePath: String? = null
19 |
20 | fun key(): String {
21 | return hashKey(id.orEmpty())
22 | }
23 | }
24 |
25 | internal fun hashKey(key: String): String {
26 | val cacheKey: String = try {
27 | val mDigest = MessageDigest.getInstance("MD5")
28 | mDigest.update(key.toByteArray())
29 | bytesToHexString(mDigest.digest())
30 | } catch (e: NoSuchAlgorithmException) {
31 | key.hashCode().toString()
32 | }
33 | return cacheKey
34 | }
35 |
36 | internal fun bytesToHexString(bytes: ByteArray): String {
37 | val sb = StringBuilder()
38 | for (i in bytes.indices) {
39 | val hex = Integer.toHexString(0xFF and bytes[i].toInt())
40 | if (hex.length == 1) {
41 | sb.append('0')
42 | }
43 | sb.append(hex)
44 | }
45 | return sb.toString()
46 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/filedownloader/core/DownloadState.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.filedownloader.core
2 |
3 | import kotlinx.coroutines.Job
4 | import kotlinx.coroutines.flow.MutableStateFlow
5 | import java.io.File
6 |
7 | class DownloadState(
8 | var request: DownloadRequest,
9 | var downloadStateType: MutableStateFlow = MutableStateFlow(DownloadStateType.Uninitialized)
10 | ) {
11 |
12 | var progress: MutableStateFlow = MutableStateFlow(0)
13 |
14 | var downloadJob: Job? = null
15 |
16 | var contentLength: Int = 0
17 |
18 | var contentType: String = ""
19 |
20 | fun getRecordFile(cacheDir: File): File? {
21 | if (request.key().isEmpty()) {
22 | return null
23 | }
24 | return File(cacheDir, "${request.key()}.record")
25 | }
26 |
27 | fun getTsFile(cacheDir: File, index: Int): File? {
28 | if (request.key().isEmpty()) {
29 | return null
30 | }
31 | return File(cacheDir, "${request.key()}_${index}.ts")
32 | }
33 |
34 | fun getM3U8ListFile(cacheDir: File): File? {
35 | if (request.key().isEmpty()) {
36 | return null
37 | }
38 | return File(cacheDir, "${request.key()}.m3u8")
39 | }
40 |
41 | fun getDownloadFile(cacheDir: File): File? {
42 | if (request.key().isEmpty()) {
43 | return null
44 | }
45 | return File(cacheDir, "${request.key()}.download")
46 | }
47 | }
48 |
49 | sealed class DownloadStateType {
50 |
51 | object Uninitialized : DownloadStateType()
52 |
53 | object Starting : DownloadStateType()
54 |
55 | object Downloading : DownloadStateType()
56 |
57 | object Finish : DownloadStateType()
58 |
59 | object Pause : DownloadStateType()
60 |
61 | data class Error(val errorMsg: String) : DownloadStateType()
62 | }
63 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/filedownloader/core/utils/FileDownloadLoaderLogger.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.filedownloader.core.utils
2 |
3 | object FileDownloadLoaderLogger {
4 | private const val TAG = "FileDownloadLoaderLogger"
5 |
6 | var isDebug: Boolean = true
7 |
8 | fun debugLog(msg: String) {
9 | if (isDebug) {
10 | println("$TAG: $msg")
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/focus/FocusKtx.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.focus
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.ExperimentalComposeUiApi
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.composed
7 | import androidx.compose.ui.focus.*
8 | import androidx.compose.ui.input.key.*
9 | import androidx.compose.ui.platform.LocalFocusManager
10 | import androidx.compose.ui.platform.debugInspectorInfo
11 | import org.succlz123.deepco.app.base.GlobalFocusViewModel
12 | import org.succlz123.lib.screen.LocalScreenNavigator
13 | import org.succlz123.lib.screen.LocalScreenRecord
14 | import org.succlz123.lib.screen.lifecycle.ScreenLifecycle
15 | import org.succlz123.lib.screen.viewmodel.viewModel
16 |
17 | val allowMove = { true }
18 |
19 | val notAllowMove = { false }
20 |
21 | var lastNotify = 0L
22 |
23 | @OptIn(ExperimentalComposeUiApi::class)
24 | @Composable
25 | fun Modifier.onFocusKeyEventMove(
26 | leftCanMove: () -> Boolean = allowMove,
27 | upCanMove: () -> Boolean = allowMove,
28 | rightCanMove: () -> Boolean = allowMove,
29 | downCanMove: () -> Boolean = allowMove,
30 | backMove: () -> Boolean = { false },
31 | ): Modifier = composed(inspectorInfo = debugInspectorInfo {
32 | name = "onFocusKeyEventMove"
33 | properties["leftCanMove"] = leftCanMove
34 | properties["UpCanMove"] = upCanMove
35 | properties["rightCanMove"] = rightCanMove
36 | properties["downCanMove"] = downCanMove
37 | }) {
38 | val focusManager = LocalFocusManager.current
39 | val screen = LocalScreenRecord.current
40 | val screenNavigator = LocalScreenNavigator.current
41 | Modifier.onKeyEvent { keyEvent ->
42 | if (keyEvent.type == KeyEventType.KeyDown) {
43 | if (System.currentTimeMillis() - lastNotify < 160L) {
44 | return@onKeyEvent true
45 | }
46 | lastNotify = System.currentTimeMillis()
47 | when (keyEvent.key) {
48 | Key.A, Key.DirectionLeft -> {
49 | if (leftCanMove.invoke()) {
50 | val result = focusManager.moveFocus(FocusDirection.Left)
51 | println("onFocusKeyEventMove Left - $result")
52 | }
53 | true
54 | }
55 |
56 | Key.W, Key.DirectionUp -> {
57 | if (upCanMove.invoke()) {
58 | val result = focusManager.moveFocus(FocusDirection.Up)
59 | println("onFocusKeyEventMove Up - $result")
60 | }
61 | true
62 | }
63 |
64 | Key.D, Key.DirectionRight -> {
65 | if (rightCanMove.invoke()) {
66 | val result = focusManager.moveFocus(FocusDirection.Right)
67 | println("onFocusKeyEventMove Right - $result")
68 | }
69 | true
70 | }
71 |
72 | Key.S, Key.DirectionDown -> {
73 | if (downCanMove.invoke()) {
74 | val result = focusManager.moveFocus(FocusDirection.Down)
75 | println("onFocusKeyEventMove Down - $result")
76 | }
77 | true
78 | }
79 |
80 | Key.Back, Key.Escape -> {
81 | if (screen.hostLifecycle.getCurrentState() == ScreenLifecycle.State.RESUMED) {
82 | val result = backMove.invoke()
83 | if (!result) {
84 | screenNavigator.pop()
85 | }
86 | result
87 | } else {
88 | false
89 | }
90 | }
91 |
92 | else -> {
93 | false
94 | }
95 | }
96 | } else {
97 | true
98 | }
99 | }
100 | }
101 |
102 | @Composable
103 | fun Modifier.onFocusParent(
104 | thisRequester: FocusRequester, tag: String = "", focusCb: ((FocusState) -> Unit)? = null
105 | ): Modifier = composed(inspectorInfo = debugInspectorInfo {
106 | name = "onFocusParent"
107 | properties["thisRequester"] = thisRequester
108 | }) {
109 | val focusVm = viewModel(GlobalFocusViewModel::class) {
110 | GlobalFocusViewModel()
111 | }
112 | Modifier.focusRequester(thisRequester).onFocusChanged {
113 | if (it.hasFocus) {
114 | focusVm.curFocusRequesterParent.value = thisRequester
115 | println("onFocusParent: $thisRequester has $it $tag")
116 | }
117 | focusCb?.invoke(it)
118 | }.focusTarget()
119 | }
120 |
121 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/focus/FocusNode.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.focus
2 |
3 | data class FocusNode(val index: Int = 0, val tag: String = "") {}
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/fps/FpsMonitor.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.fps
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.Text
5 | import androidx.compose.runtime.*
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.text.font.FontWeight
9 | import org.succlz123.lib.common.getPlatformName
10 |
11 | @Composable
12 | fun FpsMonitor(modifier: Modifier) {
13 | var fpsCount by remember { mutableStateOf(0) }
14 | var fps by remember { mutableStateOf(0) }
15 | var lastUpdate by remember { mutableStateOf(0L) }
16 | val platformName = remember {
17 | getPlatformName()
18 | }
19 | Text(
20 | text = "$platformName\nFPS: $fps",
21 | modifier = modifier,
22 | color = Color.Green,
23 | fontWeight = FontWeight.Bold,
24 | style = MaterialTheme.typography.bodyMedium
25 | )
26 | LaunchedEffect(Unit) {
27 | while (true) {
28 | withFrameMillis { ms ->
29 | fpsCount++
30 | if (fpsCount == 5) {
31 | fps = (5000 / (ms - lastUpdate)).toInt()
32 | lastUpdate = ms
33 | fpsCount = 0
34 | }
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/image/AsyncImage.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.image
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.layout.ContentScale
6 |
7 |
8 | @Composable
9 | expect fun AsyncImageUrlMultiPlatform(modifier: Modifier, url: String, contentScale: ContentScale = ContentScale.Crop)
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/init/Init.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.init
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | expect fun initComposeMultiplatform()
7 |
8 | expect fun destructionComposeMultiplatform()
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/list/LazyListKtx.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.list
2 |
3 | import androidx.compose.foundation.lazy.LazyListState
4 | import androidx.compose.foundation.lazy.grid.LazyGridState
5 |
6 | fun LazyListState.isLastItemVisible(): Boolean {
7 | return layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1
8 | }
9 |
10 | fun LazyGridState.isLastItemVisible(): Boolean {
11 | return layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1
12 | }
13 |
14 | fun LazyListState.isFirstItemVisible(): Boolean {
15 | return firstVisibleItemIndex == 0
16 | }
17 |
18 | fun LazyGridState.isFirstItemVisible(): Boolean {
19 | return firstVisibleItemIndex == 0
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/logger/Logger.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.logger
2 |
3 | import java.time.Instant
4 | import java.time.LocalDateTime
5 | import java.time.ZoneId
6 | import java.time.format.DateTimeFormatter
7 |
8 |
9 | object Logger {
10 | var DEBUG = true
11 |
12 | fun log(msg: String) {
13 | if (DEBUG) {
14 | val timestamp = System.currentTimeMillis()
15 | val localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault())
16 | val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")
17 | val formattedDateTime = localDateTime.format(formatter)
18 | println("$formattedDateTime $msg")
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/modifier/Shadow.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.modifier
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.graphics.Color
5 | import androidx.compose.ui.unit.Dp
6 | import androidx.compose.ui.unit.dp
7 |
8 | expect fun Modifier.shadow(
9 | color: Color = Color(0x47000000),
10 | borderRadius: Dp = 4.dp,
11 | blurRadius: Dp = 12.dp,
12 | offsetY: Dp = 6.dp,
13 | offsetX: Dp = 1.dp,
14 | spread: Dp = 0.dp,
15 | modifier: Modifier = Modifier
16 | ): Modifier
17 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/network/HttpKtx.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.network
2 |
3 | import java.io.ByteArrayOutputStream
4 | import java.io.IOException
5 | import java.io.InputStream
6 | import java.net.HttpURLConnection
7 | import java.net.URL
8 |
9 | @Throws(IOException::class)
10 | fun URL.openConnection(): HttpURLConnection {
11 | val connection = this.openConnection() as HttpURLConnection
12 | connection.connectTimeout = 10000
13 | connection.readTimeout = 10000
14 | return connection
15 | }
16 |
17 | @Throws(IOException::class)
18 | @JvmOverloads
19 | fun String.openConnection(
20 | ua: String?,
21 | connectTimeout: Int = 6000,
22 | readTimeout: Int = 6000
23 | ): HttpURLConnection {
24 | val connection = URL(this).openConnection() as HttpURLConnection
25 | if (ua != null) {
26 | connection.setRequestProperty("User-Agent", ua)
27 | }
28 | connection.connectTimeout = connectTimeout
29 | connection.readTimeout = readTimeout
30 | return connection
31 | }
32 |
33 | object HttpX {
34 |
35 | fun doGet(urlStr: String?): String? {
36 | var url: URL?
37 | var conn: HttpURLConnection? = null
38 | var inputStream: InputStream? = null
39 | var baos: ByteArrayOutputStream? = null
40 | try {
41 | url = URL(urlStr)
42 | conn = url.openConnection() as HttpURLConnection
43 | conn.requestMethod = "GET"
44 | conn.setRequestProperty("accept", "*/*")
45 | conn.setRequestProperty("connection", "Keep-Alive")
46 | conn.setRequestProperty(
47 | "user-agent",
48 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36"
49 | )
50 | return if (conn.responseCode == 200) {
51 | inputStream = conn.inputStream
52 | baos = ByteArrayOutputStream()
53 | var len = -1
54 | val buf = ByteArray(1024 * 8)
55 | while (inputStream.read(buf).also { len = it } != -1) {
56 | baos.write(buf, 0, len)
57 | }
58 | baos.flush()
59 | baos.toString("UTF-8")
60 | } else {
61 | throw RuntimeException(" responseCode is not 200 ... ")
62 | }
63 | } catch (e: Exception) {
64 | e.printStackTrace()
65 | } finally {
66 | try {
67 | inputStream?.close()
68 | } catch (ignored: IOException) {
69 | }
70 | try {
71 | baos?.close()
72 | } catch (ignored: IOException) {
73 | }
74 | conn?.disconnect()
75 | }
76 | return null
77 | }
78 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/result/ScreenResultExtension.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.result
2 |
3 | import org.succlz123.lib.screen.result.ScreenResult
4 |
5 | fun screenResultDataNone(msg: String? = null) = ScreenResult.Fail(NullPointerException(msg))
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/scroll/ScrollContext.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.scroll
2 |
3 | import androidx.compose.foundation.lazy.LazyListState
4 | import androidx.compose.foundation.lazy.grid.LazyGridState
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.derivedStateOf
7 | import androidx.compose.runtime.getValue
8 | import androidx.compose.runtime.remember
9 | import org.succlz123.lib.list.isFirstItemVisible
10 | import org.succlz123.lib.list.isLastItemVisible
11 |
12 | data class ScrollContext(
13 | val isTop: Boolean,
14 | val isBottom: Boolean,
15 | )
16 |
17 | @Composable
18 | fun rememberListScrollContext(listState: LazyListState): ScrollContext {
19 | val scrollContext by remember {
20 | derivedStateOf {
21 | ScrollContext(
22 | isTop = listState.isFirstItemVisible(),
23 | isBottom = listState.isLastItemVisible()
24 | )
25 | }
26 | }
27 | return scrollContext
28 | }
29 |
30 | @Composable
31 | fun rememberGridScrollContext(listState: LazyGridState): ScrollContext {
32 | val scrollContext by remember {
33 | derivedStateOf {
34 | ScrollContext(
35 | isTop = listState.isFirstItemVisible(),
36 | isBottom = listState.isLastItemVisible()
37 | )
38 | }
39 | }
40 | return scrollContext
41 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/setting/Settings.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.setting
2 |
3 | expect fun getAppDirPath(dirName: Array): String
4 |
5 | expect fun openConfigDir(dirName: String)
6 |
7 | expect fun saveConfig2AppDir(dirName: String, fileName: String, content: String)
8 |
9 | expect fun getConfigFromAppDir(dirName: String, fileName: String): String?
10 |
11 | expect fun getAllConfigFromAppDir(dirName: String): List?
12 |
13 | expect fun removeFile(filePath: String)
14 |
15 | expect fun copyFile2ConfigDir(filePath: String, dirName: String, destName: String): String
16 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/thread/TaskManager.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.thread
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Deferred
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.asCoroutineDispatcher
7 | import kotlinx.coroutines.async
8 | import kotlinx.coroutines.cancel
9 | import java.util.concurrent.ConcurrentHashMap
10 | import java.util.concurrent.Executors
11 | import kotlin.coroutines.cancellation.CancellationException
12 |
13 | class TaskManager(private val threadCount: Int = 3) {
14 | private val scope = CoroutineScope(Dispatchers.Default)
15 | private val executor = Executors.newFixedThreadPool(threadCount)
16 | private val taskMap = ConcurrentHashMap>()
17 |
18 | suspend fun execute(key: String, task: suspend () -> T): T {
19 | // 检查是否有相同 key 的任务正在执行
20 | val existingTask = taskMap[key]
21 | if (existingTask != null) {
22 | println("已经有任务在执行")
23 | return try {
24 | existingTask.await()
25 | } catch (e: CancellationException) {
26 | throw e
27 | } finally {
28 | println("上一个相同任务结束 - 移除")
29 | taskMap.remove(key)
30 | }
31 | }
32 |
33 | // 创建新任务
34 | val deferred = scope.async(executor.asCoroutineDispatcher()) {
35 | try {
36 | println("Task start: ")
37 | task()
38 | } finally {
39 | println("任务结束 - 移除")
40 | taskMap.remove(key)
41 | }
42 | }
43 |
44 | taskMap[key] = deferred
45 |
46 | return try {
47 | deferred.await()
48 | } catch (e: CancellationException) {
49 | println("cancel task")
50 | deferred.cancel()
51 | throw e
52 | } finally {
53 | taskMap.remove(key)
54 | }
55 | }
56 |
57 | fun cancelTask(key: String) {
58 | taskMap[key]?.cancel()
59 | }
60 |
61 | fun cancelAll() {
62 | taskMap.forEach { (_, deferred) -> deferred.cancel() }
63 | }
64 |
65 | fun shutdown() {
66 | cancelAll()
67 | executor.shutdown()
68 | scope.cancel()
69 | }
70 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/time/Time.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.time
2 |
3 | import java.text.SimpleDateFormat
4 | import java.util.Date
5 | import java.util.Locale
6 | import java.util.TimeZone
7 |
8 | fun Long.hhMMssSSS(withN: Boolean = false): String {
9 | if (this <= 0) {
10 | return ""
11 | }
12 | val date = Date(this)
13 | val formatter = SimpleDateFormat(
14 | "yyyy-MM-dd${
15 | if (withN) {
16 | "\n"
17 | } else {
18 | " "
19 | }
20 | }HH:mm:ss", Locale.getDefault()
21 | )
22 | formatter.timeZone = TimeZone.getTimeZone("UTC")
23 | return formatter.format(date)
24 | }
25 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/vm/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.vm
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.flow.MutableStateFlow
5 | import kotlinx.coroutines.launch
6 | import org.succlz123.lib.screen.result.ScreenResult
7 | import org.succlz123.lib.screen.viewmodel.ScreenViewModel
8 |
9 | open class BaseViewModel : ScreenViewModel() {
10 |
11 | fun fetch(
12 | result: MutableStateFlow>,
13 | isForce: Boolean = false,
14 | isRefresh: Boolean = false,
15 | content: suspend () -> T?
16 | ) {
17 | if (result.value is ScreenResult.Loading) {
18 | return
19 | }
20 | if (!isForce && result.value is ScreenResult.Success) {
21 | return
22 | }
23 | if (result.value is ScreenResult.Success) {
24 | if (isRefresh) {
25 | result.value = ScreenResult.Loading()
26 | } else {
27 | result.value = ScreenResult.Loading(result.value.invoke())
28 | }
29 | } else {
30 | result.value = ScreenResult.Loading()
31 | }
32 | viewModelScope.launch(Dispatchers.IO) {
33 | try {
34 | val response = content()
35 | if (response == null) {
36 | result.value = ScreenResult.Fail(NullPointerException())
37 | } else {
38 | if (response is ArrayList<*>) {
39 | if (response.isEmpty()) {
40 | result.value = ScreenResult.Fail(NullPointerException())
41 | } else {
42 | if (isRefresh) {
43 | result.value = ScreenResult.Success(response)
44 | } else {
45 | val before = result.value.invoke() as? ArrayList
46 | val cur = if (before != null) {
47 | before
48 | } else {
49 | ArrayList()
50 | }
51 | val responseList = response as ArrayList<*>
52 | cur.addAll(responseList)
53 | result.value = ScreenResult.Success(cur as T)
54 | }
55 | }
56 | } else {
57 | result.value = ScreenResult.Success(response)
58 | }
59 | }
60 | } catch (e: Exception) {
61 | result.value = ScreenResult.Fail(e)
62 | e.printStackTrace()
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/org/succlz123/lib/vm/ScreenPageViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.vm
2 |
3 | abstract class ScreenPageViewModel : BaseViewModel() {
4 | var page = 0
5 | var pageSize = 20
6 |
7 | var currentSize = 0
8 | var totalSize = 0
9 |
10 | var hasMore = true
11 |
12 | fun resetPageSize() {
13 | pageSize = 20
14 | page = 0
15 | hasMore = true
16 | }
17 |
18 | override fun clear() {
19 | page = 0
20 | hasMore = true
21 | onCleared()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/deepco/app/main/MainWindow.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.main
2 |
3 | import androidx.compose.runtime.Composable
4 | import org.succlz123.deepco.app.base.BaseWindow
5 | import org.succlz123.deepco.app.window.AppWindow
6 | import org.succlz123.lib.init.destructionComposeMultiplatform
7 |
8 | @Composable
9 | fun MainWindow(appWindow: AppWindow) {
10 | BaseWindow(appWindow, onCloseRequest = {
11 | destructionComposeMultiplatform()
12 | }) {
13 | MainActivity()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/deepco/app/mcp/MCPClient.desktop.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.mcp
2 |
3 | import org.succlz123.deepco.app.mcp.biz.Tool
4 | import org.succlz123.deepco.app.mcp.biz.ToolUse
5 |
6 | val cache = hashMapOf()
7 |
8 | actual suspend fun connectToServer(serverName: String, command: String?, args: List?): List {
9 | val mcp = cache[serverName] ?: MCPClient().also { cache[serverName] = it }
10 | return mcp.connectToServer(command, args)
11 | }
12 |
13 | actual suspend fun callServerTool(serverName: String, toolName: String, args: Map): ToolUse? {
14 | cache[serverName]?.let { mcp ->
15 | return mcp.callServerTool(toolName, args)
16 | } ?: return null
17 | }
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/deepco/app/tts/TTS.desktop.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.tts
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.GlobalScope
5 | import kotlinx.coroutines.launch
6 | import kotlinx.coroutines.withContext
7 |
8 | actual fun speak(inputText: String) {
9 | GlobalScope.launch {
10 | withContext(Dispatchers.IO) {
11 | TTSClient.speak(inputText)
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/deepco/app/tts/TTSClient.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.tts
2 |
3 | import io.ikfly.constant.VoiceEnum
4 | import io.ikfly.model.SSML
5 | import io.ikfly.service.TTSService
6 | import org.succlz123.lib.logger.Logger
7 | import java.io.File
8 |
9 | object TTSClient {
10 | // const val voiceName: String = "cmu-slt-hsmm"
11 |
12 | var isSpeak: Boolean = false
13 |
14 | fun getSavePath(): String {
15 | val file = File(System.getProperty("java.io.tmpdir") + "deepco/tts")
16 | if (!file.exists()) {
17 | file.mkdirs()
18 | }
19 | return file.absolutePath + File.separator
20 | }
21 |
22 | fun deleteTTSSavePathOnExit() {
23 | Logger.log("TTSClient: deleteTTSSavePathOnExit")
24 | File(getSavePath()).deleteRecursively()
25 | }
26 |
27 | fun speak(inputText: String) {
28 | if (inputText.isEmpty()) {
29 | return
30 | }
31 | if (isSpeak) {
32 | return
33 | }
34 | isSpeak = true
35 | Logger.log("TTSClient: Speak $inputText")
36 | try {
37 | val ts = TTSService()
38 | ts.baseSavePath = getSavePath()
39 | Logger.log("TTSClient: path ${ts.baseSavePath}")
40 | ts.sendText(
41 | SSML.builder()
42 | .synthesisText(inputText)
43 | .voice(VoiceEnum.zh_CN_XiaoxiaoNeural)
44 | .usePlayer(true)
45 | .build()
46 | )
47 | ts.close()
48 | } catch (e: Exception) {
49 | Logger.log("TTSClient: Speak " + e.message)
50 | }
51 | // var mary: LocalMaryInterface? = null
52 | // try {
53 | // mary = LocalMaryInterface()
54 | // } catch (e: MaryConfigurationException) {
55 | // Logger.log("TTSClient: Could not initialize MaryTTS interface " + e.message)
56 | // }
57 | // mary ?: return
58 | // // Set voice / language
59 | // mary.setVoice(voiceName)
60 | // // synthesize
61 | // var audio: AudioInputStream? = null
62 | // try {
63 | // audio = mary.generateAudio(inputText)
64 | // } catch (e: SynthesisException) {
65 | // Logger.log("TTSClient: Synthesis failed " + e.message)
66 | // }
67 | // if (audio != null) {
68 | // playAudio(audio)
69 | // }
70 | isSpeak = false
71 | }
72 |
73 | // fun playAudio(audio: AudioInputStream) {
74 | // try {
75 | // val clip = AudioSystem.getClip()
76 | // clip.open(audio)
77 | // clip.start()
78 | // while (!clip.isRunning) Thread.sleep(10)
79 | // while (clip.isRunning) Thread.sleep(10)
80 | // clip.close()
81 | // } catch (e: Exception) {
82 | // Logger.log("TTSClient: playAudio Failed " + e.message)
83 | // }
84 | // }
85 | }
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/deepco/app/window/AppApplicationState.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.window
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.mutableStateListOf
5 | import androidx.compose.runtime.remember
6 | import androidx.compose.runtime.staticCompositionLocalOf
7 | import androidx.compose.ui.unit.DpSize
8 | import androidx.compose.ui.unit.dp
9 | import androidx.compose.ui.window.Notification
10 | import androidx.compose.ui.window.TrayState
11 | import androidx.compose.ui.window.WindowPlacement
12 | import androidx.compose.ui.window.WindowPosition
13 |
14 |
15 | val LocalAppApplicationState = staticCompositionLocalOf {
16 | error("LocalAppApplicationState isn't provided")
17 | }
18 |
19 | @Composable
20 | fun rememberApplicationState(creator: @Composable (AppWindow) -> Unit) = remember {
21 | AppApplicationState().apply {
22 | newWindow(creator)
23 | }
24 | }
25 |
26 | class AppApplicationState {
27 | val tray = TrayState()
28 | val windows = mutableStateListOf()
29 |
30 | fun newWindow(
31 | creator: @Composable (AppWindow) -> Unit,
32 | placement: WindowPlacement = WindowPlacement.Floating,
33 | isMinimized: Boolean = false,
34 | position: WindowPosition = WindowPosition.PlatformDefault,
35 | size: DpSize = DpSize(1080.dp, 720.dp)
36 | ) {
37 | windows.add(
38 | AppWindow(
39 | application = this, creator = creator, exit = windows::remove,
40 | placement = placement, isMinimized = isMinimized, position = position, size = size
41 | )
42 | )
43 | }
44 |
45 | fun sendNotification(notification: Notification) {
46 | tray.sendNotification(notification)
47 | }
48 | }
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/deepco/app/window/AppWindow.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.window
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.unit.DpSize
8 | import androidx.compose.ui.window.WindowPlacement
9 | import androidx.compose.ui.window.WindowPosition
10 | import androidx.compose.ui.window.WindowState
11 |
12 | class AppWindow(
13 | val application: AppApplicationState,
14 | val creator: @Composable (AppWindow) -> Unit,
15 | val exit: (AppWindow) -> Unit,
16 | val placement: WindowPlacement,
17 | val isMinimized: Boolean,
18 | val position: WindowPosition,
19 | val size: DpSize
20 | ) {
21 | val windowState: WindowState = WindowState(placement = placement, isMinimized = isMinimized, position = position, size = size)
22 |
23 | var isInit by mutableStateOf(false)
24 | private set
25 |
26 | fun toggleFullscreen() {
27 | windowState.placement = if (windowState.placement == WindowPlacement.Fullscreen) {
28 | WindowPlacement.Floating
29 | } else {
30 | WindowPlacement.Fullscreen
31 | }
32 | }
33 |
34 | private fun initNew() {
35 | isInit = true
36 | }
37 | }
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/lib/common/Url.desktop.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.common
2 |
3 | import java.net.URI
4 |
5 | actual fun openURLByBrowser(url: String) {
6 | try {
7 | java.awt.Desktop.getDesktop().browse(URI(url))
8 | } catch (e: Exception) {
9 | e.printStackTrace()
10 | }
11 | }
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/lib/common/platform.desktop.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.common
2 |
3 | import java.util.Locale
4 |
5 | actual fun getPlatformName(): String {
6 | return "Desktop"
7 | }
8 |
9 | actual fun isAndroid(): Boolean {
10 | return false
11 | }
12 |
13 | actual fun isDesktop(): Boolean {
14 | return true
15 | }
16 |
17 | actual fun isWindows(): Boolean {
18 | val osName = System.getProperty("os.name", "generic")
19 | return osName.contains("indows")
20 | }
21 |
22 | actual fun isLinux(): Boolean {
23 | val osName = System.getProperty("os.name", "generic")
24 | if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) {
25 | return true
26 | }
27 | return false
28 | }
29 |
30 | actual fun isMac(): Boolean {
31 | val osName = System.getProperty("os.name", "generic")
32 | if (osName.lowercase(Locale.getDefault()).contains("mac")) {
33 | return true
34 | }
35 | return false
36 | }
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/lib/file/File.desktop.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.file
2 |
3 | import java.awt.FileDialog
4 |
5 | actual val HomeFolder: File
6 | get() = java.io.File(System.getProperty("user.home")).toProjectFile()
7 |
8 | actual fun choseFile(suffix: List): String? {
9 | val dialog = FileDialog(null as java.awt.Frame?, "Pic", FileDialog.LOAD)
10 | dialog.setFilenameFilter { dir, name ->
11 | suffix.find { name.endsWith(it) } != null
12 | }
13 | dialog.isVisible = true
14 | val file = dialog.file
15 | if (file != null) {
16 | return dialog.directory + file
17 | } else {
18 | return null
19 | }
20 | }
21 |
22 | actual fun choseImgFile(): String? {
23 | return choseFile(listOf(".jpg", ".avif", ".webp", ".png", ".gif", ".bmp"))
24 | }
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/lib/image/AsyncImage.desktop.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.image
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.layout.ContentScale
7 | import org.succlz123.lib.imageloader.ImageAsyncImageFile
8 | import org.succlz123.lib.imageloader.ImageAsyncImageUrl
9 | import org.succlz123.lib.imageloader.ImageRes
10 | import org.succlz123.lib.imageloader.core.ImageCallback
11 |
12 | @Composable
13 | actual fun AsyncImageUrlMultiPlatform(modifier: Modifier, url: String, contentScale: ContentScale) {
14 | if (url.startsWith("http")) {
15 | ImageAsyncImageUrl(url = url, imageCallback = ImageCallback {
16 | Image(
17 | painter = it, contentDescription = url, modifier = modifier, contentScale = contentScale
18 | )
19 | })
20 | } else if (url.startsWith("/")) {
21 | ImageAsyncImageFile(filePath = url, imageCallback = ImageCallback {
22 | Image(
23 | painter = it, contentDescription = url, modifier = modifier, contentScale = contentScale
24 | )
25 | })
26 | } else {
27 | ImageRes(resName = url, imageCallback = ImageCallback {
28 | Image(
29 | painter = it, contentDescription = url, modifier = modifier, contentScale = contentScale
30 | )
31 | })
32 | }
33 | }
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/lib/init/Init.desktop.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.init
2 |
3 | import androidx.compose.runtime.Composable
4 | import org.succlz123.deepco.app.AppBuildConfig
5 | import org.succlz123.deepco.app.tts.TTSClient.deleteTTSSavePathOnExit
6 | import org.succlz123.lib.common.isWindows
7 | import org.succlz123.lib.filedownloader.core.FileDownLoader
8 | import org.succlz123.lib.imageloader.core.ImageLoader
9 | import java.io.File
10 | import java.util.Locale
11 |
12 | actual fun destructionComposeMultiplatform() {
13 | deleteTTSSavePathOnExit()
14 | }
15 |
16 | @Composable
17 | actual fun initComposeMultiplatform() {
18 | if (isWindows()) {
19 | System.setProperty("skiko.renderApi", "OPENGL")
20 | }
21 | ImageLoader.configuration(
22 | rootDirectory = getCacheFolder(AppBuildConfig.APP),
23 | maxMemoryCacheSize = 150 * 1024 * 1024,
24 | maxDiskCacheSize = 300 * 1024 * 1024
25 | )
26 | FileDownLoader.configuration(rootDirectory = getCacheFolder(AppBuildConfig.APP))
27 | }
28 |
29 | fun getCacheFolder(dir: String): File {
30 | val osName = System.getProperty("os.name", "generic")
31 | // macOS
32 | if (osName.lowercase(Locale.getDefault()).contains("mac")) {
33 | return File(System.getProperty("user.home") + File.separator + "Library" + File.separator + "Caches" + File.separator + dir)
34 | }
35 |
36 | // Linux
37 | if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) {
38 | return File(System.getProperty("user.home") + File.separator + ".cache" + File.separator + dir)
39 | }
40 |
41 | // Windows
42 | return if (osName.contains("indows")) {
43 | File(System.getenv("AppData") + File.separator + dir)
44 | } else {
45 | // A reasonable fallback
46 | return File(System.getProperty("user.home") + File.separator + ".cache" + File.separator + dir)
47 | }
48 | }
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/lib/modifier/Shadow.desktop.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.modifier
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.draw.drawBehind
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.graphics.Paint
7 | import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
8 | import androidx.compose.ui.graphics.toArgb
9 | import androidx.compose.ui.unit.Dp
10 | import androidx.compose.ui.unit.dp
11 | import org.jetbrains.skia.FilterBlurMode
12 | import org.jetbrains.skia.MaskFilter
13 |
14 | actual fun Modifier.shadow(
15 | color: Color,
16 | borderRadius: Dp,
17 | blurRadius: Dp,
18 | offsetY: Dp,
19 | offsetX: Dp,
20 | spread: Dp,
21 | modifier: Modifier
22 | ) = this.then(
23 | modifier.drawBehind {
24 | this.drawIntoCanvas {
25 | val paint = Paint()
26 | val frameworkPaint = paint.asFrameworkPaint()
27 | val spreadPixel = spread.toPx()
28 | val leftPixel = (0f - spreadPixel) + offsetX.toPx()
29 | val topPixel = (0f - spreadPixel) + offsetY.toPx()
30 | val rightPixel = (this.size.width + spreadPixel)
31 | val bottomPixel = (this.size.height + spreadPixel)
32 |
33 | if (blurRadius != 0.dp) {
34 | frameworkPaint.maskFilter = (MaskFilter.makeBlur(FilterBlurMode.NORMAL, blurRadius.toPx()))
35 | }
36 |
37 | frameworkPaint.color = color.toArgb()
38 | it.drawRoundRect(
39 | left = leftPixel,
40 | top = topPixel,
41 | right = rightPixel,
42 | bottom = bottomPixel,
43 | radiusX = borderRadius.toPx(),
44 | radiusY = borderRadius.toPx(),
45 | paint
46 | )
47 | }
48 | }
49 | )
50 |
--------------------------------------------------------------------------------
/shared/src/desktopMain/kotlin/org/succlz123/lib/setting/Settings.desktop.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.setting
2 |
3 | import java.io.File
4 | import java.nio.file.Paths
5 | import java.util.Locale
6 |
7 | actual fun getAppDirPath(dirName: Array): String {
8 | val os = System.getProperty("os.name").lowercase(Locale.ROOT)
9 | return when {
10 | os.contains("win") -> Paths.get(
11 | System.getProperty("user.home"),
12 | "AppData",
13 | "Local",
14 | *dirName
15 | ).toString()
16 |
17 | else -> Paths.get(
18 | System.getProperty("user.home"),
19 | ".config",
20 | *dirName
21 | ).toString()
22 | }
23 | }
24 |
25 | actual fun saveConfig2AppDir(dirName: String, fileName: String, content: String) {
26 | val file = File(getAppDirPath(arrayOf(dirName, fileName)))
27 | if (file.parentFile?.exists() != true) {
28 | file.parentFile?.mkdirs()
29 | }
30 | if (!file.exists()) {
31 | file.createNewFile()
32 | }
33 | file.writeText(content)
34 | }
35 |
36 | actual fun getConfigFromAppDir(dirName: String, fileName: String): String? {
37 | val file = File(getAppDirPath(arrayOf(dirName, fileName)))
38 | if (file.exists()) {
39 | return file.readText()
40 | }
41 | return null
42 | }
43 |
44 | actual fun openConfigDir(dirName: String) {
45 | try {
46 | val desktop = java.awt.Desktop.getDesktop()
47 | val appDir = File(getAppDirPath(arrayOf(dirName)))
48 | if (appDir.exists()) {
49 | desktop.open(appDir)
50 | } else {
51 | appDir.mkdirs()
52 | desktop.open(appDir)
53 | }
54 | } catch (e: Exception) {
55 | e.printStackTrace()
56 | }
57 | }
58 |
59 | actual fun getAllConfigFromAppDir(dirName: String): List? {
60 | val file = File(getAppDirPath(arrayOf(dirName)))
61 | return if (file.isDirectory) {
62 | file.listFiles().map {
63 | it.readText()
64 | }
65 | } else {
66 | emptyList()
67 | }
68 | }
69 |
70 | actual fun removeFile(filePath: String) {
71 | val file = File(getAppDirPath(arrayOf(filePath)))
72 | if (file.exists()) {
73 | file.delete()
74 | }
75 | }
76 |
77 | actual fun copyFile2ConfigDir(filePath: String, dirName: String, destName: String): String {
78 | val extension = filePath.substringAfterLast('.', "")
79 | val file = File(getAppDirPath(arrayOf(dirName, "$destName.$extension")))
80 | File(filePath).copyTo(file, true)
81 | return file.absolutePath
82 | }
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/main.ios.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
4 | */
5 |
6 |
7 | import androidx.compose.ui.window.ComposeUIViewController
8 | import platform.UIKit.UIViewController
9 | import org.jetbrains.codeviewer.ui.MainView
10 |
11 | fun MainViewController() : UIViewController = ComposeUIViewController { MainView() }
12 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/org/jetbrains/codeviewer/platform/File.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("NewApi")
2 |
3 | package org.jetbrains.codeviewer.platform
4 |
5 | import codeviewer.shared.generated.resources.Res
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.runBlocking
8 | import org.jetbrains.codeviewer.util.EmptyTextLines
9 | import org.jetbrains.codeviewer.util.TextLines
10 | import org.jetbrains.compose.resources.ExperimentalResourceApi
11 |
12 | class VirtualFile(override val name: String, override val isDirectory: Boolean, val textLines: TextLines, override val children: List = listOf()): File {
13 | override val hasChildren: Boolean
14 | get() = children.size > 0
15 |
16 | override fun readLines(scope: CoroutineScope): TextLines = textLines
17 | }
18 |
19 | fun ByteArray.toTextLines(): TextLines = object : TextLines {
20 | val contents = decodeToString().split("\n")
21 |
22 | override val size: Int
23 | get() = contents.size
24 |
25 | override fun get(index: Int): String = contents[index]
26 | }
27 |
28 |
29 | @OptIn(ExperimentalResourceApi::class)
30 | actual val HomeFolder: File get() = VirtualFile("files",
31 | isDirectory = true,
32 | textLines = EmptyTextLines,
33 | children = listOf(
34 | VirtualFile("EditorView.kt",
35 | isDirectory = false,
36 | textLines = runBlocking {
37 | Res.readBytes("EditorView.kt")
38 | }.toTextLines()
39 | )
40 | )
41 | )
42 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/org/jetbrains/codeviewer/platform/Mouse.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.codeviewer.platform
2 |
3 | import androidx.compose.ui.Modifier
4 |
5 | actual fun Modifier.cursorForHorizontalResize(): Modifier = this
6 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/org/jetbrains/codeviewer/platform/Scrollbar.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.codeviewer.platform
2 |
3 | import androidx.compose.foundation.ScrollState
4 | import androidx.compose.foundation.lazy.LazyListState
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 |
8 | @Composable
9 | actual fun VerticalScrollbar(
10 | modifier: Modifier,
11 | scrollState: ScrollState
12 | ) = Unit
13 |
14 | @Composable
15 | actual fun VerticalScrollbar(
16 | modifier: Modifier,
17 | scrollState: LazyListState
18 | ) = Unit
--------------------------------------------------------------------------------
/shared/src/jvmMain/kotlin/org/succlz123/deepco/app/llm/gemini/Gemini.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.llm.gemini
2 |
3 | import com.google.genai.Client
4 | import com.google.genai.types.Content
5 | import com.google.genai.types.GenerateContentConfig
6 | import com.google.genai.types.GoogleSearch
7 | import com.google.genai.types.Part
8 | import com.google.genai.types.Tool
9 | import kotlinx.coroutines.Dispatchers
10 | import kotlinx.coroutines.withContext
11 | import org.succlz123.deepco.app.llm.ChatResponse
12 | import org.succlz123.deepco.app.chat.msg.ChatMessage
13 | import org.succlz123.deepco.app.ui.chat.ChatModeConfig
14 | import org.succlz123.lib.logger.Logger
15 | import kotlin.jvm.optionals.getOrNull
16 |
17 |
18 | // https://aistudio.google.com/app/plan_information
19 | val map = hashMapOf()
20 |
21 | private fun getClient(key: String): Client {
22 | var client = map[key]
23 | if (client == null) {
24 | client = Client.builder().apiKey(key).build()
25 | map[key] = client
26 | }
27 | return client
28 | }
29 |
30 | fun getConfig(prompt: String, modeConfig: ChatModeConfig): GenerateContentConfig {
31 | val systemInstruction = Content.fromParts(Part.fromText(prompt))
32 | val googleSearchTool = Tool.builder().googleSearch(GoogleSearch.builder().build()).build()
33 | return GenerateContentConfig.builder()
34 | .candidateCount(1)
35 | .maxOutputTokens(modeConfig.maxOutTokens)
36 | .systemInstruction(systemInstruction)
37 | .temperature(modeConfig.temperature)
38 | .topP(modeConfig.topP)
39 | .topK(modeConfig.topK)
40 | .presencePenalty(modeConfig.presencePenalty)
41 | .frequencyPenalty(modeConfig.frequencyPenalty)
42 | // .tools(ImmutableList.of(googleSearchTool))
43 | .build()
44 | }
45 |
46 | actual suspend fun geminiChat(key: String, model: String, prompt: String, modeConfig: ChatModeConfig, content: List): ChatResponse {
47 | return withContext(Dispatchers.Default) {
48 | val client = getClient(key)
49 | val config = getConfig(prompt, modeConfig)
50 | return@withContext withContext(Dispatchers.IO) {
51 | try {
52 | val response = client.models.generateContent(model, content.mapNotNull {
53 | if (it.isFromMe) {
54 | Content.fromParts(Part.fromText(it.content.value))
55 | } else if (!it.isLoading()) {
56 | Content.builder().role("model").parts(listOf(Part.fromText(it.content.value))).build()
57 | } else {
58 | null
59 | }
60 | }, config)
61 | val text = response.text()
62 | Logger.log("geminiChat " + text.orEmpty())
63 | ChatResponse(
64 | text = text,
65 | id = response.responseId().getOrNull(),
66 | created = response.createTime().getOrNull(),
67 | model = response.modelVersion().getOrNull()
68 | )
69 | } catch (e: Exception) {
70 | Logger.log("geminiChat e" + e.message)
71 | ChatResponse(errorMsg = e.message.orEmpty())
72 | }
73 | }
74 | }
75 | }
76 |
77 | actual suspend fun geminiChatStream(key: String, model: String, prompt: String, modeConfig: ChatModeConfig, content: List, cb: (ChatResponse, Boolean) -> Unit) {
78 | withContext(Dispatchers.Default) {
79 | val client = getClient(key)
80 | val config = getConfig(prompt, modeConfig)
81 | withContext(Dispatchers.IO) {
82 | try {
83 | val responseStream = client.models.generateContentStream(model, content.mapNotNull {
84 | if (it.isFromMe) {
85 | Content.fromParts(Part.fromText(it.content.value))
86 | } else if (!it.isLoading()) {
87 | Content.builder().role("model").parts(listOf(Part.fromText(it.content.value))).build()
88 | } else {
89 | null
90 | }
91 | }, config)
92 | for (response in responseStream) {
93 | val text = response.text()
94 | Logger.log("geminiChatStream " + text.orEmpty())
95 | cb.invoke(
96 | ChatResponse(
97 | text = text,
98 | id = response.responseId().getOrNull(),
99 | created = response.createTime().getOrNull(),
100 | model = response.modelVersion().getOrNull()
101 | ), false
102 | )
103 | }
104 | responseStream.close()
105 | cb.invoke(ChatResponse(), true)
106 | } catch (e: Exception) {
107 | Logger.log("geminiChatStream e" + e.message)
108 | cb.invoke(ChatResponse(errorMsg = e.message.orEmpty()), true)
109 | }
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/shared/src/jvmMain/kotlin/org/succlz123/deepco/app/mcp/MCPClient.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.deepco.app.mcp
2 |
3 | import com.anthropic.core.JsonValue
4 | import com.fasterxml.jackson.databind.ObjectMapper
5 | import io.modelcontextprotocol.kotlin.sdk.Implementation
6 | import io.modelcontextprotocol.kotlin.sdk.TextContent
7 | import io.modelcontextprotocol.kotlin.sdk.client.Client
8 | import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport
9 | import kotlinx.coroutines.runBlocking
10 | import kotlinx.io.asSink
11 | import kotlinx.io.asSource
12 | import kotlinx.io.buffered
13 | import kotlinx.serialization.json.JsonObject
14 | import org.succlz123.deepco.app.AppBuildConfig
15 | import org.succlz123.deepco.app.mcp.biz.InputSchema
16 | import org.succlz123.deepco.app.mcp.biz.Tool
17 | import org.succlz123.deepco.app.mcp.biz.ToolUse
18 | import org.succlz123.lib.logger.Logger
19 |
20 | class MCPClient : AutoCloseable {
21 |
22 | // Initialize MCP client
23 | private val mcp: Client = Client(clientInfo = Implementation(name = "mcp-client-cli", version = AppBuildConfig.versionName))
24 |
25 | private fun JsonObject.toJsonValue(): JsonValue {
26 | val mapper = ObjectMapper()
27 | val node = mapper.readTree(this.toString())
28 | return JsonValue.fromJsonNode(node)
29 | }
30 |
31 | suspend fun connectToServer(command: String?, args: List?): List {
32 | if (command.isNullOrEmpty() || args.isNullOrEmpty()) {
33 | return emptyList()
34 | }
35 | // Command not support
36 | if (command != "npx" && command != "node" && command != "uv" && command != "python" && command != "python3" && command != "java") {
37 | return emptyList()
38 | }
39 | try {
40 | // Build the command based on the file extension of the server script
41 | // val command = buildList {
42 | // when (serverScriptPath.substringAfterLast(".")) {
43 | // "js" -> add("node")
44 | // "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3")
45 | // "jar" -> addAll(listOf("java", "-jar"))
46 | // else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file")
47 | // }
48 | // add(serverScriptPath)
49 | // }
50 |
51 | Logger.log("Connecting to MCP server with command: $command and args: $args")
52 |
53 | // Start the server process
54 | val process = ProcessBuilder(*(buildList {
55 | add(command)
56 | addAll(args)
57 | }.toTypedArray())).start()
58 |
59 | // Setup I/O transport using the process streams
60 | val transport = StdioClientTransport(
61 | input = process.inputStream.asSource().buffered(),
62 | output = process.outputStream.asSink().buffered()
63 | )
64 |
65 | // Connect the MCP client to the server using the transport
66 | mcp.connect(transport)
67 |
68 | // Request the list of available tools from the server
69 | val toolsResult = mcp.listTools()
70 | val tools = toolsResult?.tools?.map { tool ->
71 | Tool(
72 | name = tool.name, description = tool.description ?: "",
73 | input_schema = InputSchema(
74 | type = tool.inputSchema.type,
75 | properties = tool.inputSchema.properties,
76 | required = tool.inputSchema.required
77 | )
78 | )
79 | } ?: emptyList()
80 | Logger.log("Connected to server with tools: ${tools.joinToString(", ") { it.name.orEmpty() }}")
81 | return tools
82 | } catch (e: Exception) {
83 | Logger.log("Failed to connect to MCP server: $e")
84 | return emptyList()
85 | }
86 | }
87 |
88 | suspend fun callServerTool(toolName: String, toolArgs: Map): ToolUse {
89 | val result = mcp.callTool(
90 | name = toolName,
91 | arguments = toolArgs
92 | )
93 | Logger.log("Call Tool: $result")
94 | return ToolUse(toolName, result?.content?.joinToString("\n") {
95 | (it as TextContent).text ?: ""
96 | })
97 | }
98 |
99 | override fun close() {
100 | runBlocking {
101 | mcp.close()
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/shared/src/jvmMain/kotlin/org/succlz123/lib/file/JvmFile.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.file
2 |
3 | import java.io.FileInputStream
4 | import java.io.FilenameFilter
5 | import java.io.IOException
6 | import java.nio.ByteBuffer
7 | import java.nio.channels.FileChannel
8 |
9 | fun java.io.File.toProjectFile(): File = object : File {
10 |
11 | override val name: String
12 | get() = this@toProjectFile.name
13 |
14 | override val absolutePath: String
15 | get() = this@toProjectFile.absolutePath
16 |
17 | override val isDirectory: Boolean
18 | get() = this@toProjectFile.isDirectory
19 |
20 | override val children: List
21 | get() = this@toProjectFile
22 | .listFiles(FilenameFilter { _, name -> !name.startsWith(".") })
23 | .orEmpty()
24 | .map { it.toProjectFile() }
25 |
26 | private val numberOfFiles
27 | get() = listFiles()?.size ?: 0
28 |
29 | override val hasChildren: Boolean
30 | get() = isDirectory && numberOfFiles > 0
31 | }
32 |
33 | // Backport slice from JDK 13
34 | private fun ByteBuffer.slice(index: Int, length: Int): ByteBuffer {
35 | position(index)
36 | return slice().limit(length) as ByteBuffer
37 | }
38 |
39 | private fun java.io.File.readLinePositions(starts: IntList) {
40 | require(length() <= Int.MAX_VALUE) {
41 | "Files with size over ${Int.MAX_VALUE} aren't supported"
42 | }
43 |
44 | val averageLineLength = 200
45 | starts.clear(length().toInt() / averageLineLength)
46 |
47 | try {
48 | for (i in readLinePositions()) {
49 | starts.add(i)
50 | }
51 | } catch (e: IOException) {
52 | e.printStackTrace()
53 | starts.clear(1)
54 | starts.add(0)
55 | }
56 |
57 | starts.compact()
58 | }
59 |
60 | private fun java.io.File.readLinePositions() = sequence {
61 | require(length() <= Int.MAX_VALUE) {
62 | "Files with size over ${Int.MAX_VALUE} aren't supported"
63 | }
64 | readBuffer {
65 | yield(position())
66 | while (hasRemaining()) {
67 | val byte = get()
68 | if (byte.isChar('\n')) {
69 | yield(position())
70 | }
71 | }
72 | }
73 | }
74 |
75 | private inline fun java.io.File.readBuffer(block: ByteBuffer.() -> Unit) {
76 | FileInputStream(this).use { stream ->
77 | stream.channel.use { channel ->
78 | channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()).block()
79 | }
80 | }
81 | }
82 |
83 | private fun Byte.isChar(char: Char) = toInt().toChar() == char
84 |
85 | /**
86 | * Compact version of List (without unboxing Int and using IntArray under the hood)
87 | */
88 | private class IntList(initialCapacity: Int = 16) {
89 | @Volatile
90 | private var array = IntArray(initialCapacity)
91 |
92 | @Volatile
93 | var size: Int = 0
94 | private set
95 |
96 | fun clear(capacity: Int) {
97 | array = IntArray(capacity)
98 | size = 0
99 | }
100 |
101 | fun add(value: Int) {
102 | if (size == array.size) {
103 | doubleCapacity()
104 | }
105 | array[size++] = value
106 | }
107 |
108 | operator fun get(index: Int) = array[index]
109 |
110 | private fun doubleCapacity() {
111 | val newArray = IntArray(array.size * 2 + 1)
112 | System.arraycopy(array, 0, newArray, 0, size)
113 | array = newArray
114 | }
115 |
116 | fun compact() {
117 | array = array.copyOfRange(0, size)
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/shared/src/jvmMain/kotlin/org/succlz123/lib/network/ProxyClient.kt:
--------------------------------------------------------------------------------
1 | package org.succlz123.lib.network
2 |
3 | import java.net.InetSocketAddress
4 |
5 | object ProxyClient {
6 |
7 | fun create(proxyHost: String, proxyPort: Int) {
8 | val proxy = java.net.Proxy(java.net.Proxy.Type.HTTP, InetSocketAddress(proxyHost, proxyPort))
9 | System.setProperty("http.proxyHost", proxyHost)
10 | System.setProperty("http.proxyPort", proxyPort.toString())
11 | }
12 | }
--------------------------------------------------------------------------------