Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Build

on:
push:
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Build
run: gradle test assembleDebug
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Changelog

## [Unreleased]
- Initial multi-module setup with LLM interface and TAK plugin stub.
11 changes: 11 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Contributing

## Prerequisites
- Android Studio Giraffe or newer
- JDK 17
- Android SDK with API 34

## Building
```
./gradlew assembleDebug
```
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# Tactical App

This repository contains a minimal Android application skeleton intended for tactical scenarios.
Multi-module Android project for an offline-first tactical AI assistant.

## Features
- Placeholder `MainActivity` ready for integration with the **Android Team Awareness Kit (ATAK)** SDK.
- Kotlin-based implementation with Material3 theme.
- Structured to support future offline LLM integration (e.g., GPT-OSS models) for edge deployments.
## Modules
- `app`: main Android application with basic home screen.
- `llm`: common LLM interface, router, and stub implementations.
- `tak-plugin`: CivTAK plugin stub for future CoT integration.

## Building
The project uses Gradle. Ensure a compatible Gradle version is installed on your system and run:

```bash
gradle assembleDebug
Ensure JDK 17 and Android SDK are installed, then run:
```
./gradlew assembleDebug
```

> **Note:** Building requires the Android SDK and network access to resolve dependencies. The Gradle wrapper is omitted to keep binary files out of version control.
## Development
A simple edge LLM stub server is available:
```
./scripts/dev/run_edge_stub.sh
```

## Future Work
- Integrate ATAK APIs for mission planning and situational awareness.
- Embed a local LLM runtime (e.g., `llama.cpp`) for disconnected operations.
- Add WiFi CSI processing and pose estimation modules as described in Vantage Scanner roadmap.
See `CONTRIBUTING.md` for details.
41 changes: 0 additions & 41 deletions app/build.gradle

This file was deleted.

56 changes: 56 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.kapt")
id("org.jetbrains.kotlin.plugin.serialization")
}

android {
namespace = "com.example.tacticalapp"
compileSdk = 34

defaultConfig {
applicationId = "com.example.tacticalapp"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
}

buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}

buildFeatures {
viewBinding = true
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = "17"
}
}

dependencies {
implementation(project(":llm"))
implementation(project(":tak-plugin"))

implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("com.google.android.material:material:1.12.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
kapt("androidx.room:room-compiler:2.6.1")
implementation("androidx.work:work-runtime-ktx:2.9.0")
}
20 changes: 12 additions & 8 deletions app/src/main/java/com/example/tacticalapp/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ package com.example.tacticalapp

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.widget.TextView
import com.example.tacticalapp.databinding.ActivityMainBinding

/**
* Main entry point for TacticalApp.
* Future enhancements: integrate ATAK API and local LLM for offline analysis.
*/
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val tv = TextView(this)
tv.text = "Tactical App Placeholder"
setContentView(tv)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

binding.btnLocalChat.setOnClickListener {
// TODO: open local chat UI
}
binding.btnTakStatus.setOnClickListener {
// TODO: show TAK plugin status
}
}
}
21 changes: 21 additions & 0 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">

<Button
android:id="@+id/btnLocalChat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Local Chat" />

<Button
android:id="@+id/btnTakStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="TAK Plugin Status" />

</LinearLayout>
17 changes: 0 additions & 17 deletions build.gradle

This file was deleted.

6 changes: 6 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
plugins {
id("com.android.application") version "8.5.1" apply false
id("com.android.library") version "8.5.1" apply false
id("org.jetbrains.kotlin.android") version "1.9.24" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.24" apply false
}
3 changes: 3 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Tactical App Docs

Placeholder for screenshots and walkthroughs.
34 changes: 34 additions & 0 deletions llm/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.serialization")
}

android {
namespace = "com.example.llm"
compileSdk = 34

defaultConfig {
minSdk = 26
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = "17"
}
}

dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-kotlinx-serialization:2.11.0")

testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
}
48 changes: 48 additions & 0 deletions llm/src/main/java/com/example/llm/EdgeLlm.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.example.llm

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Streaming
import retrofit2.converter.kotlinx.serialization.asConverterFactory

class EdgeLlm(
baseUrl: String,
client: OkHttpClient = OkHttpClient()
) : Llm {
private val api: EdgeApi

init {
val contentType = "application/json".toMediaType()
api = Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.addConverterFactory(Json.asConverterFactory(contentType))
.build()
.create(EdgeApi::class.java)
}

@Serializable
data class ChatRequest(val messages: List<Llm.Message>)
@Serializable
data class ChatResponse(val text: String, val done: Boolean = false)

interface EdgeApi {
@POST("chat")
@Streaming
suspend fun chat(@Body body: ChatRequest): List<ChatResponse>
}

override suspend fun chat(messages: List<Llm.Message>): Flow<Llm.TokenChunk> = flow {
val responses = api.chat(ChatRequest(messages))
for (resp in responses) {
emit(Llm.TokenChunk(resp.text, resp.done))
}
}
}
9 changes: 9 additions & 0 deletions llm/src/main/java/com/example/llm/Llm.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.llm

import kotlinx.coroutines.flow.Flow

interface Llm {
data class Message(val role: String, val content: String)
data class TokenChunk(val text: String, val done: Boolean = false)
suspend fun chat(messages: List<Message>): Flow<TokenChunk>
}
21 changes: 21 additions & 0 deletions llm/src/main/java/com/example/llm/LlmRouter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.llm

import kotlinx.coroutines.flow.Flow

class LlmRouter(
private val local: Llm,
private val edge: Llm?,
private val prefs: Preferences,
private val systemState: SystemState
) : Llm {

data class Preferences(val preferEdge: Boolean)
data class SystemState(val edgeReachable: Boolean, val batteryPercent: Int)

override suspend fun chat(messages: List<Llm.Message>): Flow<Llm.TokenChunk> {
val useEdge = prefs.preferEdge && edge != null &&
systemState.edgeReachable && systemState.batteryPercent > 20
val target = if (useEdge) edge!! else local
return target.chat(messages)
}
}
13 changes: 13 additions & 0 deletions llm/src/main/java/com/example/llm/LocalLlm.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.llm

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

/**
* Placeholder local LLM implementation.
*/
class LocalLlm : Llm {
override suspend fun chat(messages: List<Llm.Message>): Flow<Llm.TokenChunk> = flow {
emit(Llm.TokenChunk(text = "local-response", done = true))
}
}
Loading
Loading