initial commit!
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer.check.command": "clippy",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"rust-analyzer.cargo.features": "all",
|
||||||
|
"files.eol": "\n",
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"files.trimFinalNewlines": true,
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"gitea.owner": "LowLevelDevs",
|
||||||
|
"gitea.repo": "damn_simple_architecture"
|
||||||
|
}
|
||||||
Generated
+1677
File diff suppressed because it is too large
Load Diff
+10
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "vibeengine"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.12.22", features = ["blocking", "json"] }
|
||||||
|
ron = "0.10.1"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
toml = "0.9.0"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
data_dir = "./game"
|
||||||
+2179
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,119 @@
|
|||||||
|
GameObject(
|
||||||
|
imports: [
|
||||||
|
"traits.ron",
|
||||||
|
"relationships.ron",
|
||||||
|
],
|
||||||
|
|
||||||
|
characters: [
|
||||||
|
Entity (
|
||||||
|
name: "generic villager",
|
||||||
|
profession: None,
|
||||||
|
location: None,
|
||||||
|
|
||||||
|
personality: Some((
|
||||||
|
traits: [],
|
||||||
|
values: [],
|
||||||
|
fears: [],
|
||||||
|
goals: [],
|
||||||
|
)),
|
||||||
|
|
||||||
|
speech: Some((
|
||||||
|
style: None,
|
||||||
|
common_phrases: [],
|
||||||
|
topics_of_interest: [],
|
||||||
|
vocabulary_level: None,
|
||||||
|
accent: None,
|
||||||
|
)),
|
||||||
|
|
||||||
|
knowledge: Some((
|
||||||
|
areas: [],
|
||||||
|
secrets: [],
|
||||||
|
skills: [],
|
||||||
|
)),
|
||||||
|
|
||||||
|
relationships: {
|
||||||
|
"player": (
|
||||||
|
stages: [
|
||||||
|
"relationships.neutral",
|
||||||
|
"relationships.enemy",
|
||||||
|
"relationships.friend",
|
||||||
|
"relationships.ally",
|
||||||
|
],
|
||||||
|
default: "relationships.neutral",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Entity (
|
||||||
|
name: "hammerick",
|
||||||
|
profession: Some("blacksmith"),
|
||||||
|
location: Some("village"),
|
||||||
|
personality: Some((
|
||||||
|
traits: [
|
||||||
|
"traits.generous",
|
||||||
|
"traits.curious",
|
||||||
|
"traits.patient",
|
||||||
|
],
|
||||||
|
values: [
|
||||||
|
"values.honesty",
|
||||||
|
"values.community",
|
||||||
|
"values.craftsmanship",
|
||||||
|
],
|
||||||
|
fears: [
|
||||||
|
"fears.failure",
|
||||||
|
"fears.public_shame",
|
||||||
|
"fears.losing_loved_ones",
|
||||||
|
],
|
||||||
|
goals: [
|
||||||
|
"goals.happy_family",
|
||||||
|
"goals.successful_business",
|
||||||
|
"goals.community_respect",
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
speech: Some((
|
||||||
|
style: Some("friendly"),
|
||||||
|
common_phrases: [
|
||||||
|
"Ah, good on ya!",
|
||||||
|
"Don't you worry 'bout that!",
|
||||||
|
"By the fires of the forge!",
|
||||||
|
],
|
||||||
|
topics_of_interest: [
|
||||||
|
"woodworking",
|
||||||
|
"cooking",
|
||||||
|
"village_gossip",
|
||||||
|
"local_news",
|
||||||
|
],
|
||||||
|
vocabulary_level: Some("basic"),
|
||||||
|
accent: Some("rural english"),
|
||||||
|
)),
|
||||||
|
knowledge: Some((
|
||||||
|
areas: [
|
||||||
|
"village_center",
|
||||||
|
"blacksmith_shop",
|
||||||
|
"forest_path",
|
||||||
|
],
|
||||||
|
secrets: [
|
||||||
|
"secret_passage_location",
|
||||||
|
"blacksmithing_secret",
|
||||||
|
],
|
||||||
|
skills: [
|
||||||
|
"casting",
|
||||||
|
"forging",
|
||||||
|
"hammering",
|
||||||
|
"woodworking",
|
||||||
|
"cooking",
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
relationships: {
|
||||||
|
"player": (
|
||||||
|
stages: [
|
||||||
|
"relationships.neutral",
|
||||||
|
"relationships.enemy",
|
||||||
|
"relationships.friend",
|
||||||
|
"relationships.ally",
|
||||||
|
],
|
||||||
|
default: "relationships.neutral",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
+281
@@ -0,0 +1,281 @@
|
|||||||
|
use std::{collections::HashMap, fs};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let s = fs::read_to_string("Config.toml").expect("Config.toml not found");
|
||||||
|
let config: Config = toml::from_str(&s).expect("Could not parse Config.toml");
|
||||||
|
println!("Data directory: {}", config.data_dir);
|
||||||
|
|
||||||
|
// game is the first arg
|
||||||
|
let filedir =
|
||||||
|
config.data_dir + "/" + &std::env::args().nth(1).expect("No game specified") + "/main.ron";
|
||||||
|
let s = fs::read_to_string(filedir).expect("Game file not found");
|
||||||
|
let game: GameObject = ron::from_str(&s).expect("Could not parse game file");
|
||||||
|
println!("Game: {game:#?}");
|
||||||
|
|
||||||
|
let character = game
|
||||||
|
.characters
|
||||||
|
.iter()
|
||||||
|
.find(|entity| entity.name == "hammerick")
|
||||||
|
.cloned()
|
||||||
|
.expect("Character hammerick not found");
|
||||||
|
|
||||||
|
let mut history = HashMap::new();
|
||||||
|
|
||||||
|
if let Some(speech) = character.speech.as_ref() {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
speech.generate(
|
||||||
|
&character,
|
||||||
|
"player",
|
||||||
|
"what's been going on around the village today?",
|
||||||
|
&mut history
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple request structure
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct ChatRequest {
|
||||||
|
messages: Vec<Message>,
|
||||||
|
temperature: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
struct Message {
|
||||||
|
role: String,
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct ChatResponse {
|
||||||
|
choices: Vec<Choice>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Choice {
|
||||||
|
message: Message,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chat_with_lm_studio(
|
||||||
|
user_prompt: &str,
|
||||||
|
system_prompt: &str,
|
||||||
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
|
||||||
|
let messages = vec![
|
||||||
|
Message {
|
||||||
|
role: "system".to_string(),
|
||||||
|
content: system_prompt.to_string(),
|
||||||
|
},
|
||||||
|
Message {
|
||||||
|
role: "user".to_string(),
|
||||||
|
content: user_prompt.to_string(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let request = ChatRequest {
|
||||||
|
messages,
|
||||||
|
temperature: 0.7,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post("http://localhost:1234/v1/chat/completions")
|
||||||
|
.json(&request)
|
||||||
|
.send()?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(format!("Request failed: {}", response.text()?).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let response: ChatResponse = response.json()?;
|
||||||
|
|
||||||
|
if let Some(choice) = response.choices.into_iter().next() {
|
||||||
|
Ok(choice.message.content)
|
||||||
|
} else {
|
||||||
|
Err("No response from model".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
struct Config {
|
||||||
|
data_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
struct GameObject {
|
||||||
|
imports: Vec<String>,
|
||||||
|
characters: Vec<Entity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct Entity {
|
||||||
|
name: String,
|
||||||
|
profession: Option<String>,
|
||||||
|
location: Option<String>,
|
||||||
|
personality: Option<Personality>,
|
||||||
|
speech: Option<Speech>,
|
||||||
|
knowledge: Option<Knowledge>,
|
||||||
|
relationships: HashMap<String, Relationship>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct Personality {
|
||||||
|
traits: Vec<String>,
|
||||||
|
values: Vec<String>,
|
||||||
|
fears: Vec<String>,
|
||||||
|
goals: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct Speech {
|
||||||
|
style: Option<String>,
|
||||||
|
common_phrases: Vec<String>,
|
||||||
|
topics_of_interest: Vec<String>,
|
||||||
|
vocabulary_level: Option<String>,
|
||||||
|
accent: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Speech {
|
||||||
|
pub fn generate(
|
||||||
|
&self,
|
||||||
|
character: &Entity,
|
||||||
|
user_name: &str,
|
||||||
|
message: &str,
|
||||||
|
history: &mut HashMap<String, String>,
|
||||||
|
) -> String {
|
||||||
|
let name: String = character.name.clone();
|
||||||
|
let profession: String = character
|
||||||
|
.profession
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&"unknown".to_string())
|
||||||
|
.clone();
|
||||||
|
let location: String = character
|
||||||
|
.location
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&"unknown".to_string())
|
||||||
|
.clone();
|
||||||
|
let personality: Personality = character.personality.clone().unwrap_or(Personality {
|
||||||
|
traits: vec![],
|
||||||
|
values: vec![],
|
||||||
|
fears: vec![],
|
||||||
|
goals: vec![],
|
||||||
|
});
|
||||||
|
let knowledge: Knowledge = character.knowledge.clone().unwrap_or(Knowledge {
|
||||||
|
areas: vec![],
|
||||||
|
secrets: vec![],
|
||||||
|
skills: vec![],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the system prompt
|
||||||
|
let mut system_prompt = String::new();
|
||||||
|
|
||||||
|
// 1. Character Identity and Role
|
||||||
|
system_prompt.push_str(&format!(
|
||||||
|
"You are {name}, a {profession} in a text-based game. Respond naturally and stay in character at all times. \n Your location: {location}\n",
|
||||||
|
));
|
||||||
|
|
||||||
|
// 2. Personality and Speech Style
|
||||||
|
system_prompt.push_str("## CHARACTER TRAITS\n");
|
||||||
|
if !personality.traits.is_empty() {
|
||||||
|
system_prompt.push_str(&format!(
|
||||||
|
"Personality: {traits}\n",
|
||||||
|
traits = personality.traits.join(", ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !personality.values.is_empty() {
|
||||||
|
system_prompt.push_str(&format!(
|
||||||
|
"Values: {values}\n",
|
||||||
|
values = personality.values.join(", ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !personality.goals.is_empty() {
|
||||||
|
system_prompt.push_str(&format!(
|
||||||
|
"Goals: {goals}\n",
|
||||||
|
goals = personality.goals.join(", ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Speech Patterns
|
||||||
|
system_prompt.push_str("\n## SPEECH STYLE\n");
|
||||||
|
if let Some(style) = &self.style {
|
||||||
|
system_prompt.push_str(&format!("Style: {style}\n"));
|
||||||
|
}
|
||||||
|
if let Some(accent) = &self.accent {
|
||||||
|
system_prompt.push_str(&format!("Accent: {accent}\n"));
|
||||||
|
}
|
||||||
|
if let Some(vocab) = &self.vocabulary_level {
|
||||||
|
system_prompt.push_str(&format!("Vocabulary: {vocab}\n"));
|
||||||
|
}
|
||||||
|
if !self.common_phrases.is_empty() {
|
||||||
|
system_prompt.push_str(&format!(
|
||||||
|
"Common phrases: {}\n",
|
||||||
|
self.common_phrases.join(", ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Knowledge Base
|
||||||
|
system_prompt.push_str("\n## CHARACTER KNOWLEDGE\n");
|
||||||
|
system_prompt.push_str(
|
||||||
|
"You know about the following topics. Only reference information from these areas:\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
if !knowledge.areas.is_empty() {
|
||||||
|
system_prompt.push_str(&format!("- Locations: {}\n", knowledge.areas.join(", ")));
|
||||||
|
}
|
||||||
|
if !knowledge.skills.is_empty() {
|
||||||
|
system_prompt.push_str(&format!("- Skills: {}\n", knowledge.skills.join(", ")));
|
||||||
|
}
|
||||||
|
if !knowledge.secrets.is_empty() {
|
||||||
|
system_prompt.push_str("- Secrets: [REDACTED - only reveal when appropriate]\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Conversation History
|
||||||
|
if !history.is_empty() {
|
||||||
|
system_prompt.push_str("\n## CONVERSATION HISTORY\n");
|
||||||
|
for (speaker, message) in history.iter() {
|
||||||
|
system_prompt.push_str(&format!("{speaker}: {message}\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Response Guidelines
|
||||||
|
system_prompt.push_str(
|
||||||
|
r#"
|
||||||
|
## RESPONSE INSTRUCTIONS
|
||||||
|
1. Stay in character at all times
|
||||||
|
2. Only reference information from your knowledge base
|
||||||
|
3. If asked about something outside your knowledge, say so
|
||||||
|
4. Keep responses concise (1-3 sentences)
|
||||||
|
5. Use natural speech patterns and contractions
|
||||||
|
6. Show personality through word choice and tone
|
||||||
|
7. If appropriate, ask questions to continue the conversation
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Add the message to the history
|
||||||
|
let user_prompt = format!("{user_name}: {message}");
|
||||||
|
history.insert(user_name.to_string(), user_prompt.clone());
|
||||||
|
|
||||||
|
println!("System prompt: {system_prompt}");
|
||||||
|
println!("User prompt: {user_prompt}");
|
||||||
|
|
||||||
|
// 7. Generate response
|
||||||
|
chat_with_lm_studio(&user_prompt, &system_prompt).unwrap()
|
||||||
|
// String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct Knowledge {
|
||||||
|
areas: Vec<String>,
|
||||||
|
secrets: Vec<String>,
|
||||||
|
skills: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct Relationship {
|
||||||
|
stages: Vec<String>,
|
||||||
|
default: String,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user