├── .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 | icon 6 | 7 |
8 |
9 | windows 10 | macos 11 | linux 12 |
13 | android 14 | iOS 15 |
16 | kotlin 17 | compose 18 |
19 | stars 20 | gpl 21 | release 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 | ![1](screenshots/1.jpg) 54 | 55 | ### Config Your LLMs API Key 56 | 57 | ![2](screenshots/2.jpg) 58 | 59 | ### Prompt Management 60 | 61 | ![4](screenshots/4.jpg) 62 | 63 | ### Chat With Tavern Character 64 | 65 | ![6](screenshots/6.jpg) 66 | 67 | ### User Management 68 | 69 | ![5](screenshots/5.jpg) 70 | 71 | ### Config MCP Servers 72 | 73 | ![3](screenshots/3.jpg) 74 | 75 | ### Setting 76 | 77 | ![7](screenshots/7.jpg) 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 | } --------------------------------------------------------------------------------