diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index bffb357..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..dd075ba --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,35 @@ +name: Build + +on: + push: + branches: [ "**" ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build debug APK + run: ./gradlew assembleDebug --no-daemon + + - name: Run unit tests + run: ./gradlew testDebugUnitTest --no-daemon + + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + name: digital-twin-debug + path: app/build/outputs/apk/debug/app-debug.apk diff --git a/.gitignore b/.gitignore index 62190d7..a5849ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,25 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/app/build +*.apk +*.keystore +!debug.keystore /node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js /.next/ /out/ - -# production -/build - -# misc -.DS_Store *.pem - -# debug npm-debug.log* yarn-debug.log* -yarn-error.log* - -# local env files .env*.local - -# vercel .vercel - -# typescript *.tsbuildinfo next-env.d.ts - -.env \ No newline at end of file +.env diff --git a/README.md b/README.md deleted file mode 100644 index f4da3c4..0000000 --- a/README.md +++ /dev/null @@ -1,34 +0,0 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..b93fe93 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,98 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "com.digitaltwin.assistant" + compileSdk = 34 + + defaultConfig { + applicationId = "com.digitaltwin.assistant" + minSdk = 26 + targetSdk = 34 + versionCode = 1 + versionName = "0.1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { useSupportLibrary = true } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + buildFeatures { + compose = true + buildConfig = true + } + + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.androidx.activity.compose) + + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + implementation(libs.androidx.material.icons.extended) + implementation(libs.androidx.navigation.compose) + + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + implementation(libs.hilt.navigation.compose) + implementation(libs.hilt.work) + ksp(libs.hilt.work.compiler) + + implementation(libs.room.runtime) + implementation(libs.room.ktx) + ksp(libs.room.compiler) + + implementation(libs.work.runtime.ktx) + + implementation(libs.retrofit) + implementation(libs.retrofit.moshi) + implementation(libs.okhttp) + implementation(libs.okhttp.logging) + implementation(libs.moshi.kotlin) + + implementation(libs.kotlinx.coroutines.android) + implementation(libs.datastore.preferences) + + debugImplementation(libs.androidx.ui.tooling) + + testImplementation(libs.junit) + testImplementation(libs.kotlinx.coroutines.test) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..0ad9811 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,3 @@ +# Keep Moshi-generated adapters and model classes used for JSON (de)serialization. +-keep class com.digitaltwin.assistant.ai.model.** { *; } +-keepclassmembers class com.digitaltwin.assistant.ai.model.** { *; } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..25e2e31 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/digitaltwin/assistant/MainActivity.kt b/app/src/main/java/com/digitaltwin/assistant/MainActivity.kt new file mode 100644 index 0000000..71a5bc4 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/MainActivity.kt @@ -0,0 +1,81 @@ +package com.digitaltwin.assistant + +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.digitaltwin.assistant.ui.nav.Screen +import com.digitaltwin.assistant.ui.nav.bottomNavScreens +import com.digitaltwin.assistant.ui.screens.ItemsScreen +import com.digitaltwin.assistant.ui.screens.QueueScreen +import com.digitaltwin.assistant.ui.screens.SettingsScreen +import com.digitaltwin.assistant.ui.screens.TodayScreen +import com.digitaltwin.assistant.ui.theme.DigitalTwinTheme +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Capture text shared from other apps (e.g. corporate email browser). + val sharedText = if (intent?.action == Intent.ACTION_SEND) { + intent.getStringExtra(Intent.EXTRA_TEXT) + } else null + + setContent { + DigitalTwinTheme { + val navController = rememberNavController() + val backStack by navController.currentBackStackEntryAsState() + val current = backStack?.destination?.route + + Scaffold( + bottomBar = { + NavigationBar { + bottomNavScreens.forEach { screen -> + NavigationBarItem( + selected = current == screen.route, + onClick = { + navController.navigate(screen.route) { + popUpTo(Screen.Today.route) { saveState = true } + launchSingleTop = true + restoreState = true + } + }, + icon = { Icon(screen.icon, contentDescription = screen.label) }, + label = { Text(screen.label) }, + ) + } + } + }, + ) { padding -> + NavHost( + navController = navController, + startDestination = if (sharedText != null) Screen.Queue.route else Screen.Today.route, + modifier = Modifier.padding(padding), + ) { + composable(Screen.Today.route) { TodayScreen() } + composable(Screen.Queue.route) { + QueueScreen(prefillText = sharedText.takeIf { current == Screen.Queue.route }) + } + composable(Screen.Items.route) { ItemsScreen() } + composable(Screen.Settings.route) { SettingsScreen() } + } + } + } + } + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/TwinApplication.kt b/app/src/main/java/com/digitaltwin/assistant/TwinApplication.kt new file mode 100644 index 0000000..f00b63e --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/TwinApplication.kt @@ -0,0 +1,55 @@ +package com.digitaltwin.assistant + +import android.app.Application +import android.app.NotificationChannel +import android.app.NotificationManager +import android.os.Build +import androidx.hilt.work.HiltWorkerFactory +import androidx.work.Configuration +import androidx.work.WorkManager +import com.digitaltwin.assistant.workers.NotificationProcessingWorker +import dagger.hilt.android.HiltAndroidApp +import javax.inject.Inject + +@HiltAndroidApp +class TwinApplication : Application(), Configuration.Provider { + + @Inject lateinit var workerFactory: HiltWorkerFactory + @Inject lateinit var workManager: WorkManager + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setWorkerFactory(workerFactory) + .build() + + override fun onCreate() { + super.onCreate() + createNotificationChannels() + NotificationProcessingWorker.enqueueRecurring(workManager) + } + + private fun createNotificationChannels() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + val nm = getSystemService(NotificationManager::class.java) + + nm.createNotificationChannel( + NotificationChannel( + CHANNEL_CAPTURE, + getString(R.string.channel_capture_name), + NotificationManager.IMPORTANCE_LOW, + ).apply { description = getString(R.string.channel_capture_desc) }, + ) + nm.createNotificationChannel( + NotificationChannel( + CHANNEL_REMINDERS, + getString(R.string.channel_reminders_name), + NotificationManager.IMPORTANCE_HIGH, + ).apply { description = getString(R.string.channel_reminders_desc) }, + ) + } + + companion object { + const val CHANNEL_CAPTURE = "capture" + const val CHANNEL_REMINDERS = "reminders" + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ai/ExtractionCandidate.kt b/app/src/main/java/com/digitaltwin/assistant/ai/ExtractionCandidate.kt new file mode 100644 index 0000000..89cf3fa --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ai/ExtractionCandidate.kt @@ -0,0 +1,23 @@ +package com.digitaltwin.assistant.ai + +import com.digitaltwin.assistant.data.model.Category +import com.digitaltwin.assistant.data.model.ItemType +import com.digitaltwin.assistant.data.model.Priority + +/** + * A potential work item produced by an extractor from a piece of text (notification, transcript, + * shared content). Candidates always land in the approval queue — the user confirms before + * anything is acted on. [confidence] (0..1) lets the UI sort/flag low-confidence guesses. + */ +data class ExtractionCandidate( + val title: String, + val description: String? = null, + val type: ItemType = ItemType.MY_TASK, + val contact: String? = null, + val assignedTo: String? = null, + val waitingOn: String? = null, + val category: Category = Category.UNCATEGORIZED, + val priority: Priority = Priority.MEDIUM, + val dueAt: Long? = null, + val confidence: Double = 0.5, +) diff --git a/app/src/main/java/com/digitaltwin/assistant/ai/GroqApi.kt b/app/src/main/java/com/digitaltwin/assistant/ai/GroqApi.kt new file mode 100644 index 0000000..ba23e86 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ai/GroqApi.kt @@ -0,0 +1,26 @@ +package com.digitaltwin.assistant.ai + +import com.digitaltwin.assistant.ai.model.GroqTranscriptionResponse +import okhttp3.MultipartBody +import okhttp3.RequestBody +import retrofit2.http.Header +import retrofit2.http.Multipart +import retrofit2.http.POST +import retrofit2.http.Part + +/** Groq's OpenAI-compatible REST surface (only the audio transcription route we use). */ +interface GroqApi { + @Multipart + @POST("openai/v1/audio/transcriptions") + suspend fun transcribe( + @Header("Authorization") bearer: String, + @Part file: MultipartBody.Part, + @Part("model") model: RequestBody, + @Part("response_format") responseFormat: RequestBody, + ): GroqTranscriptionResponse + + companion object { + const val BASE_URL = "https://api.groq.com/" + const val WHISPER_MODEL = "whisper-large-v3" + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ai/GroqTranscriber.kt b/app/src/main/java/com/digitaltwin/assistant/ai/GroqTranscriber.kt new file mode 100644 index 0000000..f846185 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ai/GroqTranscriber.kt @@ -0,0 +1,43 @@ +package com.digitaltwin.assistant.ai + +import com.digitaltwin.assistant.data.prefs.SettingsStore +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton + +/** Tier-1 STT: Groq-hosted Whisper Large v3. Free tier covers typical personal call volume. */ +@Singleton +class GroqTranscriber @Inject constructor( + private val api: GroqApi, + private val settings: SettingsStore, +) : Transcriber { + + override suspend fun transcribe(audio: File): TranscriptionResult = withContext(Dispatchers.IO) { + val key = settings.groqApiKey ?: return@withContext TranscriptionResult.NotConfigured + if (!audio.exists()) return@withContext TranscriptionResult.Error("Recording not found: ${audio.name}") + + try { + val filePart = MultipartBody.Part.createFormData( + name = "file", + filename = audio.name, + body = audio.asRequestBody("audio/*".toMediaType()), + ) + val plain = "text/plain".toMediaType() + val response = api.transcribe( + bearer = "Bearer $key", + file = filePart, + model = GroqApi.WHISPER_MODEL.toRequestBody(plain), + responseFormat = "json".toRequestBody(plain), + ) + TranscriptionResult.Success(response.text.trim()) + } catch (e: Exception) { + TranscriptionResult.Error(e.message ?: "Transcription failed") + } + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ai/RuleBasedExtractor.kt b/app/src/main/java/com/digitaltwin/assistant/ai/RuleBasedExtractor.kt new file mode 100644 index 0000000..da6fe58 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ai/RuleBasedExtractor.kt @@ -0,0 +1,122 @@ +package com.digitaltwin.assistant.ai + +import com.digitaltwin.assistant.data.model.ItemType +import com.digitaltwin.assistant.data.model.Priority +import java.util.Calendar +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Offline, zero-cost first pass over captured text. It handles the common, unambiguous cases — + * obvious action requests, delegations, follow-ups and status updates — in English and + * Hindi/Hinglish, so most captures never need a paid AI call. Anything it can't confidently read + * is left for the Gemini extractor (Phase 2) to interpret. + */ +@Singleton +class RuleBasedExtractor @Inject constructor() : TaskExtractor { + + override suspend fun extract(text: String, context: ExtractionContext): List { + val clean = text.trim() + if (clean.length < 3) return emptyList() + val lower = clean.lowercase() + + // A status update closing a prior commitment ("done", "sent", "ho gaya"). + if (STATUS_MARKERS.any { lower.contains(it) } && clean.length < 80) { + return listOf( + ExtractionCandidate( + title = clean, + type = ItemType.STATUS_UPDATE, + contact = context.contact, + confidence = 0.55, + ), + ) + } + + val type = when { + DELEGATION_MARKERS.any { lower.contains(it) } -> ItemType.DELEGATED + FOLLOW_UP_MARKERS.any { lower.contains(it) } -> ItemType.FOLLOW_UP + ACTION_MARKERS.any { lower.contains(it) } -> ItemType.MY_TASK + else -> return emptyList() // No actionable signal — defer to AI extractor. + } + + val priority = if (URGENT_MARKERS.any { lower.contains(it) }) Priority.HIGH else Priority.MEDIUM + + return listOf( + ExtractionCandidate( + title = clean.take(140), + description = if (clean.length > 140) clean else null, + type = type, + contact = context.contact, + assignedTo = if (type == ItemType.DELEGATED) context.contact else null, + waitingOn = if (type == ItemType.FOLLOW_UP) context.contact else null, + priority = priority, + dueAt = parseDueDate(lower), + confidence = 0.7, + ), + ) + } + + /** Very small relative-date parser covering the phrases that dominate work chatter. */ + private fun parseDueDate(lower: String): Long? { + val cal = Calendar.getInstance() + return when { + lower.contains("tomorrow") || lower.contains("kal") -> { + cal.add(Calendar.DAY_OF_YEAR, 1); endOfDay(cal) + } + lower.contains("today") || lower.contains("aaj") || lower.contains("eod") -> endOfDay(cal) + lower.contains("tonight") -> { cal.set(Calendar.HOUR_OF_DAY, 20); atMinute0(cal) } + DAY_NAMES.any { lower.contains(it.key) } -> { + val target = DAY_NAMES.entries.first { lower.contains(it.key) }.value + advanceToWeekday(cal, target); endOfDay(cal) + } + else -> null + } + } + + private fun endOfDay(cal: Calendar): Long { + cal.set(Calendar.HOUR_OF_DAY, 18); return atMinute0(cal) + } + + private fun atMinute0(cal: Calendar): Long { + cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0) + return cal.timeInMillis + } + + private fun advanceToWeekday(cal: Calendar, weekday: Int) { + var guard = 0 + while (cal.get(Calendar.DAY_OF_WEEK) != weekday && guard++ < 8) { + cal.add(Calendar.DAY_OF_YEAR, 1) + } + } + + companion object { + private val ACTION_MARKERS = listOf( + "please", "pls", "plz", "can you", "could you", "need to", "have to", "make sure", + "send", "share", "prepare", "review", "call", "schedule", "remind", "submit", "complete", + "follow", "update", "finalize", "finalise", "draft", "check", "confirm", "arrange", + // Hindi / Hinglish + "karna", "karo", "kar do", "bhejo", "bhej do", "tayar", "banao", "dekho", "complete karo", + ) + private val DELEGATION_MARKERS = listOf( + "ask ", "tell ", "assign", "delegate", "get him", "get her", "have them", + "ko bolo", "se bolo", "ko bol", "ko keh", "karwana", "karwao", + ) + private val FOLLOW_UP_MARKERS = listOf( + "waiting for", "waiting on", "follow up", "followup", "revert", "get back", + "awaiting", "pending from", "intezaar", "wait kar", + ) + private val STATUS_MARKERS = listOf( + "done", "completed", "sent", "shared", "finished", "delivered", + "ho gaya", "kar diya", "bhej diya", "complete ho", + ) + private val URGENT_MARKERS = listOf( + "urgent", "asap", "immediately", "right away", "critical", "today", "eod", + "jaldi", "turant", "abhi", + ) + private val DAY_NAMES = mapOf( + "monday" to Calendar.MONDAY, "tuesday" to Calendar.TUESDAY, + "wednesday" to Calendar.WEDNESDAY, "thursday" to Calendar.THURSDAY, + "friday" to Calendar.FRIDAY, "saturday" to Calendar.SATURDAY, "sunday" to Calendar.SUNDAY, + ) + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ai/TaskExtractor.kt b/app/src/main/java/com/digitaltwin/assistant/ai/TaskExtractor.kt new file mode 100644 index 0000000..7ec5525 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ai/TaskExtractor.kt @@ -0,0 +1,20 @@ +package com.digitaltwin.assistant.ai + +import com.digitaltwin.assistant.data.model.Source + +/** Context passed to an extractor so it can attribute and classify what it finds. */ +data class ExtractionContext( + val source: Source, + val contact: String? = null, + /** The user's own name, so the extractor can tell "tasks for me" from "tasks I delegated". */ + val userName: String? = null, +) + +/** + * Turns free text into candidate work items. Phase 1 ships [RuleBasedExtractor]; Phase 2 adds a + * Gemini-backed implementation. The pipeline tries rules first (free, offline, instant) and only + * escalates ambiguous text to the cloud, keeping API cost near zero. + */ +interface TaskExtractor { + suspend fun extract(text: String, context: ExtractionContext): List +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ai/Transcriber.kt b/app/src/main/java/com/digitaltwin/assistant/ai/Transcriber.kt new file mode 100644 index 0000000..59b21fb --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ai/Transcriber.kt @@ -0,0 +1,19 @@ +package com.digitaltwin.assistant.ai + +import java.io.File + +sealed interface TranscriptionResult { + data class Success(val text: String) : TranscriptionResult + data class Error(val message: String) : TranscriptionResult + /** Returned when no API key is configured yet, so callers can prompt the user. */ + data object NotConfigured : TranscriptionResult +} + +/** + * Speech-to-text for call recordings. Phase 1 implementation is Groq Whisper Large v3 (free tier, + * Hindi + English). The interface keeps the Sarvam AI and on-device Whisper tiers from the plan + * pluggable behind the same call site. + */ +interface Transcriber { + suspend fun transcribe(audio: File): TranscriptionResult +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ai/model/GroqModels.kt b/app/src/main/java/com/digitaltwin/assistant/ai/model/GroqModels.kt new file mode 100644 index 0000000..7b5614d --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ai/model/GroqModels.kt @@ -0,0 +1,9 @@ +package com.digitaltwin.assistant.ai.model + +import com.squareup.moshi.JsonClass + +/** Response body from Groq's OpenAI-compatible audio transcription endpoint. */ +@JsonClass(generateAdapter = true) +data class GroqTranscriptionResponse( + val text: String, +) diff --git a/app/src/main/java/com/digitaltwin/assistant/capture/BootReceiver.kt b/app/src/main/java/com/digitaltwin/assistant/capture/BootReceiver.kt new file mode 100644 index 0000000..03a111d --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/capture/BootReceiver.kt @@ -0,0 +1,21 @@ +package com.digitaltwin.assistant.capture + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.work.WorkManager +import com.digitaltwin.assistant.workers.NotificationProcessingWorker +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +/** Re-enqueues recurring WorkManager jobs after device restart. */ +@AndroidEntryPoint +class BootReceiver : BroadcastReceiver() { + + @Inject lateinit var workManager: WorkManager + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != Intent.ACTION_BOOT_COMPLETED) return + NotificationProcessingWorker.enqueueRecurring(workManager) + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/capture/CallStateReceiver.kt b/app/src/main/java/com/digitaltwin/assistant/capture/CallStateReceiver.kt new file mode 100644 index 0000000..5561b39 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/capture/CallStateReceiver.kt @@ -0,0 +1,44 @@ +package com.digitaltwin.assistant.capture + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.telephony.TelephonyManager +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf +import com.digitaltwin.assistant.workers.CallRecordingWorker +import dagger.hilt.android.AndroidEntryPoint +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * Listens for PHONE_STATE broadcasts. On IDLE (call ended), fires a delayed WorkManager job that + * scans the recordings folder, finds the file that was just written, and processes it. The 60-second + * delay gives recorder apps time to finish writing the audio file. + */ +@AndroidEntryPoint +class CallStateReceiver : BroadcastReceiver() { + + @Inject lateinit var workManager: WorkManager + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != "android.intent.action.PHONE_STATE") return + + val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE) ?: return + if (state != TelephonyManager.EXTRA_STATE_IDLE) return + + val number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER) + + val request = OneTimeWorkRequestBuilder() + .setInputData(workDataOf(CallRecordingWorker.KEY_PHONE_NUMBER to number)) + .setInitialDelay(RECORDING_SETTLE_SECS, TimeUnit.SECONDS) + .build() + + workManager.enqueue(request) + } + + companion object { + private const val RECORDING_SETTLE_SECS = 60L + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/capture/TwinNotificationListenerService.kt b/app/src/main/java/com/digitaltwin/assistant/capture/TwinNotificationListenerService.kt new file mode 100644 index 0000000..56b0588 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/capture/TwinNotificationListenerService.kt @@ -0,0 +1,86 @@ +package com.digitaltwin.assistant.capture + +import android.content.ComponentName +import android.content.pm.PackageManager +import android.service.notification.NotificationListenerService +import android.service.notification.StatusBarNotification +import com.digitaltwin.assistant.data.local.entity.CapturedNotification +import com.digitaltwin.assistant.data.repository.CaptureRepository +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import java.security.MessageDigest +import javax.inject.Inject + +/** + * Runs persistently once the user grants Notification Access. Every notification is hashed and + * stored raw; a WorkManager job processes them in batch every 5 minutes. This keeps the hot path + * (notification arrival) as cheap as a single DB insert. + */ +@AndroidEntryPoint +class TwinNotificationListenerService : NotificationListenerService() { + + @Inject lateinit var captureRepository: CaptureRepository + + private val job = SupervisorJob() + private val scope = CoroutineScope(Dispatchers.IO + job) + + // Packages that dominate work comms — others are stored but given a lower priority hint. + private val workPackages = setOf( + "com.whatsapp", + "com.whatsapp.w4b", // WhatsApp Business + "com.android.mms", + "com.google.android.apps.messaging", + ) + + override fun onNotificationPosted(sbn: StatusBarNotification) { + val pkg = sbn.packageName ?: return + // Never capture our own notifications — that would create infinite loops. + if (pkg == packageName) return + // Skip ongoing/persistent notifications (music playback, navigation, etc.). + if (sbn.isOngoing) return + + val extras = sbn.notification?.extras ?: return + val title = extras.getString("android.title") + val body = extras.getCharSequence("android.text")?.toString() + ?: extras.getCharSequence("android.bigText")?.toString() + + if (title.isNullOrBlank() && body.isNullOrBlank()) return + + val hash = sha1("$pkg|$title|$body") + val notification = CapturedNotification( + appPackage = pkg, + title = title, + body = body, + contentHash = hash, + ) + + scope.launch { + captureRepository.storeNotification(notification) + } + } + + override fun onDestroy() { + super.onDestroy() + job.cancel() + } + + private fun sha1(text: String): String { + val bytes = MessageDigest.getInstance("SHA-1").digest(text.toByteArray()) + return bytes.joinToString("") { "%02x".format(it) } + } + + companion object { + /** Check if the notification listener is currently enabled. */ + fun isEnabled(pm: PackageManager, packageName: String): Boolean { + return try { + val cn = ComponentName(packageName, TwinNotificationListenerService::class.java.name) + pm.getComponentEnabledSetting(cn) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED + } catch (_: Exception) { + false + } + } + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/data/local/Converters.kt b/app/src/main/java/com/digitaltwin/assistant/data/local/Converters.kt new file mode 100644 index 0000000..e3f5a4d --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/local/Converters.kt @@ -0,0 +1,26 @@ +package com.digitaltwin.assistant.data.local + +import androidx.room.TypeConverter +import com.digitaltwin.assistant.data.model.Category +import com.digitaltwin.assistant.data.model.ItemStatus +import com.digitaltwin.assistant.data.model.ItemType +import com.digitaltwin.assistant.data.model.Priority +import com.digitaltwin.assistant.data.model.Source + +/** Stores enums as their stable [Enum.name] string so reordering the enum never corrupts data. */ +class Converters { + @TypeConverter fun itemTypeToString(v: ItemType): String = v.name + @TypeConverter fun stringToItemType(v: String): ItemType = ItemType.valueOf(v) + + @TypeConverter fun sourceToString(v: Source): String = v.name + @TypeConverter fun stringToSource(v: String): Source = Source.valueOf(v) + + @TypeConverter fun statusToString(v: ItemStatus): String = v.name + @TypeConverter fun stringToStatus(v: String): ItemStatus = ItemStatus.valueOf(v) + + @TypeConverter fun categoryToString(v: Category): String = v.name + @TypeConverter fun stringToCategory(v: String): Category = Category.valueOf(v) + + @TypeConverter fun priorityToString(v: Priority): String = v.name + @TypeConverter fun stringToPriority(v: String): Priority = Priority.valueOf(v) +} diff --git a/app/src/main/java/com/digitaltwin/assistant/data/local/TwinDatabase.kt b/app/src/main/java/com/digitaltwin/assistant/data/local/TwinDatabase.kt new file mode 100644 index 0000000..c8d0bed --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/local/TwinDatabase.kt @@ -0,0 +1,33 @@ +package com.digitaltwin.assistant.data.local + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.digitaltwin.assistant.data.local.dao.CaptureDao +import com.digitaltwin.assistant.data.local.dao.ContactTagDao +import com.digitaltwin.assistant.data.local.dao.WorkItemDao +import com.digitaltwin.assistant.data.local.entity.CallRecord +import com.digitaltwin.assistant.data.local.entity.CapturedNotification +import com.digitaltwin.assistant.data.local.entity.ContactTag +import com.digitaltwin.assistant.data.local.entity.WorkItem + +@Database( + entities = [ + WorkItem::class, + CapturedNotification::class, + CallRecord::class, + ContactTag::class, + ], + version = 1, + exportSchema = false, +) +@TypeConverters(Converters::class) +abstract class TwinDatabase : RoomDatabase() { + abstract fun workItemDao(): WorkItemDao + abstract fun captureDao(): CaptureDao + abstract fun contactTagDao(): ContactTagDao + + companion object { + const val NAME = "twin.db" + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/data/local/dao/CaptureDao.kt b/app/src/main/java/com/digitaltwin/assistant/data/local/dao/CaptureDao.kt new file mode 100644 index 0000000..92e234f --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/local/dao/CaptureDao.kt @@ -0,0 +1,38 @@ +package com.digitaltwin.assistant.data.local.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update +import com.digitaltwin.assistant.data.local.entity.CallRecord +import com.digitaltwin.assistant.data.local.entity.CapturedNotification + +@Dao +interface CaptureDao { + + /** Ignores duplicates by [CapturedNotification.contentHash]; returns -1 if it was a dupe. */ + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insertNotification(n: CapturedNotification): Long + + @Query("SELECT * FROM captured_notifications WHERE processedAt IS NULL ORDER BY receivedAt ASC LIMIT :limit") + suspend fun getUnprocessedNotifications(limit: Int = 50): List + + @Update + suspend fun updateNotification(n: CapturedNotification) + + @Query("DELETE FROM captured_notifications WHERE processedAt IS NOT NULL AND receivedAt < :before") + suspend fun pruneProcessedNotifications(before: Long) + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insertCallRecord(c: CallRecord): Long + + @Query("SELECT * FROM call_records WHERE processedAt IS NULL ORDER BY calledAt ASC") + suspend fun getUnprocessedCalls(): List + + @Query("SELECT * FROM call_records WHERE recordingPath = :path LIMIT 1") + suspend fun findCallByPath(path: String): CallRecord? + + @Update + suspend fun updateCallRecord(c: CallRecord) +} diff --git a/app/src/main/java/com/digitaltwin/assistant/data/local/dao/ContactTagDao.kt b/app/src/main/java/com/digitaltwin/assistant/data/local/dao/ContactTagDao.kt new file mode 100644 index 0000000..389cb88 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/local/dao/ContactTagDao.kt @@ -0,0 +1,24 @@ +package com.digitaltwin.assistant.data.local.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.digitaltwin.assistant.data.local.entity.ContactTag +import kotlinx.coroutines.flow.Flow + +@Dao +interface ContactTagDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun upsert(tag: ContactTag) + + @Query("SELECT * FROM contact_tags ORDER BY displayName ASC") + fun observeAll(): Flow> + + @Query("SELECT * FROM contact_tags WHERE matchKey = :key LIMIT 1") + suspend fun findByKey(key: String): ContactTag? + + @Query("DELETE FROM contact_tags WHERE id = :id") + suspend fun delete(id: Long) +} diff --git a/app/src/main/java/com/digitaltwin/assistant/data/local/dao/WorkItemDao.kt b/app/src/main/java/com/digitaltwin/assistant/data/local/dao/WorkItemDao.kt new file mode 100644 index 0000000..b9baee9 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/local/dao/WorkItemDao.kt @@ -0,0 +1,83 @@ +package com.digitaltwin.assistant.data.local.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import com.digitaltwin.assistant.data.local.entity.WorkItem +import com.digitaltwin.assistant.data.model.ItemStatus +import com.digitaltwin.assistant.data.model.ItemType +import kotlinx.coroutines.flow.Flow + +@Dao +interface WorkItemDao { + + @Insert + suspend fun insert(item: WorkItem): Long + + @Update + suspend fun update(item: WorkItem) + + @Query("SELECT * FROM work_items WHERE id = :id") + suspend fun getById(id: Long): WorkItem? + + /** The approval queue: everything awaiting a decision, newest first. */ + @Query("SELECT * FROM work_items WHERE status = 'QUEUED' ORDER BY createdAt DESC") + fun observeQueue(): Flow> + + @Query("SELECT COUNT(*) FROM work_items WHERE status = 'QUEUED'") + fun observeQueueCount(): Flow + + /** Approved items of a given type, used by the four list tabs. */ + @Query( + """ + SELECT * FROM work_items + WHERE type = :type AND status NOT IN ('QUEUED', 'DISCARDED', 'RESOLVED') + ORDER BY + CASE priority WHEN 'HIGH' THEN 0 WHEN 'MEDIUM' THEN 1 ELSE 2 END, + (dueAt IS NULL), dueAt ASC + """, + ) + fun observeActiveByType(type: ItemType): Flow> + + @Query("SELECT * FROM work_items WHERE status = 'RESOLVED' ORDER BY lastUpdatedAt DESC") + fun observeResolved(): Flow> + + /** Items due between [from] and [to] (for the Today view and morning briefing). */ + @Query( + """ + SELECT * FROM work_items + WHERE status NOT IN ('QUEUED', 'DISCARDED', 'RESOLVED') + AND dueAt IS NOT NULL AND dueAt BETWEEN :from AND :to + ORDER BY dueAt ASC + """, + ) + fun observeDueBetween(from: Long, to: Long): Flow> + + @Query( + """ + SELECT * FROM work_items + WHERE status NOT IN ('QUEUED', 'DISCARDED', 'RESOLVED') + AND dueAt IS NOT NULL AND dueAt < :now + ORDER BY dueAt ASC + """, + ) + suspend fun getOverdue(now: Long): List + + /** + * Open delegated/follow-up items the assistant might be able to auto-close when a matching + * status update arrives. + */ + @Query( + """ + SELECT * FROM work_items + WHERE status IN ('ACTIVE', 'WAITING', 'SCHEDULED') + AND type IN ('DELEGATED', 'FOLLOW_UP') + ORDER BY lastUpdatedAt ASC + """, + ) + suspend fun getOpenWaiting(): List + + @Query("UPDATE work_items SET status = :status, lastUpdatedAt = :now WHERE id = :id") + suspend fun setStatus(id: Long, status: ItemStatus, now: Long = System.currentTimeMillis()) +} diff --git a/app/src/main/java/com/digitaltwin/assistant/data/local/entity/CallRecord.kt b/app/src/main/java/com/digitaltwin/assistant/data/local/entity/CallRecord.kt new file mode 100644 index 0000000..3939815 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/local/entity/CallRecord.kt @@ -0,0 +1,25 @@ +package com.digitaltwin.assistant.data.local.entity + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +/** + * A finished phone call the assistant noticed. The recording at [recordingPath] is transcribed + * (Groq Whisper) and the transcript mined for tasks/follow-ups. [transcript] is cached so we never + * pay to transcribe the same file twice. + */ +@Entity( + tableName = "call_records", + indices = [Index("recordingPath", unique = true)], +) +data class CallRecord( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + val contactName: String? = null, + val phoneNumber: String? = null, + val durationSec: Int = 0, + val recordingPath: String? = null, + val calledAt: Long = System.currentTimeMillis(), + val processedAt: Long? = null, + val transcript: String? = null, +) diff --git a/app/src/main/java/com/digitaltwin/assistant/data/local/entity/CapturedNotification.kt b/app/src/main/java/com/digitaltwin/assistant/data/local/entity/CapturedNotification.kt new file mode 100644 index 0000000..b4c42f6 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/local/entity/CapturedNotification.kt @@ -0,0 +1,30 @@ +package com.digitaltwin.assistant.data.local.entity + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +/** + * Raw notification text harvested by the listener service. Stored first, processed later in batches + * (every few minutes) to keep the capture path cheap and battery-friendly. [contentHash] is used + * for de-duplication so the same message isn't turned into a task twice. + */ +@Entity( + tableName = "captured_notifications", + indices = [ + Index("contentHash", unique = true), + Index("processedAt"), + ], +) +data class CapturedNotification( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + val appPackage: String, + val title: String?, + val body: String?, + val contentHash: String, + val receivedAt: Long = System.currentTimeMillis(), + /** Null until the batch worker has processed this notification. */ + val processedAt: Long? = null, + /** The work item produced, if any. */ + val workItemId: Long? = null, +) diff --git a/app/src/main/java/com/digitaltwin/assistant/data/local/entity/ContactTag.kt b/app/src/main/java/com/digitaltwin/assistant/data/local/entity/ContactTag.kt new file mode 100644 index 0000000..ade8613 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/local/entity/ContactTag.kt @@ -0,0 +1,23 @@ +package com.digitaltwin.assistant.data.local.entity + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import com.digitaltwin.assistant.data.model.Category + +/** + * User-assigned classification for a contact (Client / Team / etc.). Lets the assistant + * auto-categorise and prioritise captured items by who they involve. Keyed by a normalised + * display name or number so it can be matched against notification senders and call participants. + */ +@Entity( + tableName = "contact_tags", + indices = [Index("matchKey", unique = true)], +) +data class ContactTag( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + /** Lower-cased contact name or phone number used for matching. */ + val matchKey: String, + val displayName: String, + val category: Category, +) diff --git a/app/src/main/java/com/digitaltwin/assistant/data/local/entity/WorkItem.kt b/app/src/main/java/com/digitaltwin/assistant/data/local/entity/WorkItem.kt new file mode 100644 index 0000000..facc993 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/local/entity/WorkItem.kt @@ -0,0 +1,60 @@ +package com.digitaltwin.assistant.data.local.entity + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import com.digitaltwin.assistant.data.model.Category +import com.digitaltwin.assistant.data.model.ItemStatus +import com.digitaltwin.assistant.data.model.ItemType +import com.digitaltwin.assistant.data.model.Priority +import com.digitaltwin.assistant.data.model.Source + +/** + * The central record. Everything the assistant captures becomes a [WorkItem]: the user's own tasks, + * things they delegated, things they're waiting on, and status updates that close those out. + */ +@Entity( + tableName = "work_items", + indices = [ + Index("status"), + Index("type"), + Index("parentItemId"), + ], +) +data class WorkItem( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + + val title: String, + val description: String? = null, + + val type: ItemType, + val source: Source, + val status: ItemStatus, + + /** Contact this item came from / is about (display name). */ + val sourceContact: String? = null, + + /** For DELEGATED items: who it was assigned to. */ + val assignedTo: String? = null, + + /** For FOLLOW_UP items: who the user is waiting on. */ + val waitingOn: String? = null, + + val category: Category = Category.UNCATEGORIZED, + val priority: Priority = Priority.MEDIUM, + + /** Epoch millis the item is due / scheduled for, if any. */ + val dueAt: Long? = null, + + /** Google Calendar event id once scheduled. */ + val calendarEventId: String? = null, + + /** Links a STATUS_UPDATE (or follow-up resolution) back to the item it updates. */ + val parentItemId: Long? = null, + + /** Absolute path of the call recording this item was extracted from, if applicable. */ + val callRecordingPath: String? = null, + + val createdAt: Long = System.currentTimeMillis(), + val lastUpdatedAt: Long = System.currentTimeMillis(), +) diff --git a/app/src/main/java/com/digitaltwin/assistant/data/model/Enums.kt b/app/src/main/java/com/digitaltwin/assistant/data/model/Enums.kt new file mode 100644 index 0000000..8ad7337 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/model/Enums.kt @@ -0,0 +1,74 @@ +package com.digitaltwin.assistant.data.model + +/** + * The four kinds of work the assistant tracks. The whole product thesis is that a PM doesn't only + * have their own tasks — they delegate, they wait on others, and they log status. All four flow + * through the same capture → approve → track loop. + */ +enum class ItemType { + /** Something the user needs to do. */ + MY_TASK, + + /** Something the user asked someone else to do; tracked until that person confirms. */ + DELEGATED, + + /** The user is blocked waiting on someone else's reply/decision. */ + FOLLOW_UP, + + /** An update on an existing item (e.g. "report sent") — usually closes a DELEGATED/FOLLOW_UP. */ + STATUS_UPDATE, +} + +/** Where a captured item originated. Drives confidence heuristics and context display. */ +enum class Source { + WHATSAPP, + SMS, + CALL_RECORDING, + EMAIL_NOTIF, + MANUAL, + OTHER_NOTIF, +} + +/** Lifecycle of a work item from capture to resolution. */ +enum class ItemStatus { + /** Just captured, awaiting the user's approve/discard decision. */ + QUEUED, + + /** Approved and live, not yet on the calendar. */ + ACTIVE, + + /** Approved and written to Google Calendar. */ + SCHEDULED, + + /** Blocked on someone else (delegated/follow-up). */ + WAITING, + + /** Completed / closed. */ + RESOLVED, + + /** User rejected it from the queue. */ + DISCARDED, +} + +/** Coarse classification used for prioritisation and the morning briefing. */ +enum class Category { + CLIENT, + TEAM, + ADMIN, + PERSONAL, + UNCATEGORIZED, +} + +enum class Priority { + HIGH, + MEDIUM, + LOW, +} + +/** The kind of Google Calendar artifact a work item maps to. */ +enum class CalendarTargetType { + EVENT, + ALL_DAY, + REMINDER, + TIME_BLOCK, +} diff --git a/app/src/main/java/com/digitaltwin/assistant/data/prefs/SettingsStore.kt b/app/src/main/java/com/digitaltwin/assistant/data/prefs/SettingsStore.kt new file mode 100644 index 0000000..321b951 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/prefs/SettingsStore.kt @@ -0,0 +1,50 @@ +package com.digitaltwin.assistant.data.prefs + +import android.content.Context +import androidx.core.content.edit +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Small synchronous settings + secrets store. Backed by a dedicated SharedPreferences file that is + * excluded from cloud backup (see data_extraction_rules.xml) so API keys never leave the device. + */ +@Singleton +class SettingsStore @Inject constructor( + @ApplicationContext context: Context, +) { + private val prefs = context.getSharedPreferences(FILE, Context.MODE_PRIVATE) + + var groqApiKey: String? + get() = prefs.getString(KEY_GROQ, null)?.takeIf { it.isNotBlank() } + set(value) = prefs.edit { putString(KEY_GROQ, value) } + + var geminiApiKey: String? + get() = prefs.getString(KEY_GEMINI, null)?.takeIf { it.isNotBlank() } + set(value) = prefs.edit { putString(KEY_GEMINI, value) } + + /** The user's own name, used to tell "tasks for me" from "tasks I delegated". */ + var userName: String? + get() = prefs.getString(KEY_USER_NAME, null) + set(value) = prefs.edit { putString(KEY_USER_NAME, value) } + + /** Folder the user's call recorder writes to. */ + var recordingsDir: String? + get() = prefs.getString(KEY_REC_DIR, null) + set(value) = prefs.edit { putString(KEY_REC_DIR, value) } + + /** Whether the user has opted in to sending text to cloud AI. */ + var cloudAiEnabled: Boolean + get() = prefs.getBoolean(KEY_CLOUD_AI, false) + set(value) = prefs.edit { putBoolean(KEY_CLOUD_AI, value) } + + companion object { + const val FILE = "twin_secrets" + private const val KEY_GROQ = "groq_api_key" + private const val KEY_GEMINI = "gemini_api_key" + private const val KEY_USER_NAME = "user_name" + private const val KEY_REC_DIR = "recordings_dir" + private const val KEY_CLOUD_AI = "cloud_ai_enabled" + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/data/repository/CaptureRepository.kt b/app/src/main/java/com/digitaltwin/assistant/data/repository/CaptureRepository.kt new file mode 100644 index 0000000..b3aae1e --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/repository/CaptureRepository.kt @@ -0,0 +1,118 @@ +package com.digitaltwin.assistant.data.repository + +import com.digitaltwin.assistant.ai.ExtractionCandidate +import com.digitaltwin.assistant.ai.ExtractionContext +import com.digitaltwin.assistant.ai.RuleBasedExtractor +import com.digitaltwin.assistant.data.local.dao.CaptureDao +import com.digitaltwin.assistant.data.local.entity.CallRecord +import com.digitaltwin.assistant.data.local.entity.CapturedNotification +import com.digitaltwin.assistant.data.local.entity.WorkItem +import com.digitaltwin.assistant.data.model.ItemStatus +import com.digitaltwin.assistant.data.model.Source +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class CaptureRepository @Inject constructor( + private val captureDao: CaptureDao, + private val workItemRepository: WorkItemRepository, + private val ruleBasedExtractor: RuleBasedExtractor, + private val contactClassifier: ContactClassifier, +) { + + /** Called by the NotificationListenerService for every notification that arrives. */ + suspend fun storeNotification(notification: CapturedNotification): Boolean { + val rowId = captureDao.insertNotification(notification) + return rowId != -1L // false = duplicate (same contentHash), was already stored + } + + /** + * Batch-processes stored unprocessed notifications. Runs every 5 minutes from WorkManager. + * Returns the count of work items queued. + */ + suspend fun processUnprocessedNotifications(): Int { + val pending = captureDao.getUnprocessedNotifications() + var queued = 0 + val now = System.currentTimeMillis() + + for (n in pending) { + val text = listOfNotNull(n.title, n.body).joinToString(" — ") + if (text.isBlank()) { + captureDao.updateNotification(n.copy(processedAt = now)) + continue + } + + val source = sourceForPackage(n.appPackage) + val context = ExtractionContext(source = source, contact = n.title) + val candidates = ruleBasedExtractor.extract(text, context) + + var workItemId: Long? = null + if (candidates.isNotEmpty()) { + val best = candidates.maxByOrNull { it.confidence }!! + val category = contactClassifier.categoryFor(best.contact) + val item = best.toWorkItem(source, n.title, category) + workItemId = workItemRepository.insert(item) + queued++ + } + captureDao.updateNotification(n.copy(processedAt = now, workItemId = workItemId)) + } + + // Keep the notifications table small: prune anything older than 7 days. + captureDao.pruneProcessedNotifications(now - PRUNE_AGE_MS) + return queued + } + + /** Stores a new call record. Returns the inserted id (or -1 if path already exists). */ + suspend fun storeCallRecord(record: CallRecord): Long = + captureDao.insertCallRecord(record) + + /** Marks a call record processed and optionally stores extracted work items from the transcript. */ + suspend fun processCallRecord(record: CallRecord, candidates: List): Int { + val now = System.currentTimeMillis() + var queued = 0 + for (candidate in candidates) { + val category = contactClassifier.categoryFor(candidate.contact) + val item = candidate.toWorkItem(Source.CALL_RECORDING, record.contactName, category) + .copy(callRecordingPath = record.recordingPath) + workItemRepository.insert(item) + queued++ + } + captureDao.updateCallRecord(record.copy(processedAt = now)) + return queued + } + + suspend fun findCallByPath(path: String) = captureDao.findCallByPath(path) + + suspend fun getUnprocessedCalls() = captureDao.getUnprocessedCalls() + + suspend fun updateCallRecord(record: CallRecord) = captureDao.updateCallRecord(record) + + private fun sourceForPackage(pkg: String): Source = when { + pkg.startsWith("com.whatsapp") -> Source.WHATSAPP + pkg == "com.android.mms" || pkg == "com.google.android.apps.messaging" -> Source.SMS + else -> Source.OTHER_NOTIF + } + + companion object { + private const val PRUNE_AGE_MS = 7 * 24 * 60 * 60 * 1000L + } +} + +// Extension so repositories don't depend on UI/AI layers at call site. +fun ExtractionCandidate.toWorkItem( + source: Source, + contact: String?, + category: com.digitaltwin.assistant.data.model.Category, +) = WorkItem( + title = title, + description = description, + type = type, + source = source, + status = ItemStatus.QUEUED, + sourceContact = contact, + assignedTo = assignedTo, + waitingOn = waitingOn, + category = category, + priority = priority, + dueAt = dueAt, +) diff --git a/app/src/main/java/com/digitaltwin/assistant/data/repository/ContactClassifier.kt b/app/src/main/java/com/digitaltwin/assistant/data/repository/ContactClassifier.kt new file mode 100644 index 0000000..11de633 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/repository/ContactClassifier.kt @@ -0,0 +1,17 @@ +package com.digitaltwin.assistant.data.repository + +import com.digitaltwin.assistant.data.local.dao.ContactTagDao +import com.digitaltwin.assistant.data.model.Category +import javax.inject.Inject +import javax.inject.Singleton + +/** Maps a captured contact name/number to a [Category] using the user's contact tags. */ +@Singleton +class ContactClassifier @Inject constructor( + private val contactTagDao: ContactTagDao, +) { + suspend fun categoryFor(contact: String?): Category { + val key = contact?.trim()?.lowercase() ?: return Category.UNCATEGORIZED + return contactTagDao.findByKey(key)?.category ?: Category.UNCATEGORIZED + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/data/repository/WorkItemRepository.kt b/app/src/main/java/com/digitaltwin/assistant/data/repository/WorkItemRepository.kt new file mode 100644 index 0000000..758e89b --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/data/repository/WorkItemRepository.kt @@ -0,0 +1,56 @@ +package com.digitaltwin.assistant.data.repository + +import com.digitaltwin.assistant.data.local.dao.WorkItemDao +import com.digitaltwin.assistant.data.local.entity.WorkItem +import com.digitaltwin.assistant.data.model.ItemStatus +import com.digitaltwin.assistant.data.model.ItemType +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WorkItemRepository @Inject constructor( + private val dao: WorkItemDao, +) { + fun queue(): Flow> = dao.observeQueue() + fun queueCount(): Flow = dao.observeQueueCount() + fun activeByType(type: ItemType): Flow> = dao.observeActiveByType(type) + fun resolved(): Flow> = dao.observeResolved() + fun dueBetween(from: Long, to: Long): Flow> = dao.observeDueBetween(from, to) + + suspend fun insert(item: WorkItem): Long = dao.insert(item) + + /** + * Approve a queued item. Follow-up/delegated items move to WAITING (we're tracking someone + * else); the user's own tasks become ACTIVE. Calendar scheduling is offered separately. + */ + suspend fun approve(item: WorkItem) { + val newStatus = when (item.type) { + ItemType.DELEGATED, ItemType.FOLLOW_UP -> ItemStatus.WAITING + else -> ItemStatus.ACTIVE + } + dao.update(item.copy(status = newStatus, lastUpdatedAt = System.currentTimeMillis())) + } + + suspend fun discard(item: WorkItem) = dao.setStatus(item.id, ItemStatus.DISCARDED) + + suspend fun resolve(id: Long) = dao.setStatus(id, ItemStatus.RESOLVED) + + suspend fun markScheduled(id: Long, calendarEventId: String) { + dao.getById(id)?.let { + dao.update( + it.copy( + status = ItemStatus.SCHEDULED, + calendarEventId = calendarEventId, + lastUpdatedAt = System.currentTimeMillis(), + ), + ) + } + } + + suspend fun update(item: WorkItem) = + dao.update(item.copy(lastUpdatedAt = System.currentTimeMillis())) + + suspend fun openWaitingItems(): List = dao.getOpenWaiting() + suspend fun overdue(now: Long = System.currentTimeMillis()): List = dao.getOverdue(now) +} diff --git a/app/src/main/java/com/digitaltwin/assistant/di/DatabaseModule.kt b/app/src/main/java/com/digitaltwin/assistant/di/DatabaseModule.kt new file mode 100644 index 0000000..1c484b6 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/di/DatabaseModule.kt @@ -0,0 +1,30 @@ +package com.digitaltwin.assistant.di + +import android.content.Context +import androidx.room.Room +import com.digitaltwin.assistant.data.local.TwinDatabase +import com.digitaltwin.assistant.data.local.dao.CaptureDao +import com.digitaltwin.assistant.data.local.dao.ContactTagDao +import com.digitaltwin.assistant.data.local.dao.WorkItemDao +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + + @Provides + @Singleton + fun provideDatabase(@ApplicationContext ctx: Context): TwinDatabase = + Room.databaseBuilder(ctx, TwinDatabase::class.java, TwinDatabase.NAME) + .fallbackToDestructiveMigration() + .build() + + @Provides fun provideWorkItemDao(db: TwinDatabase): WorkItemDao = db.workItemDao() + @Provides fun provideCaptureDao(db: TwinDatabase): CaptureDao = db.captureDao() + @Provides fun provideContactTagDao(db: TwinDatabase): ContactTagDao = db.contactTagDao() +} diff --git a/app/src/main/java/com/digitaltwin/assistant/di/NetworkModule.kt b/app/src/main/java/com/digitaltwin/assistant/di/NetworkModule.kt new file mode 100644 index 0000000..5996e21 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/di/NetworkModule.kt @@ -0,0 +1,54 @@ +package com.digitaltwin.assistant.di + +import com.digitaltwin.assistant.ai.GroqApi +import com.digitaltwin.assistant.ai.GroqTranscriber +import com.digitaltwin.assistant.ai.Transcriber +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class NetworkModule { + + @Binds + @Singleton + abstract fun bindTranscriber(impl: GroqTranscriber): Transcriber + + companion object { + @Provides + @Singleton + fun provideMoshi(): Moshi = Moshi.Builder() + .addLast(KotlinJsonAdapterFactory()) + .build() + + @Provides + @Singleton + fun provideOkHttp(): OkHttpClient = OkHttpClient.Builder() + .addInterceptor( + HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BASIC + }, + ) + .build() + + @Provides + @Singleton + fun provideGroqApi(client: OkHttpClient, moshi: Moshi): GroqApi = + Retrofit.Builder() + .baseUrl(GroqApi.BASE_URL) + .client(client) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() + .create(GroqApi::class.java) + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/di/WorkManagerModule.kt b/app/src/main/java/com/digitaltwin/assistant/di/WorkManagerModule.kt new file mode 100644 index 0000000..f902567 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/di/WorkManagerModule.kt @@ -0,0 +1,20 @@ +package com.digitaltwin.assistant.di + +import android.content.Context +import androidx.work.WorkManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object WorkManagerModule { + + @Provides + @Singleton + fun provideWorkManager(@ApplicationContext ctx: Context): WorkManager = + WorkManager.getInstance(ctx) +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ui/nav/TwinNavGraph.kt b/app/src/main/java/com/digitaltwin/assistant/ui/nav/TwinNavGraph.kt new file mode 100644 index 0000000..af06125 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ui/nav/TwinNavGraph.kt @@ -0,0 +1,17 @@ +package com.digitaltwin.assistant.ui.nav + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Checklist +import androidx.compose.material.icons.filled.Inbox +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Today +import androidx.compose.ui.graphics.vector.ImageVector + +sealed class Screen(val route: String, val label: String, val icon: ImageVector) { + data object Today : Screen("today", "Today", Icons.Default.Today) + data object Queue : Screen("queue", "Queue", Icons.Default.Inbox) + data object Items : Screen("items", "Items", Icons.Default.Checklist) + data object Settings : Screen("settings", "Settings", Icons.Default.Settings) +} + +val bottomNavScreens = listOf(Screen.Today, Screen.Queue, Screen.Items, Screen.Settings) diff --git a/app/src/main/java/com/digitaltwin/assistant/ui/screens/ItemsScreen.kt b/app/src/main/java/com/digitaltwin/assistant/ui/screens/ItemsScreen.kt new file mode 100644 index 0000000..4cc0c56 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ui/screens/ItemsScreen.kt @@ -0,0 +1,101 @@ +package com.digitaltwin.assistant.ui.screens + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.digitaltwin.assistant.data.local.entity.WorkItem +import com.digitaltwin.assistant.ui.viewmodel.ItemsViewModel + +@Composable +fun ItemsScreen(vm: ItemsViewModel = hiltViewModel()) { + val myTasks by vm.myTasks.collectAsStateWithLifecycle() + val delegated by vm.delegated.collectAsStateWithLifecycle() + val followUps by vm.followUps.collectAsStateWithLifecycle() + val resolved by vm.resolved.collectAsStateWithLifecycle() + + var tab by remember { mutableIntStateOf(0) } + val tabs = listOf( + "My Tasks (${myTasks.size})", + "Delegated (${delegated.size})", + "Follow-ups (${followUps.size})", + "Done", + ) + + Column(modifier = Modifier.fillMaxSize()) { + Spacer(Modifier.height(16.dp)) + Text( + "Work Items", + modifier = Modifier.padding(horizontal = 16.dp), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + ) + Spacer(Modifier.height(8.dp)) + ScrollableTabRow(selectedTabIndex = tab, edgePadding = 16.dp) { + tabs.forEachIndexed { i, title -> + Tab(selected = tab == i, onClick = { tab = i }, text = { Text(title) }) + } + } + Spacer(Modifier.height(8.dp)) + + val currentList = when (tab) { + 0 -> myTasks + 1 -> delegated + 2 -> followUps + else -> resolved + } + + if (currentList.isEmpty()) { + Text( + "Nothing here yet.", + modifier = Modifier.padding(16.dp), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f), + ) + } else { + LazyColumn( + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 4.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + items(currentList, key = { it.id }) { item -> + WorkItemCard( + item = item, + trailingContent = if (tab != 3) ({ + IconButton(onClick = { vm.resolve(item) }) { + Icon( + Icons.Default.CheckCircle, + contentDescription = "Mark resolved", + tint = MaterialTheme.colorScheme.primary, + ) + } + }) else null, + ) + } + } + } + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ui/screens/QueueScreen.kt b/app/src/main/java/com/digitaltwin/assistant/ui/screens/QueueScreen.kt new file mode 100644 index 0000000..d091d7f --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ui/screens/QueueScreen.kt @@ -0,0 +1,197 @@ +package com.digitaltwin.assistant.ui.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.digitaltwin.assistant.data.local.entity.WorkItem +import com.digitaltwin.assistant.data.model.ItemType +import com.digitaltwin.assistant.ui.viewmodel.QueueViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun QueueScreen( + prefillText: String? = null, + vm: QueueViewModel = hiltViewModel(), +) { + val queue by vm.queue.collectAsStateWithLifecycle() + val extracting by vm.extracting.collectAsStateWithLifecycle() + var showSheet by remember { mutableStateOf(prefillText != null) } + var inputText by remember { mutableStateOf(prefillText ?: "") } + + LaunchedEffect(prefillText) { + if (prefillText != null) showSheet = true + } + + Scaffold( + floatingActionButton = { + FloatingActionButton(onClick = { showSheet = true }) { + Icon(Icons.Default.Add, "Add task manually") + } + }, + ) { padding -> + Column(modifier = Modifier.fillMaxSize().padding(padding).padding(horizontal = 16.dp)) { + Spacer(Modifier.height(16.dp)) + Text("Queue", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) + Spacer(Modifier.height(4.dp)) + Text( + "${queue.size} item${if (queue.size == 1) "" else "s"} awaiting your decision", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + Spacer(Modifier.height(12.dp)) + + if (queue.isEmpty()) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("All clear — nothing in the queue.", color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)) + } + } else { + LazyColumn(contentPadding = PaddingValues(bottom = 80.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + items(queue, key = { it.id }) { item -> + QueueItemCard( + item = item, + onApprove = { vm.approve(item) }, + onDiscard = { vm.discard(item) }, + ) + } + } + } + } + + if (showSheet) { + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + ModalBottomSheet(onDismissRequest = { showSheet = false; inputText = "" }, sheetState = sheetState) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Add item manually", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + Spacer(Modifier.height(12.dp)) + OutlinedTextField( + value = inputText, + onValueChange = { inputText = it }, + modifier = Modifier.fillMaxWidth().height(140.dp), + label = { Text("Paste email, message, or type a task…") }, + maxLines = 6, + ) + Spacer(Modifier.height(12.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + TextButton(onClick = { showSheet = false; inputText = "" }) { Text("Cancel") } + TextButton( + onClick = { + if (inputText.isNotBlank()) { + vm.extractAndQueue(inputText) + showSheet = false + inputText = "" + } + }, + enabled = inputText.isNotBlank() && !extracting, + ) { + if (extracting) CircularProgressIndicator(modifier = Modifier.height(20.dp)) + else Text("Add to Queue") + } + } + } + } + } + } +} + +@Composable +private fun QueueItemCard(item: WorkItem, onApprove: () -> Unit, onDiscard: () -> Unit) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), + elevation = CardDefaults.cardElevation(2.dp), + ) { + Column(modifier = Modifier.padding(12.dp)) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + TypeChip(item.type, modifier = Modifier.weight(1f)) + SourceLabel(item) + } + Spacer(Modifier.height(6.dp)) + Text(item.title, style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Medium) + if (!item.sourceContact.isNullOrBlank()) { + Spacer(Modifier.height(2.dp)) + Text("From: ${item.sourceContact}", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.primary) + } + Spacer(Modifier.height(10.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + IconButton(onClick = onApprove) { + Icon(Icons.Default.Check, "Approve", tint = MaterialTheme.colorScheme.primary) + } + IconButton(onClick = onDiscard) { + Icon(Icons.Default.Close, "Discard", tint = MaterialTheme.colorScheme.error) + } + } + } + } +} + +@Composable +fun TypeChip(type: ItemType, modifier: Modifier = Modifier) { + val (label, color) = when (type) { + ItemType.MY_TASK -> "My Task" to MaterialTheme.colorScheme.primary + ItemType.DELEGATED -> "Delegated" to MaterialTheme.colorScheme.tertiary + ItemType.FOLLOW_UP -> "Follow-up" to MaterialTheme.colorScheme.secondary + ItemType.STATUS_UPDATE -> "Status Update" to MaterialTheme.colorScheme.outline + } + Text( + text = label, + modifier = modifier + .background(color.copy(alpha = 0.12f), shape = MaterialTheme.shapes.small) + .padding(horizontal = 8.dp, vertical = 2.dp), + color = color, + style = MaterialTheme.typography.labelSmall, + ) +} + +@Composable +fun SourceLabel(item: WorkItem) { + val label = when (item.source) { + com.digitaltwin.assistant.data.model.Source.WHATSAPP -> "WhatsApp" + com.digitaltwin.assistant.data.model.Source.SMS -> "SMS" + com.digitaltwin.assistant.data.model.Source.CALL_RECORDING -> "Call" + com.digitaltwin.assistant.data.model.Source.EMAIL_NOTIF -> "Email" + com.digitaltwin.assistant.data.model.Source.MANUAL -> "Manual" + com.digitaltwin.assistant.data.model.Source.OTHER_NOTIF -> "Notif" + } + Text(label, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)) +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ui/screens/SettingsScreen.kt b/app/src/main/java/com/digitaltwin/assistant/ui/screens/SettingsScreen.kt new file mode 100644 index 0000000..2d25bec --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ui/screens/SettingsScreen.kt @@ -0,0 +1,146 @@ +package com.digitaltwin.assistant.ui.screens + +import android.content.Intent +import android.provider.Settings +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.digitaltwin.assistant.ui.viewmodel.SettingsViewModel + +@Composable +fun SettingsScreen(vm: SettingsViewModel = hiltViewModel()) { + val state by vm.state.collectAsStateWithLifecycle() + val context = LocalContext.current + + // Local draft state so we only persist on blur/save, not on every keystroke. + var userName by remember(state.userName) { mutableStateOf(state.userName) } + var groqKey by remember(state.groqApiKey) { mutableStateOf(state.groqApiKey) } + var geminiKey by remember(state.geminiApiKey) { mutableStateOf(state.geminiApiKey) } + var recDir by remember(state.recordingsDir) { mutableStateOf(state.recordingsDir) } + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text("Settings", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) + + // --- Profile --- + SectionTitle("Profile") + OutlinedTextField( + value = userName, + onValueChange = { userName = it }, + label = { Text("Your name (used to detect delegation)") }, + modifier = Modifier.fillMaxWidth().onFocusChanged { if (!it.isFocused) vm.saveUserName(userName) }, + singleLine = true, + ) + + Divider() + + // --- Notification Listener --- + SectionTitle("Notification capture") + Text( + "Digital Twin needs Notification Access to capture tasks from WhatsApp, SMS and email apps.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + Button(onClick = { + context.startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)) + }) { + Text("Open Notification Access settings") + } + + Divider() + + // --- Call recording --- + SectionTitle("Call recording capture") + Text( + "Set the folder your call recorder writes to. Digital Twin will scan it after each call.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + OutlinedTextField( + value = recDir, + onValueChange = { recDir = it }, + label = { Text("Recordings folder path (e.g. /storage/emulated/0/CallRecordings)") }, + modifier = Modifier.fillMaxWidth().onFocusChanged { if (!it.isFocused) vm.saveRecordingsDir(recDir) }, + singleLine = true, + ) + + Divider() + + // --- AI settings --- + SectionTitle("AI & transcription") + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(Modifier.weight(1f)) { + Text("Send text to cloud AI", style = MaterialTheme.typography.bodyMedium) + Text( + "Required for Groq transcription and Gemini extraction. Text is never stored by them for training.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + } + Switch(checked = state.cloudAiEnabled, onCheckedChange = { vm.setCloudAi(it) }) + } + + OutlinedTextField( + value = groqKey, + onValueChange = { groqKey = it }, + label = { Text("Groq API key (free at console.groq.com)") }, + modifier = Modifier.fillMaxWidth().onFocusChanged { if (!it.isFocused) vm.saveGroqKey(groqKey) }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + enabled = state.cloudAiEnabled, + ) + + OutlinedTextField( + value = geminiKey, + onValueChange = { geminiKey = it }, + label = { Text("Gemini API key (Phase 2 — optional now)") }, + modifier = Modifier.fillMaxWidth().onFocusChanged { if (!it.isFocused) vm.saveGeminiKey(geminiKey) }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + enabled = state.cloudAiEnabled, + ) + + Spacer(Modifier.height(32.dp)) + } +} + +@Composable +private fun SectionTitle(text: String) { + Text(text, style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary) +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ui/screens/TodayScreen.kt b/app/src/main/java/com/digitaltwin/assistant/ui/screens/TodayScreen.kt new file mode 100644 index 0000000..e69cb12 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ui/screens/TodayScreen.kt @@ -0,0 +1,105 @@ +package com.digitaltwin.assistant.ui.screens + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Badge +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.digitaltwin.assistant.data.local.entity.WorkItem +import com.digitaltwin.assistant.ui.viewmodel.TodayViewModel +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@Composable +fun TodayScreen(vm: TodayViewModel = hiltViewModel()) { + val items by vm.todayItems.collectAsStateWithLifecycle() + val queueCount by vm.queueCount.collectAsStateWithLifecycle() + + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp)) { + Spacer(Modifier.height(16.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + "Today", + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + ) + if (queueCount > 0) { + Badge(containerColor = MaterialTheme.colorScheme.primary) { + Text("$queueCount in queue", color = MaterialTheme.colorScheme.onPrimary) + } + } + } + Spacer(Modifier.height(4.dp)) + Text( + SimpleDateFormat("EEEE, d MMMM", Locale.getDefault()).format(Date()), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + Spacer(Modifier.height(16.dp)) + + if (items.isEmpty()) { + Text( + "No tasks due today. ✓", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), + ) + } else { + LazyColumn(contentPadding = PaddingValues(bottom = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + items(items) { item -> WorkItemCard(item) } + } + } + } +} + +@Composable +fun WorkItemCard(item: WorkItem, trailingContent: @Composable (() -> Unit)? = null) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant), + ) { + Row( + modifier = Modifier.padding(12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Column(modifier = Modifier.weight(1f)) { + Text(item.title, style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Medium) + if (!item.sourceContact.isNullOrBlank()) { + Spacer(Modifier.height(2.dp)) + Text( + "From: ${item.sourceContact}", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.primary, + ) + } + if (item.dueAt != null) { + Spacer(Modifier.height(2.dp)) + Text( + "Due: ${SimpleDateFormat("d MMM, h:mm a", Locale.getDefault()).format(Date(item.dueAt))}", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + ) + } + } + trailingContent?.invoke() + } + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ui/theme/Theme.kt b/app/src/main/java/com/digitaltwin/assistant/ui/theme/Theme.kt new file mode 100644 index 0000000..85fa305 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ui/theme/Theme.kt @@ -0,0 +1,36 @@ +package com.digitaltwin.assistant.ui.theme + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +private val Primary = Color(0xFF2E5BFF) +private val OnPrimary = Color.White +private val PrimaryContainer = Color(0xFFDDE3FF) +private val Secondary = Color(0xFF5B74FF) +private val Surface = Color(0xFFFFFBFE) +private val OnSurface = Color(0xFF1C1B1F) +private val SurfaceVariant = Color(0xFFE7E0EC) +private val Error = Color(0xFFB3261E) + +private val TwinColors = lightColorScheme( + primary = Primary, + onPrimary = OnPrimary, + primaryContainer = PrimaryContainer, + onPrimaryContainer = Color(0xFF001164), + secondary = Secondary, + onSecondary = OnPrimary, + surface = Surface, + onSurface = OnSurface, + surfaceVariant = SurfaceVariant, + error = Error, +) + +@Composable +fun DigitalTwinTheme(content: @Composable () -> Unit) { + MaterialTheme( + colorScheme = TwinColors, + content = content, + ) +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/ItemsViewModel.kt b/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/ItemsViewModel.kt new file mode 100644 index 0000000..db4aa98 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/ItemsViewModel.kt @@ -0,0 +1,32 @@ +package com.digitaltwin.assistant.ui.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.digitaltwin.assistant.data.local.entity.WorkItem +import com.digitaltwin.assistant.data.model.ItemType +import com.digitaltwin.assistant.data.repository.WorkItemRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class ItemsViewModel @Inject constructor( + private val repo: WorkItemRepository, +) : ViewModel() { + + val myTasks = repo.activeByType(ItemType.MY_TASK) + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + + val delegated = repo.activeByType(ItemType.DELEGATED) + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + + val followUps = repo.activeByType(ItemType.FOLLOW_UP) + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + + val resolved = repo.resolved() + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + + fun resolve(item: WorkItem) = viewModelScope.launch { repo.resolve(item.id) } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/QueueViewModel.kt b/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/QueueViewModel.kt new file mode 100644 index 0000000..5d39bc3 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/QueueViewModel.kt @@ -0,0 +1,68 @@ +package com.digitaltwin.assistant.ui.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.digitaltwin.assistant.ai.ExtractionCandidate +import com.digitaltwin.assistant.ai.ExtractionContext +import com.digitaltwin.assistant.ai.RuleBasedExtractor +import com.digitaltwin.assistant.data.local.entity.WorkItem +import com.digitaltwin.assistant.data.model.ItemStatus +import com.digitaltwin.assistant.data.model.ItemType +import com.digitaltwin.assistant.data.model.Priority +import com.digitaltwin.assistant.data.model.Source +import com.digitaltwin.assistant.data.repository.CaptureRepository +import com.digitaltwin.assistant.data.repository.ContactClassifier +import com.digitaltwin.assistant.data.repository.WorkItemRepository +import com.digitaltwin.assistant.data.repository.toWorkItem +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class QueueViewModel @Inject constructor( + private val workItemRepo: WorkItemRepository, + private val captureRepo: CaptureRepository, + private val extractor: RuleBasedExtractor, + private val classifier: ContactClassifier, +) : ViewModel() { + + val queue = workItemRepo.queue() + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + + private val _extracting = MutableStateFlow(false) + val extracting = _extracting.asStateFlow() + + fun approve(item: WorkItem) = viewModelScope.launch { workItemRepo.approve(item) } + fun discard(item: WorkItem) = viewModelScope.launch { workItemRepo.discard(item) } + + /** Manual capture: extract candidates from pasted/shared text and queue them. */ + fun extractAndQueue(text: String) = viewModelScope.launch { + _extracting.value = true + try { + val ctx = ExtractionContext(source = Source.MANUAL) + val candidates = extractor.extract(text, ctx) + if (candidates.isEmpty()) { + // Nothing matched — queue a MY_TASK with the raw text so user can review it. + val fallback = ExtractionCandidate( + title = text.take(140), + type = ItemType.MY_TASK, + priority = Priority.MEDIUM, + confidence = 0.3, + ) + val cat = classifier.categoryFor(null) + workItemRepo.insert(fallback.toWorkItem(Source.MANUAL, null, cat)) + } else { + candidates.forEach { candidate -> + val cat = classifier.categoryFor(candidate.contact) + workItemRepo.insert(candidate.toWorkItem(Source.MANUAL, candidate.contact, cat)) + } + } + } finally { + _extracting.value = false + } + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/SettingsViewModel.kt new file mode 100644 index 0000000..beba8e4 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/SettingsViewModel.kt @@ -0,0 +1,39 @@ +package com.digitaltwin.assistant.ui.viewmodel + +import androidx.lifecycle.ViewModel +import com.digitaltwin.assistant.data.prefs.SettingsStore +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject + +data class SettingsUiState( + val userName: String = "", + val groqApiKey: String = "", + val geminiApiKey: String = "", + val recordingsDir: String = "", + val cloudAiEnabled: Boolean = false, +) + +@HiltViewModel +class SettingsViewModel @Inject constructor( + private val store: SettingsStore, +) : ViewModel() { + + private val _state = MutableStateFlow( + SettingsUiState( + userName = store.userName ?: "", + groqApiKey = store.groqApiKey ?: "", + geminiApiKey = store.geminiApiKey ?: "", + recordingsDir = store.recordingsDir ?: "", + cloudAiEnabled = store.cloudAiEnabled, + ), + ) + val state = _state.asStateFlow() + + fun saveUserName(v: String) { store.userName = v; _state.value = _state.value.copy(userName = v) } + fun saveGroqKey(v: String) { store.groqApiKey = v; _state.value = _state.value.copy(groqApiKey = v) } + fun saveGeminiKey(v: String) { store.geminiApiKey = v; _state.value = _state.value.copy(geminiApiKey = v) } + fun saveRecordingsDir(v: String) { store.recordingsDir = v; _state.value = _state.value.copy(recordingsDir = v) } + fun setCloudAi(enabled: Boolean) { store.cloudAiEnabled = enabled; _state.value = _state.value.copy(cloudAiEnabled = enabled) } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/TodayViewModel.kt b/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/TodayViewModel.kt new file mode 100644 index 0000000..c3646ea --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/ui/viewmodel/TodayViewModel.kt @@ -0,0 +1,35 @@ +package com.digitaltwin.assistant.ui.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.digitaltwin.assistant.data.local.entity.WorkItem +import com.digitaltwin.assistant.data.repository.WorkItemRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn +import java.util.Calendar +import javax.inject.Inject + +@HiltViewModel +class TodayViewModel @Inject constructor( + repo: WorkItemRepository, +) : ViewModel() { + + private val startOfDay: Long + get() = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0); set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0); set(Calendar.MILLISECOND, 0) + }.timeInMillis + + private val endOfDay: Long + get() = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 23); set(Calendar.MINUTE, 59) + set(Calendar.SECOND, 59); set(Calendar.MILLISECOND, 999) + }.timeInMillis + + val todayItems = repo.dueBetween(startOfDay, endOfDay) + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + + val queueCount = repo.queueCount() + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), 0) +} diff --git a/app/src/main/java/com/digitaltwin/assistant/workers/CallRecordingWorker.kt b/app/src/main/java/com/digitaltwin/assistant/workers/CallRecordingWorker.kt new file mode 100644 index 0000000..c1199d7 --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/workers/CallRecordingWorker.kt @@ -0,0 +1,97 @@ +package com.digitaltwin.assistant.workers + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.digitaltwin.assistant.ai.ExtractionContext +import com.digitaltwin.assistant.ai.RuleBasedExtractor +import com.digitaltwin.assistant.ai.TranscriptionResult +import com.digitaltwin.assistant.ai.Transcriber +import com.digitaltwin.assistant.data.local.entity.CallRecord +import com.digitaltwin.assistant.data.model.Source +import com.digitaltwin.assistant.data.prefs.SettingsStore +import com.digitaltwin.assistant.data.repository.CaptureRepository +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import java.io.File + +/** + * Triggered 60 seconds after a call ends (to let the recorder finish writing). + * 1. Finds the newest audio file in the configured recordings directory. + * 2. Transcribes it via Groq Whisper. + * 3. Runs the rule-based extractor over the transcript. + * 4. Queues any extracted items for user approval. + */ +@HiltWorker +class CallRecordingWorker @AssistedInject constructor( + @Assisted context: Context, + @Assisted params: WorkerParameters, + private val captureRepository: CaptureRepository, + private val transcriber: Transcriber, + private val ruleBasedExtractor: RuleBasedExtractor, + private val settings: SettingsStore, +) : CoroutineWorker(context, params) { + + override suspend fun doWork(): Result { + val phoneNumber = inputData.getString(KEY_PHONE_NUMBER) + val recordingsDir = settings.recordingsDir ?: return Result.success() // Not configured yet. + + val dir = File(recordingsDir) + if (!dir.exists() || !dir.isDirectory) return Result.success() + + // Pick the audio file written most recently (within the last 30 minutes). + val cutoff = System.currentTimeMillis() - 30 * 60 * 1000L + val newest = dir.listFiles() + ?.filter { it.isFile && it.lastModified() > cutoff && isAudio(it) } + ?.maxByOrNull { it.lastModified() } + ?: return Result.success() + + // Guard: don't process the same recording twice. + if (captureRepository.findCallByPath(newest.absolutePath) != null) return Result.success() + + val callRecord = CallRecord( + phoneNumber = phoneNumber, + recordingPath = newest.absolutePath, + ) + val id = captureRepository.storeCallRecord(callRecord) + if (id == -1L) return Result.success() // Duplicate path. + + val storedRecord = callRecord.copy(id = id) + + return when (val result = transcriber.transcribe(newest)) { + is TranscriptionResult.NotConfigured -> { + // Groq API key not set — save the record, user can manually revisit. + Result.success() + } + is TranscriptionResult.Error -> { + // Store transcript as error note; don't retry indefinitely. + captureRepository.updateCallRecord( + storedRecord.copy(transcript = "⚠ Transcription error: ${result.message}"), + ) + Result.success() + } + is TranscriptionResult.Success -> { + val transcript = result.text + val ctx = ExtractionContext( + source = Source.CALL_RECORDING, + contact = storedRecord.contactName, + userName = settings.userName, + ) + val candidates = ruleBasedExtractor.extract(transcript, ctx) + captureRepository.updateCallRecord(storedRecord.copy(transcript = transcript)) + captureRepository.processCallRecord(storedRecord, candidates) + Result.success() + } + } + } + + private fun isAudio(file: File): Boolean { + val ext = file.extension.lowercase() + return ext in setOf("mp3", "m4a", "aac", "ogg", "wav", "amr", "3gp", "opus") + } + + companion object { + const val KEY_PHONE_NUMBER = "phone_number" + } +} diff --git a/app/src/main/java/com/digitaltwin/assistant/workers/NotificationProcessingWorker.kt b/app/src/main/java/com/digitaltwin/assistant/workers/NotificationProcessingWorker.kt new file mode 100644 index 0000000..a17afef --- /dev/null +++ b/app/src/main/java/com/digitaltwin/assistant/workers/NotificationProcessingWorker.kt @@ -0,0 +1,48 @@ +package com.digitaltwin.assistant.workers + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import com.digitaltwin.assistant.data.repository.CaptureRepository +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import java.util.concurrent.TimeUnit + +/** + * Runs every 5 minutes to batch-process notifications stored by [TwinNotificationListenerService]. + * Keeping the processing out of the hot path makes the listener's onNotificationPosted cheap. + */ +@HiltWorker +class NotificationProcessingWorker @AssistedInject constructor( + @Assisted context: Context, + @Assisted params: WorkerParameters, + private val captureRepository: CaptureRepository, +) : CoroutineWorker(context, params) { + + override suspend fun doWork(): Result { + return try { + captureRepository.processUnprocessedNotifications() + Result.success() + } catch (e: Exception) { + Result.retry() + } + } + + companion object { + private const val WORK_NAME = "notification_processing" + + fun enqueueRecurring(workManager: WorkManager) { + val request = PeriodicWorkRequestBuilder(5, TimeUnit.MINUTES) + .build() + workManager.enqueueUniquePeriodicWork( + WORK_NAME, + ExistingPeriodicWorkPolicy.KEEP, + request, + ) + } + } +} diff --git a/app/src/main/res/drawable/card_background.xml b/app/src/main/res/drawable/card_background.xml new file mode 100644 index 0000000..319d57d --- /dev/null +++ b/app/src/main/res/drawable/card_background.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_calendar_add.xml b/app/src/main/res/drawable/ic_calendar_add.xml new file mode 100644 index 0000000..acd88dc --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_add.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_check_circle.xml b/app/src/main/res/drawable/ic_check_circle.xml new file mode 100644 index 0000000..e461126 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_circle.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..05047cb --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/priority_indicator_high.xml b/app/src/main/res/drawable/priority_indicator_high.xml new file mode 100644 index 0000000..ff29e2e --- /dev/null +++ b/app/src/main/res/drawable/priority_indicator_high.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable/priority_indicator_low.xml b/app/src/main/res/drawable/priority_indicator_low.xml new file mode 100644 index 0000000..6d12694 --- /dev/null +++ b/app/src/main/res/drawable/priority_indicator_low.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable/priority_indicator_medium.xml b/app/src/main/res/drawable/priority_indicator_medium.xml new file mode 100644 index 0000000..476b2c3 --- /dev/null +++ b/app/src/main/res/drawable/priority_indicator_medium.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..a8a8fa5 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..a8a8fa5 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..3331acf --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,14 @@ + + + #90CAF9 + #1565C0 + #FFB74D + #EF5350 + #FFA726 + #66BB6A + #FFFFFF + #E0E0E0 + #9E9E9E + #121212 + #1E1E1E + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..d796cff --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #2E5BFF + #FFFBFE + #2E5BFF + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..7f10ff3 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,16 @@ + + Digital Twin + Digital Twin — capture tasks from notifications + + + Today + Queue + Items + Settings + + + Task capture + Shows when new tasks are captured from your messages and calls. + Reminders + Reminders for upcoming and overdue tasks. + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..770a50e --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..da0673a --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..fd2dddb --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..462688e --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/xml/notification_listener_config.xml b/app/src/main/res/xml/notification_listener_config.xml new file mode 100644 index 0000000..b6b584d --- /dev/null +++ b/app/src/main/res/xml/notification_listener_config.xml @@ -0,0 +1,2 @@ + + diff --git a/app/src/test/java/com/digitaltwin/assistant/RuleBasedExtractorTest.kt b/app/src/test/java/com/digitaltwin/assistant/RuleBasedExtractorTest.kt new file mode 100644 index 0000000..a907e42 --- /dev/null +++ b/app/src/test/java/com/digitaltwin/assistant/RuleBasedExtractorTest.kt @@ -0,0 +1,73 @@ +package com.digitaltwin.assistant + +import com.digitaltwin.assistant.ai.ExtractionContext +import com.digitaltwin.assistant.ai.RuleBasedExtractor +import com.digitaltwin.assistant.data.model.ItemType +import com.digitaltwin.assistant.data.model.Priority +import com.digitaltwin.assistant.data.model.Source +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class RuleBasedExtractorTest { + + private val extractor = RuleBasedExtractor() + private val ctx = ExtractionContext(source = Source.WHATSAPP, contact = "Priya") + + @Test + fun `English action request is extracted as MY_TASK`() = runTest { + val results = extractor.extract("Please send the proposal by tomorrow", ctx) + assertTrue(results.isNotEmpty()) + assertEquals(ItemType.MY_TASK, results.first().type) + } + + @Test + fun `Hindi action request is extracted`() = runTest { + val results = extractor.extract("Report bhej do aaj", ctx) + assertTrue(results.isNotEmpty()) + assertEquals(ItemType.MY_TASK, results.first().type) + } + + @Test + fun `Delegation marker produces DELEGATED item`() = runTest { + val results = extractor.extract("Ask Rahul to prepare the deck", ctx) + assertTrue(results.isNotEmpty()) + assertEquals(ItemType.DELEGATED, results.first().type) + } + + @Test + fun `Follow-up marker produces FOLLOW_UP item`() = runTest { + val results = extractor.extract("Waiting for client to revert on budget", ctx) + assertTrue(results.isNotEmpty()) + assertEquals(ItemType.FOLLOW_UP, results.first().type) + } + + @Test + fun `Status completion produces STATUS_UPDATE`() = runTest { + val results = extractor.extract("ho gaya", ctx) + assertTrue(results.isNotEmpty()) + assertEquals(ItemType.STATUS_UPDATE, results.first().type) + } + + @Test + fun `Urgent keyword sets HIGH priority`() = runTest { + val results = extractor.extract("urgent: please review the contract asap", ctx) + assertTrue(results.isNotEmpty()) + assertEquals(Priority.HIGH, results.first().priority) + } + + @Test + fun `Unactionable text returns empty`() = runTest { + val results = extractor.extract("ok thanks", ctx) + assertTrue(results.isEmpty()) + } + + @Test + fun `Tomorrow due date is set`() = runTest { + val results = extractor.extract("please send the file tomorrow", ctx) + assertTrue(results.isNotEmpty()) + assertFalse(results.first().dueAt == null) + } +} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..0a066db --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.ksp) apply false + alias(libs.plugins.hilt) apply false +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..32af67f --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.caching=true +org.gradle.configuration-cache=false +android.useAndroidX=true +android.nonTransitiveRClass=true +kotlin.code.style=official diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..0ca6890 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,70 @@ +[versions] +agp = "8.6.1" +kotlin = "2.0.21" +ksp = "2.0.21-1.0.25" +coreKtx = "1.13.1" +lifecycle = "2.8.6" +activityCompose = "1.9.2" +composeBom = "2024.09.03" +navigationCompose = "2.8.2" +hilt = "2.52" +hiltNavigationCompose = "1.2.0" +room = "2.6.1" +workManager = "2.9.1" +hiltWork = "1.2.0" +retrofit = "2.11.0" +okhttp = "4.12.0" +moshi = "1.15.1" +coroutines = "1.8.1" +datastore = "1.1.1" +junit = "4.13.2" +androidxJunit = "1.2.1" +espresso = "3.6.1" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } +androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } +androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } + +hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } +hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } +hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" } +hilt-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltWork" } +hilt-work-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hiltWork" } + +room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } +room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } + +work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workManager" } + +retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } +retrofit-moshi = { group = "com.squareup.retrofit2", name = "converter-moshi", version.ref = "retrofit" } +okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } +okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } +moshi-kotlin = { group = "com.squareup.moshi", name = "moshi-kotlin", version.ref = "moshi" } + +kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } +datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" } + +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" } +kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..a4b76b9 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..df97d72 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..d95bf61 --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..640d686 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/next.config.js b/next.config.js deleted file mode 100644 index 767719f..0000000 --- a/next.config.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = {} - -module.exports = nextConfig diff --git a/package.json b/package.json deleted file mode 100644 index 4a1f182..0000000 --- a/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "chatterbot", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@types/node": "20.3.1", - "@types/react": "18.2.13", - "@types/react-dom": "18.2.6", - "autoprefixer": "10.4.14", - "dotenv": "^16.3.1", - "eslint": "8.43.0", - "eslint-config-next": "13.4.6", - "next": "13.4.6", - "openai": "^3.3.0", - "postcss": "8.4.24", - "react": "18.2.0", - "react-dom": "18.2.0", - "tailwindcss": "3.3.2", - "typescript": "5.1.3" - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 99f3c46..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,2728 +0,0 @@ -lockfileVersion: '6.0' - -dependencies: - '@types/node': - specifier: 20.3.1 - version: 20.3.1 - '@types/react': - specifier: 18.2.13 - version: 18.2.13 - '@types/react-dom': - specifier: 18.2.6 - version: 18.2.6 - autoprefixer: - specifier: 10.4.14 - version: 10.4.14(postcss@8.4.24) - dotenv: - specifier: ^16.3.1 - version: 16.3.1 - eslint: - specifier: 8.43.0 - version: 8.43.0 - eslint-config-next: - specifier: 13.4.6 - version: 13.4.6(eslint@8.43.0)(typescript@5.1.3) - next: - specifier: 13.4.6 - version: 13.4.6(react-dom@18.2.0)(react@18.2.0) - openai: - specifier: ^3.3.0 - version: 3.3.0 - postcss: - specifier: 8.4.24 - version: 8.4.24 - react: - specifier: 18.2.0 - version: 18.2.0 - react-dom: - specifier: 18.2.0 - version: 18.2.0(react@18.2.0) - tailwindcss: - specifier: 3.3.2 - version: 3.3.2 - typescript: - specifier: 5.1.3 - version: 5.1.3 - -packages: - - /@alloc/quick-lru@5.2.0: - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - dev: false - - /@babel/runtime@7.22.5: - resolution: {integrity: sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.13.11 - dev: false - - /@eslint-community/eslint-utils@4.4.0(eslint@8.43.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.43.0 - eslint-visitor-keys: 3.4.1 - dev: false - - /@eslint-community/regexpp@4.5.1: - resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: false - - /@eslint/eslintrc@2.0.3: - resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.5.2 - globals: 13.20.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: false - - /@eslint/js@8.43.0: - resolution: {integrity: sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: false - - /@humanwhocodes/config-array@0.11.10: - resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: false - - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: false - - /@humanwhocodes/object-schema@1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - dev: false - - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.18 - dev: false - - /@jridgewell/resolve-uri@3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} - engines: {node: '>=6.0.0'} - dev: false - - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - dev: false - - /@jridgewell/sourcemap-codec@1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - dev: false - - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: false - - /@jridgewell/trace-mapping@0.3.18: - resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - dev: false - - /@next/env@13.4.6: - resolution: {integrity: sha512-nqUxEtvDqFhmV1/awSg0K2XHNwkftNaiUqCYO9e6+MYmqNObpKVl7OgMkGaQ2SZnFx5YqF0t60ZJTlyJIDAijg==} - dev: false - - /@next/eslint-plugin-next@13.4.6: - resolution: {integrity: sha512-bPigeu0RI7bgy1ucBA2Yqcfg539y0Lzo38P2hIkrRB1GNvFSbYg6RTu8n6tGqPVrH3TTlPTNKLXG01wc+5NuwQ==} - dependencies: - glob: 7.1.7 - dev: false - - /@next/swc-darwin-arm64@13.4.6: - resolution: {integrity: sha512-ahi6VP98o4HV19rkOXPSUu+ovfHfUxbJQ7VVJ7gL2FnZRr7onEFC1oGQ6NQHpm8CxpIzSSBW79kumlFMOmZVjg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@next/swc-darwin-x64@13.4.6: - resolution: {integrity: sha512-13cXxKFsPJIJKzUqrU5XB1mc0xbUgYsRcdH6/rB8c4NMEbWGdtD4QoK9ShN31TZdePpD4k416Ur7p+deMIxnnA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-arm64-gnu@13.4.6: - resolution: {integrity: sha512-Ti+NMHEjTNktCVxNjeWbYgmZvA2AqMMI2AMlzkXsU7W4pXCMhrryAmAIoo+7YdJbsx01JQWYVxGe62G6DoCLaA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-arm64-musl@13.4.6: - resolution: {integrity: sha512-OHoC6gO7XfjstgwR+z6UHKlvhqJfyMtNaJidjx3sEcfaDwS7R2lqR5AABi8PuilGgi0BO0O0sCXqLlpp3a0emQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-x64-gnu@13.4.6: - resolution: {integrity: sha512-zHZxPGkUlpfNJCboUrFqwlwEX5vI9LSN70b8XEb0DYzzlrZyCyOi7hwDp/+3Urm9AB7YCAJkgR5Sp1XBVjHdfQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-x64-musl@13.4.6: - resolution: {integrity: sha512-K/Y8lYGTwTpv5ME8PSJxwxLolaDRdVy+lOd9yMRMiQE0BLUhtxtCWC9ypV42uh9WpLjoaD0joOsB9Q6mbrSGJg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-win32-arm64-msvc@13.4.6: - resolution: {integrity: sha512-U6LtxEUrjBL2tpW+Kr1nHCSJWNeIed7U7l5o7FiKGGwGgIlFi4UHDiLI6TQ2lxi20fAU33CsruV3U0GuzMlXIw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@next/swc-win32-ia32-msvc@13.4.6: - resolution: {integrity: sha512-eEBeAqpCfhdPSlCZCayjCiyIllVqy4tcqvm1xmg3BgJG0G5ITiMM4Cw2WVeRSgWDJqQGRyyb+q8Y2ltzhXOWsQ==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@next/swc-win32-x64-msvc@13.4.6: - resolution: {integrity: sha512-OrZs94AuO3ZS5tnqlyPRNgfWvboXaDQCi5aXGve3o3C+Sj0ctMUV9+Do+0zMvvLRumR8E0PTWKvtz9n5vzIsWw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: false - - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - dev: false - - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 - dev: false - - /@pkgr/utils@2.4.1: - resolution: {integrity: sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - dependencies: - cross-spawn: 7.0.3 - fast-glob: 3.2.12 - is-glob: 4.0.3 - open: 9.1.0 - picocolors: 1.0.0 - tslib: 2.5.3 - dev: false - - /@rushstack/eslint-patch@1.3.2: - resolution: {integrity: sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==} - dev: false - - /@swc/helpers@0.5.1: - resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==} - dependencies: - tslib: 2.5.3 - dev: false - - /@types/json5@0.0.29: - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - dev: false - - /@types/node@20.3.1: - resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} - dev: false - - /@types/prop-types@15.7.5: - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - dev: false - - /@types/react-dom@18.2.6: - resolution: {integrity: sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A==} - dependencies: - '@types/react': 18.2.13 - dev: false - - /@types/react@18.2.13: - resolution: {integrity: sha512-vJ+zElvi/Zn9cVXB5slX2xL8PZodPCwPRDpittQdw43JR2AJ5k3vKdgJJyneV/cYgIbLQUwXa9JVDvUZXGba+Q==} - dependencies: - '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.3 - csstype: 3.1.2 - dev: false - - /@types/scheduler@0.16.3: - resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} - dev: false - - /@typescript-eslint/parser@5.60.0(eslint@8.43.0)(typescript@5.1.3): - resolution: {integrity: sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 5.60.0 - '@typescript-eslint/types': 5.60.0 - '@typescript-eslint/typescript-estree': 5.60.0(typescript@5.1.3) - debug: 4.3.4 - eslint: 8.43.0 - typescript: 5.1.3 - transitivePeerDependencies: - - supports-color - dev: false - - /@typescript-eslint/scope-manager@5.60.0: - resolution: {integrity: sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - '@typescript-eslint/types': 5.60.0 - '@typescript-eslint/visitor-keys': 5.60.0 - dev: false - - /@typescript-eslint/types@5.60.0: - resolution: {integrity: sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: false - - /@typescript-eslint/typescript-estree@5.60.0(typescript@5.1.3): - resolution: {integrity: sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 5.60.0 - '@typescript-eslint/visitor-keys': 5.60.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.5.2 - tsutils: 3.21.0(typescript@5.1.3) - typescript: 5.1.3 - transitivePeerDependencies: - - supports-color - dev: false - - /@typescript-eslint/visitor-keys@5.60.0: - resolution: {integrity: sha512-wm9Uz71SbCyhUKgcaPRauBdTegUyY/ZWl8gLwD/i/ybJqscrrdVSFImpvUz16BLPChIeKBK5Fa9s6KDQjsjyWw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - '@typescript-eslint/types': 5.60.0 - eslint-visitor-keys: 3.4.1 - dev: false - - /acorn-jsx@5.3.2(acorn@8.9.0): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.9.0 - dev: false - - /acorn@8.9.0: - resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: false - - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: false - - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - dev: false - - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: false - - /any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: false - - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: false - - /arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: false - - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: false - - /aria-query@5.2.1: - resolution: {integrity: sha512-7uFg4b+lETFgdaJyETnILsXgnnzVnkHcgRbwbPwevm5x/LmUlt3MjczMRe1zg824iBgXZNRPTBftNYyRSKLp2g==} - dependencies: - dequal: 2.0.3 - dev: false - - /array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} - dependencies: - call-bind: 1.0.2 - is-array-buffer: 3.0.2 - dev: false - - /array-includes@3.1.6: - resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - get-intrinsic: 1.2.1 - is-string: 1.0.7 - dev: false - - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: false - - /array.prototype.flat@1.3.1: - resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - es-shim-unscopables: 1.0.0 - dev: false - - /array.prototype.flatmap@1.3.1: - resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - es-shim-unscopables: 1.0.0 - dev: false - - /array.prototype.tosorted@1.1.1: - resolution: {integrity: sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - es-shim-unscopables: 1.0.0 - get-intrinsic: 1.2.1 - dev: false - - /ast-types-flow@0.0.7: - resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} - dev: false - - /asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: false - - /autoprefixer@10.4.14(postcss@8.4.24): - resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.21.9 - caniuse-lite: 1.0.30001505 - fraction.js: 4.2.0 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: false - - /available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} - engines: {node: '>= 0.4'} - dev: false - - /axe-core@4.7.2: - resolution: {integrity: sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==} - engines: {node: '>=4'} - dev: false - - /axios@0.26.1: - resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} - dependencies: - follow-redirects: 1.15.2 - transitivePeerDependencies: - - debug - dev: false - - /axobject-query@3.2.1: - resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} - dependencies: - dequal: 2.0.3 - dev: false - - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: false - - /big-integer@1.6.51: - resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} - engines: {node: '>=0.6'} - dev: false - - /binary-extensions@2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} - dev: false - - /bplist-parser@0.2.0: - resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} - engines: {node: '>= 5.10.0'} - dependencies: - big-integer: 1.6.51 - dev: false - - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: false - - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: false - - /browserslist@4.21.9: - resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001505 - electron-to-chromium: 1.4.435 - node-releases: 2.0.12 - update-browserslist-db: 1.0.11(browserslist@4.21.9) - dev: false - - /bundle-name@3.0.0: - resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} - engines: {node: '>=12'} - dependencies: - run-applescript: 5.0.0 - dev: false - - /busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - dependencies: - streamsearch: 1.1.0 - dev: false - - /call-bind@1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} - dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.2.1 - dev: false - - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: false - - /camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} - dev: false - - /caniuse-lite@1.0.30001505: - resolution: {integrity: sha512-jaAOR5zVtxHfL0NjZyflVTtXm3D3J9P15zSJ7HmQF8dSKGA6tqzQq+0ZI3xkjyQj46I4/M0K2GbMpcAFOcbr3A==} - dev: false - - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: false - - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - dev: false - - /client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - dev: false - - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: false - - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: false - - /combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - dependencies: - delayed-stream: 1.0.0 - dev: false - - /commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - dev: false - - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: false - - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: false - - /cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - dev: false - - /csstype@3.1.2: - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - dev: false - - /damerau-levenshtein@1.0.8: - resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - dev: false - - /debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.3 - dev: false - - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: false - - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: false - - /default-browser-id@3.0.0: - resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} - engines: {node: '>=12'} - dependencies: - bplist-parser: 0.2.0 - untildify: 4.0.0 - dev: false - - /default-browser@4.0.0: - resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} - engines: {node: '>=14.16'} - dependencies: - bundle-name: 3.0.0 - default-browser-id: 3.0.0 - execa: 7.1.1 - titleize: 3.0.0 - dev: false - - /define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - dev: false - - /define-properties@1.2.0: - resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} - engines: {node: '>= 0.4'} - dependencies: - has-property-descriptors: 1.0.0 - object-keys: 1.1.1 - dev: false - - /delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - dev: false - - /dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - dev: false - - /didyoumean@1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dev: false - - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dependencies: - path-type: 4.0.0 - dev: false - - /dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dev: false - - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - dependencies: - esutils: 2.0.3 - dev: false - - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: false - - /dotenv@16.3.1: - resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} - engines: {node: '>=12'} - dev: false - - /electron-to-chromium@1.4.435: - resolution: {integrity: sha512-B0CBWVFhvoQCW/XtjRzgrmqcgVWg6RXOEM/dK59+wFV93BFGR6AeNKc4OyhM+T3IhJaOOG8o/V+33Y2mwJWtzw==} - dev: false - - /emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: false - - /enhanced-resolve@5.15.0: - resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} - engines: {node: '>=10.13.0'} - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - dev: false - - /es-abstract@1.21.2: - resolution: {integrity: sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==} - engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.0 - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - es-set-tostringtag: 2.0.1 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.5 - get-intrinsic: 1.2.1 - get-symbol-description: 1.0.0 - globalthis: 1.0.3 - gopd: 1.0.1 - has: 1.0.3 - has-property-descriptors: 1.0.0 - has-proto: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.5 - is-array-buffer: 3.0.2 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-typed-array: 1.1.10 - is-weakref: 1.0.2 - object-inspect: 1.12.3 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 - safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.7 - string.prototype.trimend: 1.0.6 - string.prototype.trimstart: 1.0.6 - typed-array-length: 1.0.4 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.9 - dev: false - - /es-set-tostringtag@2.0.1: - resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.1 - has: 1.0.3 - has-tostringtag: 1.0.0 - dev: false - - /es-shim-unscopables@1.0.0: - resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} - dependencies: - has: 1.0.3 - dev: false - - /es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} - dependencies: - is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - dev: false - - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} - dev: false - - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: false - - /eslint-config-next@13.4.6(eslint@8.43.0)(typescript@5.1.3): - resolution: {integrity: sha512-nlv4FYish1RYYHILbQwM5/rD37cOvEqtMfDjtQCYbXdE2O3MggqHu2q6IDeLE2Z6u8ZJyNPgWOA6OimWcxj3qw==} - peerDependencies: - eslint: ^7.23.0 || ^8.0.0 - typescript: '>=3.3.1' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@next/eslint-plugin-next': 13.4.6 - '@rushstack/eslint-patch': 1.3.2 - '@typescript-eslint/parser': 5.60.0(eslint@8.43.0)(typescript@5.1.3) - eslint: 8.43.0 - eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.60.0)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.27.5)(eslint@8.43.0) - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.60.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.43.0) - eslint-plugin-jsx-a11y: 6.7.1(eslint@8.43.0) - eslint-plugin-react: 7.32.2(eslint@8.43.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.43.0) - typescript: 5.1.3 - transitivePeerDependencies: - - eslint-import-resolver-webpack - - supports-color - dev: false - - /eslint-import-resolver-node@0.3.7: - resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} - dependencies: - debug: 3.2.7 - is-core-module: 2.12.1 - resolve: 1.22.2 - transitivePeerDependencies: - - supports-color - dev: false - - /eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.60.0)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.27.5)(eslint@8.43.0): - resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - dependencies: - debug: 4.3.4 - enhanced-resolve: 5.15.0 - eslint: 8.43.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.60.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.43.0) - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.60.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.43.0) - get-tsconfig: 4.6.0 - globby: 13.2.0 - is-core-module: 2.12.1 - is-glob: 4.0.3 - synckit: 0.8.5 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - dev: false - - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.60.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.43.0): - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - dependencies: - '@typescript-eslint/parser': 5.60.0(eslint@8.43.0)(typescript@5.1.3) - debug: 3.2.7 - eslint: 8.43.0 - eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.60.0)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.27.5)(eslint@8.43.0) - transitivePeerDependencies: - - supports-color - dev: false - - /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.60.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.43.0): - resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - dependencies: - '@typescript-eslint/parser': 5.60.0(eslint@8.43.0)(typescript@5.1.3) - array-includes: 3.1.6 - array.prototype.flat: 1.3.1 - array.prototype.flatmap: 1.3.1 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.43.0 - eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.60.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.43.0) - has: 1.0.3 - is-core-module: 2.12.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.values: 1.1.6 - resolve: 1.22.2 - semver: 6.3.0 - tsconfig-paths: 3.14.2 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: false - - /eslint-plugin-jsx-a11y@6.7.1(eslint@8.43.0): - resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - dependencies: - '@babel/runtime': 7.22.5 - aria-query: 5.2.1 - array-includes: 3.1.6 - array.prototype.flatmap: 1.3.1 - ast-types-flow: 0.0.7 - axe-core: 4.7.2 - axobject-query: 3.2.1 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - eslint: 8.43.0 - has: 1.0.3 - jsx-ast-utils: 3.3.3 - language-tags: 1.0.5 - minimatch: 3.1.2 - object.entries: 1.1.6 - object.fromentries: 2.0.6 - semver: 6.3.0 - dev: false - - /eslint-plugin-react-hooks@4.6.0(eslint@8.43.0): - resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - dependencies: - eslint: 8.43.0 - dev: false - - /eslint-plugin-react@7.32.2(eslint@8.43.0): - resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - dependencies: - array-includes: 3.1.6 - array.prototype.flatmap: 1.3.1 - array.prototype.tosorted: 1.1.1 - doctrine: 2.1.0 - eslint: 8.43.0 - estraverse: 5.3.0 - jsx-ast-utils: 3.3.3 - minimatch: 3.1.2 - object.entries: 1.1.6 - object.fromentries: 2.0.6 - object.hasown: 1.1.2 - object.values: 1.1.6 - prop-types: 15.8.1 - resolve: 2.0.0-next.4 - semver: 6.3.0 - string.prototype.matchall: 4.0.8 - dev: false - - /eslint-scope@7.2.0: - resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: false - - /eslint-visitor-keys@3.4.1: - resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: false - - /eslint@8.43.0: - resolution: {integrity: sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.43.0) - '@eslint-community/regexpp': 4.5.1 - '@eslint/eslintrc': 2.0.3 - '@eslint/js': 8.43.0 - '@humanwhocodes/config-array': 0.11.10 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.0 - eslint-visitor-keys: 3.4.1 - espree: 9.5.2 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.20.0 - graphemer: 1.4.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.1 - strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: false - - /espree@9.5.2: - resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.9.0 - acorn-jsx: 5.3.2(acorn@8.9.0) - eslint-visitor-keys: 3.4.1 - dev: false - - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: false - - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: false - - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: false - - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: false - - /execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - dev: false - - /execa@7.1.1: - resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==} - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 4.3.1 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.1.0 - onetime: 6.0.0 - signal-exit: 3.0.7 - strip-final-newline: 3.0.0 - dev: false - - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: false - - /fast-glob@3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - dev: false - - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: false - - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: false - - /fastq@1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} - dependencies: - reusify: 1.0.4 - dev: false - - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.0.4 - dev: false - - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: false - - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: false - - /flat-cache@3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flatted: 3.2.7 - rimraf: 3.0.2 - dev: false - - /flatted@3.2.7: - resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} - dev: false - - /follow-redirects@1.15.2: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dev: false - - /for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - dependencies: - is-callable: 1.2.7 - dev: false - - /form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - dev: false - - /fraction.js@4.2.0: - resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} - dev: false - - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: false - - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: false - - /function.prototype.name@1.1.5: - resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - functions-have-names: 1.2.3 - dev: false - - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: false - - /get-intrinsic@1.2.1: - resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} - dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-proto: 1.0.1 - has-symbols: 1.0.3 - dev: false - - /get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - dev: false - - /get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - dev: false - - /get-tsconfig@4.6.0: - resolution: {integrity: sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg==} - dependencies: - resolve-pkg-maps: 1.0.0 - dev: false - - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: false - - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - dev: false - - /glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - dev: false - - /glob@7.1.6: - resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: false - - /glob@7.1.7: - resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: false - - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: false - - /globals@13.20.0: - resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: false - - /globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.2.0 - dev: false - - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.2.12 - ignore: 5.2.4 - merge2: 1.4.1 - slash: 3.0.0 - dev: false - - /globby@13.2.0: - resolution: {integrity: sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - dir-glob: 3.0.1 - fast-glob: 3.2.12 - ignore: 5.2.4 - merge2: 1.4.1 - slash: 4.0.0 - dev: false - - /gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - dependencies: - get-intrinsic: 1.2.1 - dev: false - - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: false - - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: false - - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: false - - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - dev: false - - /has-property-descriptors@1.0.0: - resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} - dependencies: - get-intrinsic: 1.2.1 - dev: false - - /has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} - engines: {node: '>= 0.4'} - dev: false - - /has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - dev: false - - /has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: false - - /has@1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - dev: false - - /human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - dev: false - - /human-signals@4.3.1: - resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} - engines: {node: '>=14.18.0'} - dev: false - - /ignore@5.2.4: - resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} - engines: {node: '>= 4'} - dev: false - - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: false - - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: false - - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: false - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: false - - /internal-slot@1.0.5: - resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.1 - has: 1.0.3 - side-channel: 1.0.4 - dev: false - - /is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - is-typed-array: 1.1.10 - dev: false - - /is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - dependencies: - has-bigints: 1.0.2 - dev: false - - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - dev: false - - /is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: false - - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - dev: false - - /is-core-module@2.12.1: - resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} - dependencies: - has: 1.0.3 - dev: false - - /is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: false - - /is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - dev: false - - /is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - dev: false - - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: false - - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: false - - /is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - dependencies: - is-docker: 3.0.0 - dev: false - - /is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} - engines: {node: '>= 0.4'} - dev: false - - /is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: false - - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: false - - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: false - - /is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: false - - /is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} - dependencies: - call-bind: 1.0.2 - dev: false - - /is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - dev: false - - /is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: false - - /is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: false - - /is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: false - - /is-typed-array@1.1.10: - resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 - dev: false - - /is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - dependencies: - call-bind: 1.0.2 - dev: false - - /is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - dependencies: - is-docker: 2.2.1 - dev: false - - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: false - - /jiti@1.18.2: - resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} - hasBin: true - dev: false - - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: false - - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: false - - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: false - - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: false - - /json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - dependencies: - minimist: 1.2.8 - dev: false - - /jsx-ast-utils@3.3.3: - resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} - engines: {node: '>=4.0'} - dependencies: - array-includes: 3.1.6 - object.assign: 4.1.4 - dev: false - - /language-subtag-registry@0.3.22: - resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} - dev: false - - /language-tags@1.0.5: - resolution: {integrity: sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==} - dependencies: - language-subtag-registry: 0.3.22 - dev: false - - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: false - - /lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - dev: false - - /lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: false - - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - dev: false - - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: false - - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - dependencies: - js-tokens: 4.0.0 - dev: false - - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - dev: false - - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: false - - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - dev: false - - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - dev: false - - /mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - dev: false - - /mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 - dev: false - - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - dev: false - - /mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - dev: false - - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: false - - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: false - - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: false - - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: false - - /mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - dev: false - - /nanoid@3.3.6: - resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: false - - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: false - - /next@13.4.6(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-sjVqjxU+U2aXZnYt4Ud6CTLNNwWjdSfMgemGpIQJcN3Z7Jni9xRWbR0ie5fQzCg87aLqQVhKA2ud2gPoqJ9lGw==} - engines: {node: '>=16.8.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - fibers: '>= 3.1.0' - react: ^18.2.0 - react-dom: ^18.2.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - fibers: - optional: true - sass: - optional: true - dependencies: - '@next/env': 13.4.6 - '@swc/helpers': 0.5.1 - busboy: 1.6.0 - caniuse-lite: 1.0.30001505 - postcss: 8.4.14 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.1(react@18.2.0) - watchpack: 2.4.0 - zod: 3.21.4 - optionalDependencies: - '@next/swc-darwin-arm64': 13.4.6 - '@next/swc-darwin-x64': 13.4.6 - '@next/swc-linux-arm64-gnu': 13.4.6 - '@next/swc-linux-arm64-musl': 13.4.6 - '@next/swc-linux-x64-gnu': 13.4.6 - '@next/swc-linux-x64-musl': 13.4.6 - '@next/swc-win32-arm64-msvc': 13.4.6 - '@next/swc-win32-ia32-msvc': 13.4.6 - '@next/swc-win32-x64-msvc': 13.4.6 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: false - - /node-releases@2.0.12: - resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==} - dev: false - - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: false - - /normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - dev: false - - /npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - dependencies: - path-key: 3.1.1 - dev: false - - /npm-run-path@5.1.0: - resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - path-key: 4.0.0 - dev: false - - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - dev: false - - /object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - dev: false - - /object-inspect@1.12.3: - resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - dev: false - - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - dev: false - - /object.assign@4.1.4: - resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - has-symbols: 1.0.3 - object-keys: 1.1.1 - dev: false - - /object.entries@1.1.6: - resolution: {integrity: sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: false - - /object.fromentries@2.0.6: - resolution: {integrity: sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: false - - /object.hasown@1.1.2: - resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==} - dependencies: - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: false - - /object.values@1.1.6: - resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: false - - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: false - - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - dependencies: - mimic-fn: 2.1.0 - dev: false - - /onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - dependencies: - mimic-fn: 4.0.0 - dev: false - - /open@9.1.0: - resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} - engines: {node: '>=14.16'} - dependencies: - default-browser: 4.0.0 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - is-wsl: 2.2.0 - dev: false - - /openai@3.3.0: - resolution: {integrity: sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==} - dependencies: - axios: 0.26.1 - form-data: 4.0.0 - transitivePeerDependencies: - - debug - dev: false - - /optionator@0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} - engines: {node: '>= 0.8.0'} - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.3 - dev: false - - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - dev: false - - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - dev: false - - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: false - - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: false - - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: false - - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: false - - /path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - dev: false - - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: false - - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: false - - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: false - - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: false - - /pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - dev: false - - /pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - dev: false - - /postcss-import@15.1.0(postcss@8.4.24): - resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} - engines: {node: '>=14.0.0'} - peerDependencies: - postcss: ^8.0.0 - dependencies: - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.2 - dev: false - - /postcss-js@4.0.1(postcss@8.4.24): - resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - dependencies: - camelcase-css: 2.0.1 - postcss: 8.4.24 - dev: false - - /postcss-load-config@4.0.1(postcss@8.4.24): - resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 2.1.0 - postcss: 8.4.24 - yaml: 2.3.1 - dev: false - - /postcss-nested@6.0.1(postcss@8.4.24): - resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - dependencies: - postcss: 8.4.24 - postcss-selector-parser: 6.0.13 - dev: false - - /postcss-selector-parser@6.0.13: - resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} - engines: {node: '>=4'} - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - dev: false - - /postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: false - - /postcss@8.4.14: - resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: false - - /postcss@8.4.24: - resolution: {integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: false - - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: false - - /prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - dev: false - - /punycode@2.3.0: - resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} - engines: {node: '>=6'} - dev: false - - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: false - - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} - peerDependencies: - react: ^18.2.0 - dependencies: - loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 - dev: false - - /react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - dev: false - - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - dev: false - - /read-cache@1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - dependencies: - pify: 2.3.0 - dev: false - - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - dev: false - - /regenerator-runtime@0.13.11: - resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - dev: false - - /regexp.prototype.flags@1.5.0: - resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - functions-have-names: 1.2.3 - dev: false - - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: false - - /resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - dev: false - - /resolve@1.22.2: - resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} - hasBin: true - dependencies: - is-core-module: 2.12.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: false - - /resolve@2.0.0-next.4: - resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} - hasBin: true - dependencies: - is-core-module: 2.12.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: false - - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: false - - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: false - - /run-applescript@5.0.0: - resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} - engines: {node: '>=12'} - dependencies: - execa: 5.1.1 - dev: false - - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: false - - /safe-regex-test@1.0.0: - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - is-regex: 1.1.4 - dev: false - - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} - dependencies: - loose-envify: 1.4.0 - dev: false - - /semver@6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - dev: false - - /semver@7.5.2: - resolution: {integrity: sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: false - - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: false - - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: false - - /side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - object-inspect: 1.12.3 - dev: false - - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: false - - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: false - - /slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} - dev: false - - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - dev: false - - /streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - dev: false - - /string.prototype.matchall@4.0.8: - resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - get-intrinsic: 1.2.1 - has-symbols: 1.0.3 - internal-slot: 1.0.5 - regexp.prototype.flags: 1.5.0 - side-channel: 1.0.4 - dev: false - - /string.prototype.trim@1.2.7: - resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: false - - /string.prototype.trimend@1.0.6: - resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: false - - /string.prototype.trimstart@1.0.6: - resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: false - - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - dev: false - - /strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: false - - /strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - dev: false - - /strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - dev: false - - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: false - - /styled-jsx@5.1.1(react@18.2.0): - resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - dependencies: - client-only: 0.0.1 - react: 18.2.0 - dev: false - - /sucrase@3.32.0: - resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==} - engines: {node: '>=8'} - hasBin: true - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - commander: 4.1.1 - glob: 7.1.6 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - dev: false - - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - dev: false - - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: false - - /synckit@0.8.5: - resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==} - engines: {node: ^14.18.0 || >=16.0.0} - dependencies: - '@pkgr/utils': 2.4.1 - tslib: 2.5.3 - dev: false - - /tailwindcss@3.3.2: - resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.5.3 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.2.12 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.18.2 - lilconfig: 2.1.0 - micromatch: 4.0.5 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.0.0 - postcss: 8.4.24 - postcss-import: 15.1.0(postcss@8.4.24) - postcss-js: 4.0.1(postcss@8.4.24) - postcss-load-config: 4.0.1(postcss@8.4.24) - postcss-nested: 6.0.1(postcss@8.4.24) - postcss-selector-parser: 6.0.13 - postcss-value-parser: 4.2.0 - resolve: 1.22.2 - sucrase: 3.32.0 - transitivePeerDependencies: - - ts-node - dev: false - - /tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} - dev: false - - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: false - - /thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - dependencies: - thenify: 3.3.1 - dev: false - - /thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - dependencies: - any-promise: 1.3.0 - dev: false - - /titleize@3.0.0: - resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} - engines: {node: '>=12'} - dev: false - - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: false - - /ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: false - - /tsconfig-paths@3.14.2: - resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - dev: false - - /tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: false - - /tslib@2.5.3: - resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} - dev: false - - /tsutils@3.21.0(typescript@5.1.3): - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - dependencies: - tslib: 1.14.1 - typescript: 5.1.3 - dev: false - - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: false - - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: false - - /typed-array-length@1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} - dependencies: - call-bind: 1.0.2 - for-each: 0.3.3 - is-typed-array: 1.1.10 - dev: false - - /typescript@5.1.3: - resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} - engines: {node: '>=14.17'} - hasBin: true - dev: false - - /unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - dependencies: - call-bind: 1.0.2 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - dev: false - - /untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} - dev: false - - /update-browserslist-db@1.0.11(browserslist@4.21.9): - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.9 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: false - - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.3.0 - dev: false - - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: false - - /watchpack@2.4.0: - resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} - engines: {node: '>=10.13.0'} - dependencies: - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - dev: false - - /which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 - dev: false - - /which-typed-array@1.1.9: - resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 - is-typed-array: 1.1.10 - dev: false - - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: false - - /word-wrap@1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} - engines: {node: '>=0.10.0'} - dev: false - - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: false - - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: false - - /yaml@2.3.1: - resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} - engines: {node: '>= 14'} - dev: false - - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: false - - /zod@3.21.4: - resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} - dev: false diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 33ad091..0000000 --- a/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index d2f8422..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..90f794f --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,24 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "DigitalTwin" +include(":app") diff --git a/src/app/api/route.ts b/src/app/api/route.ts deleted file mode 100644 index 62ecdfa..0000000 --- a/src/app/api/route.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { NextResponse } from 'next/server'; -import { Configuration, OpenAIApi } from 'openai'; - -const configuration = new Configuration({ - apiKey: process.env.OPENAI_API_KEY, -}); - -const openai = new OpenAIApi(configuration); - -export async function POST(req: Request, res: NextResponse) { - const body = await req.json() - - const completion = await openai.createChatCompletion({ - model: "gpt-3.5-turbo", - messages: body.messages, - }); - console.log(completion.data.choices[0].message); - const theResponse = completion.data.choices[0].message; - - return NextResponse.json({ output: theResponse }, { status: 200 }) - -}; \ No newline at end of file diff --git a/src/app/favicon.ico b/src/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/src/app/favicon.ico and /dev/null differ diff --git a/src/app/globals.css b/src/app/globals.css deleted file mode 100644 index a1822d7..0000000 --- a/src/app/globals.css +++ /dev/null @@ -1,10 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -body { - - background: #222222 ; - color: white; - -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx deleted file mode 100644 index 71b3fbf..0000000 --- a/src/app/layout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import './globals.css' -import { Inter } from 'next/font/google' - -const inter = Inter({ subsets: ['latin'] }) - -export const metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -} - -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - {children} - - ) -} diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index eed344e..0000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,91 +0,0 @@ -"use client"; - -import Image from "next/image"; -import { useState } from "react"; - -export default function Home() { - const [messages, setMessages] = useState([ - { - role: "assistant", - content: "Yo, this is ChatterBot! How can I help you today?", - }, - ]); - - const [theInput, setTheInput] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const callGetResponse = async () => { - setIsLoading(true); - let temp = messages; - temp.push({ role: "user", content: theInput }); - setTheInput(""); - console.log("Calling OpenAI..."); - - const response = await fetch("/api", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - - body: JSON.stringify({ messages }), - }); - - const data = await response.json(); - const { output } = data; - console.log("OpenAI replied...", output.content); - - setMessages((prevMessages) => [...prevMessages, output]); - setIsLoading(false); - - // scrollToRef.current.scrollIntoView() - }; - const Submit = (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - callGetResponse(); - } - }; - return ( - - ChatterBot - - - - {messages.map((e) => { - return ( - - {e.content} - - ); - })} - - - - {isLoading ? *loading* : ""} - - - setTheInput(event.target.value)} - className="w-[85%] h-10 px-3 py-2 resize-none overflow-y-auto text-black bg-gray-300 rouded" - onKeyDown={Submit} - /> - - send - - - - - - - ); -} diff --git a/tailwind.config.js b/tailwind.config.js deleted file mode 100644 index 4de5173..0000000 --- a/tailwind.config.js +++ /dev/null @@ -1,16 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: [ - './src/pages/**/*.{js,ts,jsx,tsx,mdx}', - './src/components/**/*.{js,ts,jsx,tsx,mdx}', - './src/app/**/*.{js,ts,jsx,tsx,mdx}', - ], - theme: { - extend: { - fontFamily: { - sans: ['var(--font-inter)'], - }, - }, - }, - plugins: [], -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 0c7555f..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -}