diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 12b06bdcc..069111808 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -2392,6 +2392,7 @@ "bool": False, "string": "", "text": "", + "file": "", "list": [], "object": {}, } diff --git a/astrbot/dashboard/routes/__init__.py b/astrbot/dashboard/routes/__init__.py index ef2fa3e86..5ce5e2d99 100644 --- a/astrbot/dashboard/routes/__init__.py +++ b/astrbot/dashboard/routes/__init__.py @@ -11,6 +11,7 @@ from .file import FileRoute from .session_management import SessionManagementRoute from .persona import PersonaRoute +from .plugin_config_files import PluginConfigFileFieldRoute __all__ = [ "AuthRoute", @@ -26,4 +27,5 @@ "FileRoute", "SessionManagementRoute", "PersonaRoute", + "PluginConfigFileFieldRoute", ] diff --git a/astrbot/dashboard/routes/plugin_config_files.py b/astrbot/dashboard/routes/plugin_config_files.py new file mode 100644 index 000000000..5b2eb3f6f --- /dev/null +++ b/astrbot/dashboard/routes/plugin_config_files.py @@ -0,0 +1,282 @@ +import os +import traceback +import uuid +from datetime import datetime +from pathlib import Path +from typing import Any, Dict, Optional, Tuple + +from quart import request + +from astrbot.core import logger +from astrbot.core.star.star import star_registry +from astrbot.core.star.star_tools import StarTools + +from .route import Route, Response, RouteContext + + +def _find_plugin_md(plugin_name: str): + for md in star_registry: + if md.name == plugin_name: + return md + return None + + +def _resolve_field_schema(schema: Dict[str, Any], field: str) -> Optional[Dict[str, Any]]: + """Find the schema object for a field. Support flat schema and object nesting 1-level for safety. + + Note: Current UI passes top-level field names. This supports nested paths like "a.b" as best-effort. + """ + if field in schema: + return schema.get(field) + + # Support dot selector for object nesting (limited depth) + parts = field.split(".") + curr = schema + for idx, key in enumerate(parts): + node = curr.get(key) + if not isinstance(node, dict): + return None + if idx == len(parts) - 1: + return node + if node.get("type") != "object": + return None + curr = node.get("items", {}) + return None + + +def _ensure_inside_root(root: Path, target: Path) -> bool: + try: + root_r = root.resolve() + tgt_r = target.resolve() + return str(tgt_r).startswith(str(root_r)) + except Exception: + return False + + +def _gen_safe_filename(tmpl: str, original_name: str) -> str: + name, ext = os.path.splitext(original_name) + ext = ext.lower() + ts = datetime.utcnow().strftime("%Y%m%d-%H%M%S") + uid = uuid.uuid4().hex + + def sanitize(text: str) -> str: + # keep letters/numbers/underscore/dash/dot and CJK letters + out = [] + for ch in text: + if ch.isalnum() or ch in ("-", "_", ".", " "): + out.append(ch) + s = "".join(out).strip().replace(" ", "_") + # no path traversal or separators + s = s.replace("..", "_").replace("/", "_").replace("\\", "_") + return s or uid # fallback to uid if empty + + name_clean = sanitize(name) + original_clean = f"{name_clean}{ext}" + + # Support direct original name keepers + if tmpl.strip().lower() in ("original", "{original}", "{filename}"): + safe = original_clean + else: + # Known placeholders: {timestamp} {uuid} {ext} {name} {original} + safe = ( + tmpl.replace("{timestamp}", ts) + .replace("{uuid}", uid) + .replace("{ext}", ext) + .replace("{name}", name_clean) + .replace("{original}", original_clean) + ) + safe = sanitize(safe) + + # Ensure extension consistency when user forgot {ext} + if not safe.endswith(ext): + # keep as-is; schema's accept has already validated the ext + pass + + return safe + + +class PluginConfigFileFieldRoute(Route): + """Per-plugin file field manager (list/upload/delete).""" + + def __init__(self, context: RouteContext) -> None: + super().__init__(context) + self.routes = { + "/plugin//config/filefield/list": ("GET", self.list_files), + "/plugin//config/filefield/upload": ( + "POST", + self.upload_file, + ), + "/plugin//config/filefield/delete": ( + "DELETE", + self.delete_file, + ), + } + self.register_routes() + + async def _resolve_dest_dir( + self, plugin_name: str, field: str + ) -> Tuple[Path, Dict[str, Any]]: + md = _find_plugin_md(plugin_name) + if not md: + raise ValueError(f"插件 {plugin_name} 不存在") + if not md.config: + raise ValueError(f"插件 {plugin_name} 没有注册配置") + + schema = md.config.schema or {} + field_schema = _resolve_field_schema(schema, field) + if not field_schema: + raise ValueError(f"字段 {field} 未在 _conf_schema.json 中定义") + if field_schema.get("type") != "file": + raise ValueError(f"字段 {field} 不是 file 类型") + + dest_dir = field_schema.get("dest_dir") + if not dest_dir or not isinstance(dest_dir, str): + raise ValueError(f"字段 {field} 缺少必填 dest_dir") + + # Root is StarTools.get_data_dir(plugin_name) + root = StarTools.get_data_dir(plugin_name) + target_root = (root / dest_dir).resolve() + target_root.mkdir(parents=True, exist_ok=True) + if not _ensure_inside_root(root, target_root): + raise ValueError("非法目录: 超出插件数据目录") + return target_root, field_schema + + async def list_files(self, plugin_name: str): + try: + field = request.args.get("field", type=str) + if not field: + return Response().error("缺少参数 field").__dict__ + + target_root, _ = await self._resolve_dest_dir(plugin_name, field) + root = StarTools.get_data_dir(plugin_name) + + files = [] + if target_root.exists(): + for entry in target_root.iterdir(): + if entry.is_file(): + stat = entry.stat() + rel_path = str(entry.resolve()).replace(str(root.resolve()) + os.sep, "") + files.append( + { + "name": entry.name, + "rel_path": rel_path.replace("\\", "/"), + "size": stat.st_size, + "mtime": int(stat.st_mtime), + } + ) + + files.sort(key=lambda x: x["mtime"], reverse=True) + return Response().ok(files).__dict__ + except Exception as e: + logger.error(traceback.format_exc()) + return Response().error(str(e)).__dict__ + + async def upload_file(self, plugin_name: str): + try: + form = await request.form + field = form.get("field") if form else None + if not field: + return Response().error("缺少参数 field").__dict__ + + files = await request.files + file = files.get("file") if files else None + if not file: + return Response().error("缺少文件 file").__dict__ + + target_root, field_schema = await self._resolve_dest_dir(plugin_name, field) + root = StarTools.get_data_dir(plugin_name) + + # Validate extension + accept = field_schema.get("accept") + orig_name = file.filename or "upload.bin" + _, ext = os.path.splitext(orig_name) + ext = ext.lower() + if accept and isinstance(accept, list): + normalized = [s.lower() for s in accept] + if ext not in normalized: + return Response().error(f"非法后缀: {ext}").__dict__ + + # Read content (for size check and write) + # Quart's FileStorage.read() returns bytes (not awaitable) + content: bytes = file.read() + max_mb = field_schema.get("max_size_mb") + if isinstance(max_mb, (int, float)) and max_mb > 0: + if len(content) > max_mb * 1024 * 1024: + return Response().error("文件过大").__dict__ + + name_tmpl = field_schema.get("name_template", "{timestamp}-{uuid}{ext}") + safe_name = _gen_safe_filename(name_tmpl, orig_name) + dest_path = (target_root / safe_name).resolve() + if not _ensure_inside_root(root, dest_path): + return Response().error("非法路径").__dict__ + + with open(dest_path, "wb") as f: + f.write(content) + + rel_path = str(dest_path).replace(str(root.resolve()) + os.sep, "").replace("\\", "/") + + # Update plugin config value + md = _find_plugin_md(plugin_name) + if not md or not md.config: + return Response().error("插件配置未注册").__dict__ + + # multiple=false: overwrite string; if true: append to array (not exposed in UI now) + multiple = bool(field_schema.get("multiple", False)) + new_conf = dict(md.config) + if multiple: + cur = new_conf.get(field) + if not isinstance(cur, list): + cur = [] + cur.append(rel_path) + new_conf[field] = cur + else: + new_conf[field] = rel_path + md.config.save_config(new_conf) + + stat = os.stat(dest_path) + return Response().ok( + { + "ok": True, + "path": rel_path, + "size": stat.st_size, + "mtime": int(stat.st_mtime), + } + ).__dict__ + except Exception as e: + logger.error(traceback.format_exc()) + return Response().error(str(e)).__dict__ + + async def delete_file(self, plugin_name: str): + try: + field = request.args.get("field", type=str) + rel_path = request.args.get("path", type=str) + if not field or not rel_path: + return Response().error("缺少参数 field 或 path").__dict__ + + target_root, field_schema = await self._resolve_dest_dir(plugin_name, field) + root = StarTools.get_data_dir(plugin_name) + + target_file = (root / rel_path).resolve() + if not _ensure_inside_root(target_root, target_file): + return Response().error("非法路径").__dict__ + if target_file.exists() and target_file.is_file(): + target_file.unlink() + + # If current config value equals this file, clear it + md = _find_plugin_md(plugin_name) + if md and md.config: + new_conf = dict(md.config) + multiple = bool(field_schema.get("multiple", False)) + if multiple: + cur = new_conf.get(field) + if isinstance(cur, list): + new_conf[field] = [p for p in cur if p != rel_path] + else: + if new_conf.get(field) == rel_path: + new_conf[field] = "" + md.config.save_config(new_conf) + + return Response().ok({"ok": True}).__dict__ + except Exception as e: + logger.error(traceback.format_exc()) + return Response().error(str(e)).__dict__ diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index 9b9588d43..35ea3e842 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -72,6 +72,7 @@ def __init__( ) self.persona_route = PersonaRoute(self.context, db, core_lifecycle) self.t2i_route = T2iRoute(self.context, core_lifecycle) + self.plugin_config_file_route = PluginConfigFileFieldRoute(self.context) self.app.add_url_rule( "/api/plug/", diff --git a/dashboard/src/api/configFileField.ts b/dashboard/src/api/configFileField.ts new file mode 100644 index 000000000..4eae243a4 --- /dev/null +++ b/dashboard/src/api/configFileField.ts @@ -0,0 +1,42 @@ +import axios from 'axios'; + +export type FileItem = { + name: string; + rel_path: string; + size: number; + mtime: number; +}; + +export async function listFiles(plugin: string, field: string): Promise { + const res = await axios.get(`/api/plugin/${encodeURIComponent(plugin)}/config/filefield/list`, { + params: { field } + }); + if (res.data?.status === 'ok') { + return res.data.data as FileItem[]; + } + throw new Error(res.data?.message || 'List failed'); +} + +export async function uploadFile(plugin: string, field: string, file: File): Promise<{ path: string; size: number; mtime: number }>{ + const form = new FormData(); + form.append('field', field); + form.append('file', file); + const res = await axios.post(`/api/plugin/${encodeURIComponent(plugin)}/config/filefield/upload`, form, { + headers: { 'Content-Type': 'multipart/form-data' } + }); + if (res.data?.status === 'ok') { + return res.data.data as { path: string; size: number; mtime: number }; + } + throw new Error(res.data?.message || 'Upload failed'); +} + +export async function deleteFile(plugin: string, field: string, relPath: string): Promise { + const res = await axios.delete(`/api/plugin/${encodeURIComponent(plugin)}/config/filefield/delete`, { + params: { field, path: relPath } + }); + if (res.data?.status === 'ok') { + return; + } + throw new Error(res.data?.message || 'Delete failed'); +} + diff --git a/dashboard/src/components/config/fields/FileField.vue b/dashboard/src/components/config/fields/FileField.vue new file mode 100644 index 000000000..9331f74ce --- /dev/null +++ b/dashboard/src/components/config/fields/FileField.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/dashboard/src/components/shared/AstrBotConfig.vue b/dashboard/src/components/shared/AstrBotConfig.vue index 4374c99a0..a11233110 100644 --- a/dashboard/src/components/shared/AstrBotConfig.vue +++ b/dashboard/src/components/shared/AstrBotConfig.vue @@ -6,6 +6,7 @@ import ProviderSelector from './ProviderSelector.vue' import PersonaSelector from './PersonaSelector.vue' import KnowledgeBaseSelector from './KnowledgeBaseSelector.vue' import { useI18n } from '@/i18n/composables' +import FileField from '@/components/config/fields/FileField.vue' const props = defineProps({ metadata: { @@ -231,6 +232,16 @@ function hasVisibleItemsAfter(items, currentIndex) { hide-details > + +
+ +
+
+ +
+ +
+
+import { ref } from 'vue' + +const props = defineProps<{ + accept?: string, + multiple?: boolean, + color?: string, + buttonText?: string, + icon?: string, + loading?: boolean, + disabled?: boolean, + size?: string +}>() + +const emit = defineEmits<{ (e: 'picked', files: File | File[] | null): void }>() + +const fileInputRef = ref(null) +const model = ref(null) + +function trigger() { + if (fileInputRef.value?.click) fileInputRef.value.click() +} + +function onPicked(val: any) { + emit('picked', val || null) + // reset to allow picking same file again + model.value = null +} + + + + + + diff --git a/main.py b/main.py index b3c8e5893..3b30920a0 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,7 @@ from astrbot.core.config.default import VERSION from astrbot.core.utils.io import download_dashboard, get_dashboard_version from astrbot.core.utils.astrbot_path import get_astrbot_data_path +import shutil # add parent path to sys.path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -50,6 +51,22 @@ async def check_dashboard_files(webui_dir: str | None = None): logger.warning(f"指定的 WebUI 目录 {webui_dir} 不存在,将使用默认逻辑。") data_dist_path = os.path.join(get_astrbot_data_path(), "dist") + + # Way B: If a local built dashboard/dist exists, copy it to data/dist + try: + repo_root = os.path.dirname(os.path.abspath(__file__)) + local_dist = os.path.join(repo_root, "dashboard", "dist") + if os.path.exists(local_dist): + logger.info(f"检测到本地 WebUI 构建产物: {local_dist},复制到 {data_dist_path}") + if os.path.exists(data_dist_path): + try: + shutil.rmtree(data_dist_path) + except Exception as e: + logger.warning(f"清理旧的 data/dist 失败: {e}") + shutil.copytree(local_dist, data_dist_path) + return data_dist_path + except Exception as e: + logger.warning(f"使用本地 WebUI 构建产物失败: {e},将尝试使用已有或下载的面板文件") if os.path.exists(data_dist_path): v = await get_dashboard_version() if v is not None: diff --git a/uv.lock b/uv.lock index 3d0499d6d..9bc72d371 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.11'", @@ -213,7 +213,7 @@ wheels = [ [[package]] name = "astrbot" -version = "4.0.0b3" +version = "4.0.0" source = { editable = "." } dependencies = [ { name = "aiocqhttp" }, @@ -288,7 +288,7 @@ requires-dist = [ { name = "deprecated", specifier = ">=1.2.18" }, { name = "dingtalk-stream", specifier = ">=0.22.1" }, { name = "docstring-parser", specifier = ">=0.16" }, - { name = "faiss-cpu", specifier = ">=1.10.0" }, + { name = "faiss-cpu", specifier = "==1.10.0" }, { name = "filelock", specifier = ">=3.18.0" }, { name = "google-genai", specifier = ">=1.14.0" }, { name = "googlesearch-python", specifier = ">=1.3.0" }, @@ -783,62 +783,34 @@ wheels = [ [[package]] name = "faiss-cpu" -version = "1.12.0" +version = "1.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/80/bb75a7ed6e824dea452a24d3434a72ed799324a688b10b047d441d270185/faiss_cpu-1.12.0.tar.gz", hash = "sha256:2f87cbcd603f3ed464ebceb857971fdebc318de938566c9ae2b82beda8e953c0", size = 69292, upload-time = "2025-08-13T06:07:26.553Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/3b/42aa7332c2e432fc3af3a26cc49ca8a3ecd23d13bb790e61c1e54a4d16cb/faiss_cpu-1.12.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:be96f9290edd13d56fb3c69b8dd6be487552b4401f2e95b437cabf5309c424ad", size = 8006082, upload-time = "2025-08-13T06:05:33.131Z" }, - { url = "https://files.pythonhosted.org/packages/00/ab/9959c2d9c3a511a5dbfa4e2e2a1d0bdcad5929d410b3abe87bbed74dcb9b/faiss_cpu-1.12.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0834c547c39d5e5d0b769c90ac5d5ca42e00bcdbba491f3440d2d458058b19d6", size = 3360138, upload-time = "2025-08-13T06:05:35.003Z" }, - { url = "https://files.pythonhosted.org/packages/80/b9/7456f89effe93b7693c7e39cd365065e27aa31794442510c44ad8cce6c4c/faiss_cpu-1.12.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a40830a16d8718b14a462e1a1efaa26660eb3bb8ada22e0712a6ac181092750e", size = 3825459, upload-time = "2025-08-13T06:05:36.546Z" }, - { url = "https://files.pythonhosted.org/packages/f4/0f/02d5d2ae8b53e5629cb03fbd871bbbfbbd647ffc3d09393b34f6347072d7/faiss_cpu-1.12.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7f8732796e3f730556e99327861066ead0ae7e66b5cbf6c0f217be48074e41e", size = 31425823, upload-time = "2025-08-13T06:05:38.778Z" }, - { url = "https://files.pythonhosted.org/packages/d0/39/a9fcb0b82727ab2d5509caa7637e5d345c710502f68c7a7e90dd212654ad/faiss_cpu-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ded5063e13c3bb6b1b463827f838ae45a0aea4c9aeaf6c938e7e87f3f6ea4126", size = 9751939, upload-time = "2025-08-13T06:05:41.816Z" }, - { url = "https://files.pythonhosted.org/packages/bd/25/7efcb5856f9df4c003716687c4604bb5cfc44819539b79d60e302018962b/faiss_cpu-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8e74e71249165757a12fb02feee67ea95df542bcafa21b449fbd2ed0c31b48b4", size = 24160951, upload-time = "2025-08-13T06:05:44.243Z" }, - { url = "https://files.pythonhosted.org/packages/34/d4/1f1cc444708b426b42ec52f01c735d91cb9775fe55cf3d2c64b9a6fd8792/faiss_cpu-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:d04d1cae2a9b66083cd8f48ff391731d81e0a1fdf67ab5c33ae10b3a22a0caae", size = 18169601, upload-time = "2025-08-13T06:05:46.558Z" }, - { url = "https://files.pythonhosted.org/packages/87/ed/83fed257ea410c2e691374f04ac914d5f9414f04a9c7a266bdfbb999eb16/faiss_cpu-1.12.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:fbb63595c7ad43c0d9caaf4d554a38a30ea4edda5e7c3ed38845562776992ba9", size = 8006079, upload-time = "2025-08-13T06:05:48.932Z" }, - { url = "https://files.pythonhosted.org/packages/5b/07/80c248db87ef2e753ad390fca3b0d7dd6092079e904f35b248c7064e791e/faiss_cpu-1.12.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83e74cbde6fa5caceec5bc103c82053d50fde163e3ceabaa58c91508e984142b", size = 3360138, upload-time = "2025-08-13T06:05:50.873Z" }, - { url = "https://files.pythonhosted.org/packages/b9/22/73bd9ed7b11cd14eb0da6e2f2eae763306abaad1b25a5808da8b1fc07665/faiss_cpu-1.12.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6155a5138604b702a32f8f0a63948a539eb7468898554a9911f9ab8c899284fb", size = 3825466, upload-time = "2025-08-13T06:05:52.311Z" }, - { url = "https://files.pythonhosted.org/packages/9e/7f/e1a21337b3cba24b953c760696e3b188a533d724440e050fd60a3c1aa919/faiss_cpu-1.12.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bf4b5f0e9b6bb5a566b1a31e84a93b283f26c2b0155fb2eb5970c32a540a906", size = 31425626, upload-time = "2025-08-13T06:05:54.155Z" }, - { url = "https://files.pythonhosted.org/packages/05/24/f352cf8400f414e6a31385ef12d43d11aac8beb11d573a2fd00ec44b8cb7/faiss_cpu-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60a535b79d3d6225c7c21d7277fb0c6fde80c46a9c1e33632b1b293c1d177f30", size = 9751949, upload-time = "2025-08-13T06:05:56.369Z" }, - { url = "https://files.pythonhosted.org/packages/05/50/a122e3076d7fd95cbe9a0cdf0fc796836f1e4fd399b418c6ba8533c75770/faiss_cpu-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0d1b243468a24564f85a41166f2ca4c92f8f6755da096ffbdcf551675ca739c5", size = 24161021, upload-time = "2025-08-13T06:05:58.776Z" }, - { url = "https://files.pythonhosted.org/packages/72/9f/3344f6fe69f6fbfb19dec298b4dda3d47a87dc31e418911fdcc3a3ace013/faiss_cpu-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:84510079a2efe954e6b89fe5e62f23a98c1ef999756565e056f95f835ff43c5e", size = 18169278, upload-time = "2025-08-13T06:06:01.44Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b1/37d532292c1b3dab690636947a532d3797741b09f2dfb9cb558ffeaff34b/faiss_cpu-1.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:2283f1014f7f86dd56b53bf0ea0d7f848eb4c9c6704b8f4f99a0af02e994e479", size = 8007093, upload-time = "2025-08-13T06:06:03.904Z" }, - { url = "https://files.pythonhosted.org/packages/4a/58/602ed184d35742eb240cbfea237bd214f2ae7f01cb369c39f4dff392f7c9/faiss_cpu-1.12.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:9b54990fcbcf90e37393909d4033520237194263c93ab6dbfae0616ef9af242b", size = 8034413, upload-time = "2025-08-13T06:06:05.564Z" }, - { url = "https://files.pythonhosted.org/packages/83/d5/f84c3d0e022cdeb73ff8406a6834a7698829fa242eb8590ddf8a0b09357f/faiss_cpu-1.12.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a5f5bca7e1a3e0a98480d1e2748fc86d12c28d506173e460e6746886ff0e08de", size = 3362034, upload-time = "2025-08-13T06:06:07.091Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a4ba4d285ea4f9b0824bf31ebded3171da08bfcf5376f4771cc5481f72cd/faiss_cpu-1.12.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:016e391f49933875b8d60d47f282f2e93d8ea9f9ffbda82467aa771b11a237db", size = 3834319, upload-time = "2025-08-13T06:06:08.86Z" }, - { url = "https://files.pythonhosted.org/packages/4c/c9/be4e52fd96be601fefb313c26e1259ac2e6b556fb08cc392db641baba8c7/faiss_cpu-1.12.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2e4963c7188f57cfba248f09ebd8a14c76b5ffb87382603ccd4576f2da39d74", size = 31421585, upload-time = "2025-08-13T06:06:10.643Z" }, - { url = "https://files.pythonhosted.org/packages/4b/aa/12c6723ce30df721a6bace21398559c0367c5418c04139babc2d26d8d158/faiss_cpu-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:88bfe134f8c7cd2dda7df34f2619448906624962c8207efdd6eb1647e2f5338b", size = 9762449, upload-time = "2025-08-13T06:06:13.373Z" }, - { url = "https://files.pythonhosted.org/packages/67/15/ed2c9de47c3ebae980d6938f0ec12d739231438958bc5ab2d636b272d913/faiss_cpu-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9243ee4c224a0d74419040503f22bf067462a040281bf6f3f107ab205c97d438", size = 24156525, upload-time = "2025-08-13T06:06:15.307Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b8/6911de6b8fdcfa76144680c2195df6ce7e0cc920a8be8c5bbd2dfe5e3c37/faiss_cpu-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:6b8012353d50d9bc81bcfe35b226d0e5bfad345fdebe0da31848395ebc83816d", size = 18169636, upload-time = "2025-08-13T06:06:17.613Z" }, - { url = "https://files.pythonhosted.org/packages/2f/69/d2b0f434b0ae35344280346b58d2b9a251609333424f3289c54506e60c51/faiss_cpu-1.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:8b4f5b18cbe335322a51d2785bb044036609c35bfac5915bff95eadc10e89ef1", size = 8012423, upload-time = "2025-08-13T06:06:19.73Z" }, - { url = "https://files.pythonhosted.org/packages/5f/4e/6be5fbd2ceccd87b168c64edeefa469cd11f095bb63b16a61a29296b0fdb/faiss_cpu-1.12.0-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:c9c79b5f28dcf9b2e2557ce51b938b21b7a9d508e008dc1ffea7b8249e7bd443", size = 8034409, upload-time = "2025-08-13T06:06:22.519Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f0/658012a91a690d82f3587fd8e56ea1d9b9698c31970929a9dba17edd211e/faiss_cpu-1.12.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0db6485bc9f32b69aaccf9ad520782371a79904dcfe20b6da5cbfd61a712e85f", size = 3362034, upload-time = "2025-08-13T06:06:24.052Z" }, - { url = "https://files.pythonhosted.org/packages/81/8b/9b355309d448e1a737fac31d45e9b2484ffb0f04f10fba3b544efe6661e4/faiss_cpu-1.12.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6db5532831791d7bac089fc580e741e99869122946bb6a5f120016c83b95d10", size = 3834324, upload-time = "2025-08-13T06:06:25.506Z" }, - { url = "https://files.pythonhosted.org/packages/7e/31/d229f6cdb9cbe03020499d69c4b431b705aa19a55aa0fe698c98022b2fef/faiss_cpu-1.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d57ed7aac048b18809af70350c31acc0fb9f00e6c03b6ed1651fd58b174882d", size = 31421590, upload-time = "2025-08-13T06:06:27.601Z" }, - { url = "https://files.pythonhosted.org/packages/26/19/80289ba008f14c95fbb6e94617ea9884e421ca745864fe6b8b90e1c3fc94/faiss_cpu-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:26c29290e7d1c5938e5886594dc0a2272b30728351ca5f855d4ae30704d5a6cc", size = 9762452, upload-time = "2025-08-13T06:06:30.237Z" }, - { url = "https://files.pythonhosted.org/packages/af/e7/6cc03ead5e19275e34992419e2b7d107d0295390ccf589636ff26adb41e2/faiss_cpu-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b43d0c295e93a8e5f1dd30325caaf34d4ecb51f1e3d461c7b0e71bff3a8944b", size = 24156530, upload-time = "2025-08-13T06:06:32.23Z" }, - { url = "https://files.pythonhosted.org/packages/34/90/438865fe737d65e7348680dadf3b2983bdcef7e5b7e852000e74c50a9933/faiss_cpu-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:a7c6156f1309bb969480280906e8865c3c4378eebb0f840c55c924bf06efd8d3", size = 18169604, upload-time = "2025-08-13T06:06:34.884Z" }, - { url = "https://files.pythonhosted.org/packages/76/69/40a1d8d781a70d33c57ef1b4b777486761dd1c502a86d27e90ef6aa8a9f9/faiss_cpu-1.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:0b5fac98a350774a98b904f7a7c6689eb5cf0a593d63c552e705a80c55636d15", size = 8012523, upload-time = "2025-08-13T06:06:37.24Z" }, - { url = "https://files.pythonhosted.org/packages/12/35/01a4a7c179d67bee0d8a027b95c3eae19cb354ae69ef2bc50ac3b93bc853/faiss_cpu-1.12.0-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:ff7db774968210d08cd0331287f3f66a6ffef955a7aa9a7fcd3eb4432a4ce5f5", size = 8036142, upload-time = "2025-08-13T06:06:38.894Z" }, - { url = "https://files.pythonhosted.org/packages/08/23/bac2859490096608c9d527f3041b44c2e43f8df0d4aadd53a4cc5ce678ac/faiss_cpu-1.12.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:220b5bb5439c64e417b35f9ade4c7dc3bf7df683d6123901ba84d6d764ecd486", size = 3363747, upload-time = "2025-08-13T06:06:40.73Z" }, - { url = "https://files.pythonhosted.org/packages/7b/1d/e18023e1f43a18ec593adcd69d356f1fa94bde20344e38334d5985e5c5cc/faiss_cpu-1.12.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:693d0bf16f79e8d16a1baaeda459f3375f37da0354e97dc032806b48a2a54151", size = 3835232, upload-time = "2025-08-13T06:06:42.172Z" }, - { url = "https://files.pythonhosted.org/packages/cd/2b/1c1fea423d3f550f44c5ec3f14d8400919b49c285c3bd146687c63e40186/faiss_cpu-1.12.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcc6587dee21e17430fb49ddc5200625d6f5e1de2bdf436f14827bad4ca78d19", size = 31432677, upload-time = "2025-08-13T06:06:44.348Z" }, - { url = "https://files.pythonhosted.org/packages/de/d2/3483e92a02f30e2d8491a256f470f54b7f5483266dfe09126d28741d31ec/faiss_cpu-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b80e5965f001822cc99ec65c715169af1b70bdae72eccd573520a2dec485b3ee", size = 9765504, upload-time = "2025-08-13T06:06:46.567Z" }, - { url = "https://files.pythonhosted.org/packages/ce/2f/d97792211a9bd84b8d6b1dcaa1dcd69ac11e026c6ef19c641b6a87e31025/faiss_cpu-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98279f1b4876ef9902695a329b81a99002782ab6e26def472022009df6f1ac68", size = 24169930, upload-time = "2025-08-13T06:06:48.916Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b8/b707ca4d88af472509a053c39d3cced53efd19d096b8dff2fadc18c4b82d/faiss_cpu-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:11670337f9f5ee9ff3490e30683eea80add060c300cf6f6cb0e8faf3155fd20e", size = 18475400, upload-time = "2025-08-13T06:06:51.233Z" }, - { url = "https://files.pythonhosted.org/packages/77/11/42e41ddebde4dfe77e36e92d0110b4f733c8640883abffde54f802482deb/faiss_cpu-1.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:7ac1c8b53609b5c722ab60f1749260a7cb3c72fdfb720a0e3033067e73591da5", size = 8281229, upload-time = "2025-08-13T06:06:53.735Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/8ae5bbeabe70eb673c37fc7c77e2e476746331afb6654b2df97d8b6d380d/faiss_cpu-1.12.0-cp314-cp314t-macosx_13_0_x86_64.whl", hash = "sha256:110b21b7bb4c93c4f1a5eb2ffb8ef99dcdb4725f8ab2e5cd161324e4d981f204", size = 8087247, upload-time = "2025-08-13T06:06:55.407Z" }, - { url = "https://files.pythonhosted.org/packages/f4/df/b3d79098860b67b126da351788c04ac243c29718dadc4a678a6f5e7209c0/faiss_cpu-1.12.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:82eb5515ce72be9a43f4cf74447a0d090e014231981df91aff7251204b506fbf", size = 3411043, upload-time = "2025-08-13T06:06:56.983Z" }, - { url = "https://files.pythonhosted.org/packages/bc/2f/b1a2a03dd3cce22ff9fc434aa3c7390125087260c1d1349311da36eaa432/faiss_cpu-1.12.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:754eef89cdf2b35643df6b0923a5a098bdfecf63b5f4bd86c385042ee511b287", size = 3801789, upload-time = "2025-08-13T06:06:58.688Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a8/16ad0c6a966e93d04bfd5248d2be1d8b5849842b0e2611c5ecd26fcaf036/faiss_cpu-1.12.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7285c71c8f5e9c58b55175f5f74c78c518c52c421a88a430263f34e3e31f719c", size = 31231388, upload-time = "2025-08-13T06:07:00.55Z" }, - { url = "https://files.pythonhosted.org/packages/62/a1/9c16eca0b8f8b13c32c47a5e4ff7a4bc0ca3e7d263140312088811230871/faiss_cpu-1.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84a50d7a2f711f79cc8b65aa28956dba6435e47b71a38b2daea44c94c9b8e458", size = 9737605, upload-time = "2025-08-13T06:07:03.018Z" }, - { url = "https://files.pythonhosted.org/packages/a8/4a/2c2d615078c9d816a836fb893aaef551ad152f2eb00bc258698273c240c0/faiss_cpu-1.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f3e0a14e4edec6a3959a9f51afccb89e863138f184ff2cc24c13f9ad788740b", size = 23922880, upload-time = "2025-08-13T06:07:05.099Z" }, - { url = "https://files.pythonhosted.org/packages/30/aa/99b8402a4dac678794f13f8f4f29d666c2ef0a91594418147f47034ebc81/faiss_cpu-1.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8b3239cc371df6826ac43c62ac04eec7cc497bedb43f681fcd8ea494f520ddbb", size = 18750661, upload-time = "2025-08-13T06:07:07.551Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a2/b546e9a20ba157eb2fbe141289f1752f157ee6d932899f4853df4ded6d4b/faiss_cpu-1.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58b23456db725ee1bd605a6135d2ef55b2ac3e0b6fe873fd99a909e8ef4bd0ff", size = 8302032, upload-time = "2025-08-13T06:07:09.602Z" }, +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/56/87eb506d8634f08fc7c63d1ca5631aeec7d6b9afbfabedf2cb7a2a804b13/faiss_cpu-1.10.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:6693474be296a7142ade1051ea18e7d85cedbfdee4b7eac9c52f83fed0467855", size = 7693034, upload-time = "2025-01-31T07:44:31.908Z" }, + { url = "https://files.pythonhosted.org/packages/51/46/f4d9de34ed1b06300b1a75b824d4857963216f5826de33f291af78088e39/faiss_cpu-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:70ebe60a560414dc8dd6cfe8fed105c8f002c0d11f765f5adfe8d63d42c0467f", size = 3234656, upload-time = "2025-01-31T07:44:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/74/3a/e146861019d9290e0198b3470b8d13a658c3b5f228abefc3658ce0afd63d/faiss_cpu-1.10.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:74c5712d4890f15c661ab7b1b75867812e9596e1469759956fad900999bedbb5", size = 3663789, upload-time = "2025-01-31T07:44:36.698Z" }, + { url = "https://files.pythonhosted.org/packages/aa/40/624f0002bb777e37aac1aadfadec1eb4391be6ad05b7fcfbf66049b99a48/faiss_cpu-1.10.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:473d158fbd638d6ad5fb64469ba79a9f09d3494b5f4e8dfb4f40ce2fc335dca4", size = 30673545, upload-time = "2025-01-31T07:44:40.959Z" }, + { url = "https://files.pythonhosted.org/packages/d6/39/298ffcbefd899e84a43e63df217a6dc800d52bca37ebe0d1155ff367886a/faiss_cpu-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:dcd0cb2ec84698cbe3df9ed247d2392f09bda041ad34b92d38fa916cd019ad4b", size = 13684176, upload-time = "2025-01-31T07:44:45.399Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/81800f41cb2c719c199d3eb534fcc154853123261d841e37482e8e468619/faiss_cpu-1.10.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8ff6924b0f00df278afe70940ae86302066466580724c2f3238860039e9946f1", size = 7693037, upload-time = "2025-01-31T07:44:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/8d/83/fc9028f6d6aec2c2f219f53a5d4a2b279434715643242e59a2e9755b1ce0/faiss_cpu-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb80b530a9ded44a7d4031a7355a237aaa0ff1f150c1176df050e0254ea5f6f6", size = 3234657, upload-time = "2025-01-31T07:44:51.399Z" }, + { url = "https://files.pythonhosted.org/packages/af/45/588a02e60daa73f6052611334fbbdffcedf37122320f1c91cb90f3e69b96/faiss_cpu-1.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7a9fef4039ed877d40e41d5563417b154c7f8cd57621487dad13c4eb4f32515f", size = 3663710, upload-time = "2025-01-31T07:44:53.198Z" }, + { url = "https://files.pythonhosted.org/packages/cb/cf/9caa08ca4e21ab935f82be0713e5d60566140414c3fff7932d9427c8fd72/faiss_cpu-1.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:49b6647aa9e159a2c4603cbff2e1b313becd98ad6e851737ab325c74fe8e0278", size = 30673629, upload-time = "2025-01-31T07:44:56.652Z" }, + { url = "https://files.pythonhosted.org/packages/2c/2d/d2a4171a9cca9a7c04cd9d6f9441a37f1e0558724b90bf7fc7db08553601/faiss_cpu-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:6f8c0ef8b615c12c7bf612bd1fc51cffa49c1ddaa6207c6981f01ab6782e6b3b", size = 13683966, upload-time = "2025-01-31T07:45:01.098Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cc/f6aa1288dbb40b2a4f101d16900885e056541f37d8d08ec70462e92cf277/faiss_cpu-1.10.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:2aca486fe2d680ea64a18d356206c91ff85db99fd34c19a757298c67c23262b1", size = 7720242, upload-time = "2025-01-31T07:45:03.871Z" }, + { url = "https://files.pythonhosted.org/packages/be/56/40901306324a17fbc1eee8a6e86ba67bd99a67e768ce9908f271e648e9e0/faiss_cpu-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1108a4059c66c37c403183e566ca1ed0974a6af7557c92d49207639aab661bc", size = 3239223, upload-time = "2025-01-31T07:45:06.585Z" }, + { url = "https://files.pythonhosted.org/packages/2e/34/5b1463c450c9a6de3109caf8f38fbf0c329ef940ed1973fcf8c8ec7fa27e/faiss_cpu-1.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:449f3eb778d6d937e01a16a3170de4bb8aabfe87c7cb479b458fb790276310c5", size = 3671461, upload-time = "2025-01-31T07:45:09.099Z" }, + { url = "https://files.pythonhosted.org/packages/78/d9/0b78c474289f23b31283d8fb64c8e6a522a7fa47b131a3c6c141c8e6639d/faiss_cpu-1.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9899c340f92bd94071d6faf4bef0ccb5362843daea42144d4ba857a2a1f67511", size = 30663859, upload-time = "2025-01-31T07:45:13.027Z" }, + { url = "https://files.pythonhosted.org/packages/17/f0/194727b9e6e282e2877bc001ba886228f6af52e9a6730bbdb223e38591c3/faiss_cpu-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:345a52dbfa980d24b93c94410eadf82d1eef359c6a42e5e0768cca96539f1c3c", size = 13687087, upload-time = "2025-01-31T07:45:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/93/25/23239a83142faa319c4f8c025e25fec6cccc7418995eba3515218a57a45b/faiss_cpu-1.10.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:cb8473d69c3964c1bf3f8eb3e04287bb3275f536e6d9635ef32242b5f506b45d", size = 7720240, upload-time = "2025-01-31T07:45:19.943Z" }, + { url = "https://files.pythonhosted.org/packages/18/f1/0e979277831af337739dbacf386d8a359a05eef9642df23d36e6c7d1b1a9/faiss_cpu-1.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82ca5098de694e7b8495c1a8770e2c08df6e834922546dad0ae1284ff519ced6", size = 3239224, upload-time = "2025-01-31T07:45:21.744Z" }, + { url = "https://files.pythonhosted.org/packages/bd/fa/c2ad85b017a5754f6cdb09c179f8c4f4198d2a264046a8daa7a4d080521f/faiss_cpu-1.10.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:035e4d797e2db7fc0d0c90531d4a655d089ad5d1382b7a49358c1f2307b3a309", size = 3671236, upload-time = "2025-01-31T07:45:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9b/759962f2c34800058f6a76457df3b0ab93b24f383650ea1ef0231acd322c/faiss_cpu-1.10.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e02af3696a6b9e1f9072e502f48095a305de2163c42ceb1f6f6b1db9e7ffe574", size = 30663948, upload-time = "2025-01-31T07:45:27.271Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9a/6c496e0189897761978653177386452d62f4060579413d109bff05f458f2/faiss_cpu-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:e71f7e24d5b02d3a51df47b77bd10f394a1b48a8331d5c817e71e9e27a8a75ac", size = 13687212, upload-time = "2025-01-31T07:45:32.198Z" }, ] [[package]]