Skip to content

Commit 469fa90

Browse files
committed
feat: Add support for AI-powered method inference
1 parent bd3c863 commit 469fa90

File tree

56 files changed

+3104
-813
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3104
-813
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
plugins {
22
id("java")
3-
id("org.jetbrains.kotlin.jvm") version "1.8.0"
3+
id("org.jetbrains.kotlin.jvm") version "2.1.0"
44
id("application")
55
id("jacoco")
66
`java-library`

common-api/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ repositories {
77

88
dependencies {
99

10-
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0")
10+
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${properties["kotlin_version"]}")
1111

12-
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.0")
12+
implementation("org.jetbrains.kotlin:kotlin-reflect:${properties["kotlin_version"]}")
1313

1414
implementation("com.itangcent:commons:${properties["itangcent_intellij_version"]}") {
1515
exclude("com.google.inject")
@@ -43,7 +43,7 @@ dependencies {
4343
testImplementation("org.junit.jupiter:junit-jupiter-params:${properties["junit_version"]}")
4444
testImplementation("org.junit.jupiter:junit-jupiter-api:${properties["junit_version"]}")
4545
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${properties["junit_version"]}")
46-
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.8.0")
46+
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:${properties["kotlin_version"]}")
4747
}
4848

4949
tasks.getByName<Test>("test") {

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
plugin_name=EasyYapi
22
plugin_version=2.7.7.212.0
33
kotlin.code.style=official
4-
kotlin_version=1.8.0
4+
kotlin_version=2.1.0
55
junit_version=5.9.2
66
itangcent_intellij_version=1.7.5

idea-plugin/build.gradle.kts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ dependencies {
7070
exclude("com.google.guava", "guava")
7171
}
7272

73-
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.0")
73+
implementation("org.jetbrains.kotlin:kotlin-reflect:${properties["kotlin_version"]}")
7474

7575
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
7676
implementation("com.fasterxml.jackson.core:jackson-databind:2.12.2")
@@ -81,6 +81,9 @@ dependencies {
8181
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
8282
implementation("com.squareup.okhttp3:okhttp:4.12.0")
8383

84+
// Official OpenAI SDK for Java
85+
implementation("com.openai:openai-java:0.31.0")
86+
implementation("com.openai:openai-java-client-okhttp:0.31.0")
8487

8588
// https://search.maven.org/artifact/org.mockito.kotlin/mockito-kotlin/3.2.0/jar
8689
testImplementation("org.mockito.kotlin:mockito-kotlin:3.2.0")
@@ -96,7 +99,7 @@ dependencies {
9699
testImplementation("org.junit.jupiter:junit-jupiter-api:${properties["junit_version"]}")
97100
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${properties["junit_version"]}")
98101
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.7.1")
99-
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.8.0")
102+
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:${properties["kotlin_version"]}")
100103
}
101104

102105
tasks.getByName<Test>("test") {

idea-plugin/gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
2+
kotlin.daemon.jvmargs=-Xmx2g
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.itangcent.ai
2+
3+
/**
4+
* Base exception for all AI-related errors
5+
*/
6+
open class AIException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
7+
8+
/**
9+
* Exception thrown when there's an issue with the AI service configuration
10+
*/
11+
class AIConfigurationException(message: String) : AIException(message)
12+
13+
/**
14+
* Exception thrown when there's an error in the AI service API response
15+
*/
16+
class AIApiException(message: String, cause: Throwable? = null) : AIException(message, cause)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.itangcent.ai
2+
3+
/**
4+
* Contains messages used by AI services
5+
*/
6+
object AIMessages {
7+
/**
8+
* Default system message for general code-related tasks
9+
*/
10+
const val DEFAULT_SYSTEM_MESSAGE =
11+
"You are a helpful programming assistant with expertise in Java, Kotlin, and related technologies. " +
12+
"You can assist with code understanding, debugging, refactoring, optimization, and design. " +
13+
"Analyze the provided code and context carefully, and provide clear, accurate, and practical responses. " +
14+
"When appropriate, suggest improvements while respecting the existing code structure and patterns."
15+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.itangcent.ai
2+
3+
/**
4+
* Enum representing the supported AI service providers and their models
5+
*/
6+
enum class AIProvider(val displayName: String, val models: List<AIModel>) {
7+
/**
8+
* OpenAI service
9+
*/
10+
OPENAI(
11+
"OpenAI", listOf(
12+
AIModel("gpt-3.5-turbo", "GPT-3.5 Turbo"),
13+
AIModel("gpt-4", "GPT-4"),
14+
AIModel("gpt-4-turbo", "GPT-4 Turbo"),
15+
AIModel("o3-mini", "O3 Mini"),
16+
AIModel("o1", "O1"),
17+
AIModel("o1-mini", "O1 Mini"),
18+
AIModel("gpt-4o", "GPT-4o")
19+
)
20+
),
21+
22+
/**
23+
* DeepSeek service
24+
*/
25+
DEEPSEEK(
26+
"DeepSeek", listOf(
27+
AIModel("deepseek-chat", "DeepSeek-V3"),
28+
AIModel("deepseek-reasoner", "DeepSeek-R1")
29+
)
30+
);
31+
32+
companion object {
33+
/**
34+
* Get AIProvider by its display name (case-insensitive)
35+
*/
36+
fun fromDisplayName(name: String?): AIProvider? {
37+
return values().find { it.displayName.equals(name, ignoreCase = true) }
38+
}
39+
40+
/**
41+
* Get default model for a specific AI provider
42+
*/
43+
fun getDefaultModel(aiProvider: AIProvider): AIModel? {
44+
return aiProvider.models.firstOrNull()
45+
}
46+
}
47+
}
48+
49+
/**
50+
* Data class representing an AI model
51+
* @param id The model identifier used in API requests
52+
* @param displayName The human-readable name for display in UI
53+
*/
54+
data class AIModel(val id: String, val displayName: String)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.itangcent.ai
2+
3+
/**
4+
* Interface for AI service operations
5+
*/
6+
interface AIService {
7+
/**
8+
* Sends a prompt to the AI service and returns the response
9+
* @param prompt The user prompt to send to the AI service
10+
* @throws AIException if there's an error with the AI service
11+
*/
12+
fun sendPrompt(prompt: String): String {
13+
return sendPrompt(AIMessages.DEFAULT_SYSTEM_MESSAGE, prompt)
14+
}
15+
16+
/**
17+
* Sends a prompt to the AI service with a specific system message and returns the response
18+
* @param systemMessage The system message that defines the AI's behavior/role
19+
* @param userPrompt The user prompt to send to the AI service
20+
* @throws AIConfigurationException if there's an issue with the AI service configuration
21+
* @throws AIApiException if there's an error in the AI service API response
22+
*/
23+
fun sendPrompt(systemMessage: String, userPrompt: String): String {
24+
// Default implementation delegates to the simpler method
25+
// Implementations should override this for better efficiency
26+
return sendPrompt(userPrompt)
27+
}
28+
}
29+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.itangcent.ai
2+
3+
import com.google.inject.Inject
4+
import com.google.inject.Singleton
5+
import com.itangcent.common.logger.Log
6+
import com.itangcent.idea.plugin.api.cache.ProjectCacheRepository
7+
import com.itangcent.idea.sqlite.SqliteDataResourceHelper
8+
import com.itangcent.idea.sqlite.get
9+
import com.itangcent.idea.sqlite.set
10+
import com.itangcent.idea.utils.DigestUtils
11+
import com.itangcent.intellij.context.ActionContext
12+
import java.util.concurrent.TimeUnit
13+
14+
/**
15+
* Cache for AI service responses to avoid duplicate API calls
16+
*/
17+
@Singleton
18+
class AIServiceCache {
19+
20+
companion object : Log() {
21+
private val EXPIRED_TIME = TimeUnit.DAYS.toMillis(30)
22+
}
23+
24+
@Inject
25+
private lateinit var projectCacheRepository: ProjectCacheRepository
26+
27+
@Inject
28+
private lateinit var actionContext: ActionContext
29+
30+
// Use SqliteDataResourceHelper instead of ConcurrentHashMap for persistent storage
31+
private val beanDAO: SqliteDataResourceHelper.ExpiredBeanDAO by lazy {
32+
val sqliteDataResourceHelper = actionContext.instance(SqliteDataResourceHelper::class)
33+
sqliteDataResourceHelper.getExpiredBeanDAO(
34+
projectCacheRepository.getOrCreateFile(".ai.service.cache.db").path, "AI_SERVICE_CACHE"
35+
)
36+
}
37+
38+
/**
39+
* Get a cached response if available
40+
* @param systemMessage The system message
41+
* @param userPrompt The user prompt
42+
* @return The cached response or null if not found
43+
*/
44+
fun getCachedResponse(systemMessage: String, userPrompt: String): String? {
45+
val key = createCacheKey(systemMessage, userPrompt)
46+
return beanDAO.get(key)
47+
}
48+
49+
/**
50+
* Cache a response
51+
* @param systemMessage The system message
52+
* @param userPrompt The user prompt
53+
* @param response The AI response to cache
54+
*/
55+
fun cacheResponse(systemMessage: String, userPrompt: String, response: String) {
56+
LOG.info("cache response: $userPrompt, $response")
57+
val key = createCacheKey(systemMessage, userPrompt)
58+
beanDAO.set(key, response, System.currentTimeMillis() + EXPIRED_TIME)
59+
}
60+
61+
/**
62+
* Create a cache key from the system message and user prompt using MD5 hash
63+
*/
64+
private fun createCacheKey(systemMessage: String, userPrompt: String): String {
65+
val input = "$systemMessage::$userPrompt"
66+
return DigestUtils.md5(input)
67+
}
68+
}

0 commit comments

Comments
 (0)