summaryrefslogtreecommitdiffstats
path: root/src/config
diff options
context:
space:
mode:
authorsigoden <sigoden@gmail.com>2025-02-08 08:46:34 +0800
committerGitHub <noreply@github.com>2025-02-08 08:46:34 +0800
commiteb527ea758b49adbc1e311638cc6999190ff1124 (patch)
treee2396eb8c92adb7cd236bbe1b6c0fa4ec26f6d05 /src/config
parenta491820ab5ff9bb58e62fd25d88f31ab53a81db9 (diff)
downloadaichat-eb527ea758b49adbc1e311638cc6999190ff1124.tar.gz
feat: enhance `.file` for loading resources from diverse sources (#1155)
Diffstat (limited to 'src/config')
-rw-r--r--src/config/input.rs138
1 files changed, 86 insertions, 52 deletions
diff --git a/src/config/input.rs b/src/config/input.rs
index 482b1d5..c16e1e2 100644
--- a/src/config/input.rs
+++ b/src/config/input.rs
@@ -5,11 +5,11 @@ use crate::client::{
MessageContent, MessageContentPart, MessageContentToolCalls, MessageRole, Model,
};
use crate::function::ToolResult;
-use crate::utils::{base64_encode, sha256, AbortSignal};
+use crate::utils::{base64_encode, is_loader_protocol, sha256, AbortSignal};
use anyhow::{bail, Context, Result};
-use path_absolutize::Absolutize;
-use std::{collections::HashMap, fs::File, io::Read, path::Path};
+use indexmap::IndexSet;
+use std::{collections::HashMap, fs::File, io::Read};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
const IMAGE_EXTS: [&str; 5] = ["png", "jpeg", "jpg", "webp", "gif"];
@@ -60,38 +60,19 @@ impl Input {
paths: Vec<String>,
role: Option<Role>,
) -> Result<Self> {
- let mut raw_paths = vec![];
- let mut external_cmds = vec![];
- let mut local_paths = vec![];
- let mut remote_urls = vec![];
+ let loaders = config.read().document_loaders.clone();
+ let (raw_paths, local_paths, remote_urls, external_cmds, protocol_paths, with_last_reply) =
+ resolve_paths(&loaders, paths)?;
let mut last_reply = None;
- let mut with_last_reply = false;
- for path in paths {
- match resolve_local_path(&path) {
- Some(v) => {
- if v == "%%" {
- with_last_reply = true;
- raw_paths.push(v);
- } else if v.len() > 2 && v.starts_with('`') && v.ends_with('`') {
- external_cmds.push(v[1..v.len() - 1].to_string());
- raw_paths.push(v);
- } else {
- if let Ok(path) = Path::new(&v).absolutize() {
- raw_paths.push(path.display().to_string());
- }
- local_paths.push(v);
- }
- }
- None => {
- raw_paths.push(path.clone());
- remote_urls.push(path);
- }
- }
- }
- let (documents, medias, data_urls) =
- load_documents(config, external_cmds, local_paths, remote_urls)
- .await
- .context("Failed to load files")?;
+ let (documents, medias, data_urls) = load_documents(
+ &loaders,
+ local_paths,
+ remote_urls,
+ external_cmds,
+ protocol_paths,
+ )
+ .await
+ .context("Failed to load files")?;
let mut texts = vec![];
if !raw_text.is_empty() {
texts.push(raw_text.to_string());
@@ -409,11 +390,65 @@ fn resolve_role(config: &Config, role: Option<Role>) -> (Role, bool, bool) {
}
}
+type ResolvePathsOutput = (
+ Vec<String>,
+ Vec<String>,
+ Vec<String>,
+ Vec<String>,
+ Vec<String>,
+ bool,
+);
+
+fn resolve_paths(
+ loaders: &HashMap<String, String>,
+ paths: Vec<String>,
+) -> Result<ResolvePathsOutput> {
+ let mut raw_paths = IndexSet::new();
+ let mut local_paths = IndexSet::new();
+ let mut remote_urls = IndexSet::new();
+ let mut external_cmds = IndexSet::new();
+ let mut protocol_paths = IndexSet::new();
+ let mut with_last_reply = false;
+ for path in paths {
+ if path == "%%" {
+ with_last_reply = true;
+ raw_paths.insert(path);
+ } else if path.starts_with('`') && path.len() > 2 && path.ends_with('`') {
+ external_cmds.insert(path[1..path.len() - 1].to_string());
+ raw_paths.insert(path);
+ } else if is_url(&path) {
+ if path.strip_suffix("**").is_some() {
+ bail!("Invalid website '{path}'");
+ }
+ remote_urls.insert(path.clone());
+ raw_paths.insert(path);
+ } else if is_loader_protocol(loaders, &path) {
+ protocol_paths.insert(path.clone());
+ raw_paths.insert(path);
+ } else {
+ let resolved_path = resolve_home_dir(&path);
+ let absolute_path = to_absolute_path(&resolved_path)
+ .with_context(|| format!("Invalid path '{path}'"))?;
+ local_paths.insert(resolved_path);
+ raw_paths.insert(absolute_path);
+ }
+ }
+ Ok((
+ raw_paths.into_iter().collect(),
+ local_paths.into_iter().collect(),
+ remote_urls.into_iter().collect(),
+ external_cmds.into_iter().collect(),
+ protocol_paths.into_iter().collect(),
+ with_last_reply,
+ ))
+}
+
async fn load_documents(
- config: &GlobalConfig,
- external_cmds: Vec<String>,
+ loaders: &HashMap<String, String>,
local_paths: Vec<String>,
remote_urls: Vec<String>,
+ external_cmds: Vec<String>,
+ protocol_paths: Vec<String>,
) -> Result<(
Vec<(&'static str, String, String)>,
Vec<String>,
@@ -422,6 +457,7 @@ async fn load_documents(
let mut files = vec![];
let mut medias = vec![];
let mut data_urls = HashMap::new();
+
for cmd in external_cmds {
let (success, stdout, stderr) =
run_command_with_output(&SHELL.cmd, &[&SHELL.arg, &cmd], None)?;
@@ -433,15 +469,14 @@ async fn load_documents(
}
let local_files = expand_glob_paths(&local_paths, true).await?;
- let loaders = config.read().document_loaders.clone();
for file_path in local_files {
if is_image(&file_path) {
let contents = read_media_to_data_url(&file_path)
- .with_context(|| format!("Unable to read media file '{file_path}'"))?;
+ .with_context(|| format!("Unable to read media '{file_path}'"))?;
data_urls.insert(sha256(&contents), file_path);
medias.push(contents)
} else {
- let document = load_file(&loaders, &file_path)
+ let document = load_file(loaders, &file_path)
.await
.with_context(|| format!("Unable to read file '{file_path}'"))?;
files.push(("FILE", file_path, document.contents));
@@ -449,7 +484,7 @@ async fn load_documents(
}
for file_url in remote_urls {
- let (contents, extension) = fetch_with_loaders(&loaders, &file_url, true)
+ let (contents, extension) = fetch_with_loaders(loaders, &file_url, true)
.await
.with_context(|| format!("Failed to load url '{file_url}'"))?;
if extension == MEDIA_URL_EXTENSION {
@@ -459,6 +494,17 @@ async fn load_documents(
files.push(("URL", file_url, contents));
}
}
+
+ for protocol_path in protocol_paths {
+ let documents = load_protocol_path(loaders, &protocol_path)
+ .with_context(|| format!("Failed to load from '{protocol_path}'"))?;
+ files.extend(
+ documents
+ .into_iter()
+ .map(|document| ("FROM", document.path, document.contents)),
+ );
+ }
+
Ok((files, medias, data_urls))
}
@@ -474,18 +520,6 @@ pub fn resolve_data_url(data_urls: &HashMap<String, String>, data_url: String) -
}
}
-fn resolve_local_path(path: &str) -> Option<String> {
- if is_url(path) {
- return None;
- }
- let new_path = if let (Some(file), Some(home)) = (path.strip_prefix("~/"), dirs::home_dir()) {
- home.join(file).display().to_string()
- } else {
- path.to_string()
- };
- Some(new_path)
-}
-
fn is_image(path: &str) -> bool {
get_patch_extension(path)
.map(|v| IMAGE_EXTS.contains(&v.as_str()))