Skip to content

Commit

Permalink
fix: Since the OpenAI usage API(/v1/dashboard/billing/usage) is no lo…
Browse files Browse the repository at this point in the history
…nger available in July 22, the cost usage on the openai settings page has been replaced with token usage.

* Also fixed the API key validation error .
  • Loading branch information
Yubyf committed Jul 22, 2023
1 parent 2f1ccea commit 554acfe
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "56f49147e19f56a03019b2fa3a1bb569",
"entities": [
{
"tableName": "usage",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `api_key` TEXT NOT NULL, `model` TEXT NOT NULL, `tokens` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "apiKey",
"columnName": "api_key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "model",
"columnName": "model",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tokens",
"columnName": "tokens",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"_id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '56f49147e19f56a03019b2fa3a1bb569')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import com.crossbowffs.quotelock.app.SnackBarEvent
import com.crossbowffs.quotelock.app.emptySnackBarEvent
import com.crossbowffs.quotelock.data.AsyncResult
import com.crossbowffs.quotelock.data.api.AndroidString
import com.crossbowffs.quotelock.data.api.OpenAIAccount
import com.crossbowffs.quotelock.data.api.OpenAIConfigs
import com.crossbowffs.quotelock.data.modules.openai.OpenAIException
import com.crossbowffs.quotelock.data.modules.openai.OpenAIRepository
import com.crossbowffs.quotelock.data.modules.openai.OpenAIUsage
import com.crossbowffs.quotelock.utils.Xlog
import com.crossbowffs.quotelock.utils.prefix
import com.yubyf.quotelockx.R
Expand All @@ -30,7 +30,7 @@ import javax.inject.Inject
data class OpenAIUiState(
val openAIConfigs: OpenAIConfigs = OpenAIConfigs(),
val validateResult: AsyncResult<Unit>? = null,
val openAIAccount: OpenAIAccount? = null,
val openAIUsage: OpenAIUsage? = null,
)

@HiltViewModel
Expand All @@ -55,12 +55,11 @@ class OpenAIConfigsViewModel @Inject constructor(
openAIConfigs = it,
)
}.launchIn(this)
openAIRepository.openAIAccountFlow.onEach {
openAIRepository.openAIUsageFlow.onEach {
_uiState.value = _uiState.value.copy(
openAIAccount = it,
openAIUsage = it,
)
}.launchIn(this)
runCatching { openAIRepository.fetchAccountInfo() }
}
}

Expand All @@ -80,7 +79,7 @@ class OpenAIConfigsViewModel @Inject constructor(
if (openAIRepository.apiKey != apiKey) {
openAIRepository.apiKey = apiKey
_uiState.value = _uiState.value.copy(
openAIAccount = null,
openAIUsage = null,
)
viewModelScope.launch {
runCatching { openAIRepository.fetchAccountInfo() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import androidx.compose.material.icons.rounded.ArrowDropDown
import androidx.compose.material.icons.rounded.ArrowDropUp
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.MonetizationOn
import androidx.compose.material.icons.rounded.Token
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
Expand Down Expand Up @@ -325,7 +325,7 @@ fun OpenAIScreen(
placeholder = {
Text(
PREF_OPENAI_API_KEY_PREFIX.plus(
"*".toString().repeat(20)
"*".repeat(20)
),
color = hintColor
)
Expand Down Expand Up @@ -379,27 +379,56 @@ fun OpenAIScreen(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
uiState.openAIAccount?.takeIf { it.apiKey == configs.apiKey }?.let { account ->
Row(
modifier = Modifier
.padding(start = PREFERENCE_ITEM_HORIZONTAL_PADDING, top = 24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Rounded.MonetizationOn,
modifier = Modifier.size(16.dp),
contentDescription = null
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = stringResource(
id = R.string.module_openai_usage,
account.usageUsd,
account.hardLimitUsd
),
style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.Normal
)
uiState.openAIUsage?.takeIf { it.apiKey == configs.apiKey }?.let { usage ->
Column {
Spacer(modifier = Modifier.height(24.dp))
val usageTooltipState: PlainTooltipState = remember { PlainTooltipState() }
PlainTooltipBox(
tooltip = {
val modelColumns = usage.usages.map { it.first }
val tokenColumns = usage.usages.map { it.second }
Row {
Column {
modelColumns.forEach {
Text(
text = it,
)
}
}
Spacer(modifier = Modifier.width(8.dp))
Column(horizontalAlignment = Alignment.End) {
tokenColumns.forEach {
Text(
text = it.toString(),
)
}
}
}
},
tooltipState = usageTooltipState
) {
Row(
modifier = Modifier
.padding(start = PREFERENCE_ITEM_HORIZONTAL_PADDING)
.clip(RoundedCornerShape(10))
.clickable { scope.launch { usageTooltipState.show() } },
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Rounded.Token,
modifier = Modifier.size(16.dp),
contentDescription = null
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = stringResource(
id = R.string.module_openai_token_usage, usage.totalTokens
),
style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.Normal
)
}
}
}
} ?: Spacer(modifier = Modifier.weight(1f, true))
val haptic = LocalHapticFeedback.current
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.crossbowffs.quotelock.data.modules.openai

import android.content.Context
import android.provider.BaseColumns
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Delete
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.Room
import androidx.room.RoomDatabase
import com.crossbowffs.quotelock.data.modules.openai.OpenAIUsageContract.DATABASE_NAME
import kotlinx.coroutines.flow.Flow

const val OPENAI_USAGE_DB_VERSION = 1

object OpenAIUsageContract {
const val DATABASE_NAME = "openai_usage.db"

const val TABLE = "usage"

const val ID = BaseColumns._ID
const val API_KEY = "api_key"
const val MODEL = "model"
const val TOKENS = "tokens"
const val TIMESTAMP = "timestamp"
}

@Entity(tableName = OpenAIUsageContract.TABLE)
data class OpenAIUsageEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = OpenAIUsageContract.ID)
val id: Int? = null,
@ColumnInfo(name = OpenAIUsageContract.API_KEY)
val apiKey: String,
@ColumnInfo(name = OpenAIUsageContract.MODEL)
val model: String,
@ColumnInfo(name = OpenAIUsageContract.TOKENS)
val tokens: Int,
@ColumnInfo(name = OpenAIUsageContract.TIMESTAMP)
val timestamp: Long = System.currentTimeMillis(),
)

@Dao
interface OpenAIUsageDao {

@Query(
"SELECT * FROM ${OpenAIUsageContract.TABLE} WHERE ${OpenAIUsageContract.API_KEY} = :apiKey " +
"AND ${OpenAIUsageContract.TIMESTAMP} >= (:startDate / 1000)"
)
fun getUsageByApiKeyStream(apiKey: String, startDate: Long): Flow<List<OpenAIUsageEntity>>

@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(quote: OpenAIUsageEntity): Long?

@Delete
suspend fun delete(quote: OpenAIUsageEntity): Int

@Query("DELETE FROM ${OpenAIUsageContract.TABLE} WHERE ${OpenAIUsageContract.ID} = :id")
suspend fun delete(id: Long): Int

@Query("DELETE FROM ${OpenAIUsageContract.TABLE}")
suspend fun deleteAll()
}

@Database(entities = [OpenAIUsageEntity::class], version = OPENAI_USAGE_DB_VERSION)
abstract class OpenAIUsageDatabase : RoomDatabase() {
abstract fun dao(): OpenAIUsageDao

companion object {
@Volatile
private var INSTANCE: OpenAIUsageDatabase? = null

fun getDatabase(context: Context): OpenAIUsageDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
OpenAIUsageDatabase::class.java,
DATABASE_NAME
).setJournalMode(JournalMode.TRUNCATE).build()
INSTANCE = instance
// return instance
instance
}
}
}
}
Loading

0 comments on commit 554acfe

Please sign in to comment.