Init Gitea
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
.DS_Store
|
||||
|
||||
BIN
etc/test-data/SisAi-About-en.pptx
Normal file
39
etc/test-data/mongodb.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
MongoDB is a popular, open-source NoSQL database management system that is designed to store and retrieve large volumes of data with high performance and flexibility. Unlike traditional relational databases (SQL databases) that use structured tables and rows, MongoDB stores data in a flexible, JSON-like format called BSON (Binary JSON). This allows it to handle unstructured or semi-structured data more efficiently.
|
||||
|
||||
### Key Features of MongoDB:
|
||||
|
||||
1. **Document-Oriented Storage**:
|
||||
- **Flexible Schema**: MongoDB stores data in documents, which are JSON-like structures (in BSON format). Each document can have a different structure, meaning the database schema is flexible and can evolve over time without requiring a complex migration process.
|
||||
- **Collections**: Documents are grouped into collections, similar to tables in a relational database, but collections do not enforce a fixed schema.
|
||||
|
||||
2. **Scalability**:
|
||||
- **Horizontal Scaling (Sharding)**: MongoDB supports horizontal scaling through a process called sharding, where data is distributed across multiple servers. This makes it easier to scale out and handle large amounts of data and high traffic.
|
||||
- **Replication**: MongoDB offers replica sets, which are groups of MongoDB instances that maintain the same data set, providing redundancy and high availability.
|
||||
|
||||
3. **High Performance**:
|
||||
- **Indexing**: MongoDB allows for the creation of indexes on any field in a document, which can improve query performance.
|
||||
- **Efficient Queries**: MongoDB supports a wide range of query types, including range queries, text search, and geospatial queries, which can be executed efficiently due to its indexing capabilities.
|
||||
|
||||
4. **Aggregation Framework**:
|
||||
- **Data Processing**: MongoDB includes a powerful aggregation framework that allows you to perform operations like filtering, grouping, and transforming data within the database, which is useful for generating reports and performing complex data analysis.
|
||||
|
||||
5. **Ease of Use**:
|
||||
- **Developer-Friendly**: MongoDB’s query language is based on JavaScript, making it easy to work with for developers familiar with JSON and JavaScript.
|
||||
- **Rich Ecosystem**: MongoDB has a rich ecosystem of tools and libraries, including drivers for many programming languages, a cloud-based service called MongoDB Atlas, and other tools for data visualization, monitoring, and backup.
|
||||
|
||||
6. **ACID Transactions**:
|
||||
- **Transactions**: MongoDB supports multi-document ACID (Atomicity, Consistency, Isolation, Durability) transactions, which provide more robust guarantees for operations that need to be executed as a single unit of work.
|
||||
|
||||
### Use Cases:
|
||||
- **Content Management Systems (CMS)**: MongoDB’s flexible schema makes it ideal for managing varied content types, like text, images, and videos.
|
||||
- **Real-Time Analytics**: Its high performance and scalability are well-suited for real-time analytics, such as monitoring and processing data streams.
|
||||
- **E-commerce**: MongoDB is often used in e-commerce platforms for handling product catalogs, customer profiles, and orders, which require flexible and fast data access.
|
||||
- **Internet of Things (IoT)**: MongoDB can efficiently store and process the large volumes of unstructured or semi-structured data generated by IoT devices.
|
||||
|
||||
### Comparison with Relational Databases:
|
||||
- **Schema Flexibility**: Unlike relational databases, MongoDB does not require a predefined schema, allowing for more flexibility in how data is stored and structured.
|
||||
- **Scalability**: MongoDB is designed to scale out by distributing data across multiple servers, which is generally more complex with relational databases.
|
||||
- **Data Relationships**: MongoDB uses embedded documents and references to represent relationships between data, rather than the traditional foreign key relationships in relational databases.
|
||||
|
||||
### Summary:
|
||||
MongoDB is a powerful, flexible, and scalable NoSQL database system, particularly suited for modern applications that require handling large volumes of diverse and unstructured data. Its ability to scale horizontally, along with its flexible document-based model, makes it a popular choice for a wide range of applications, from simple data storage to complex, large-scale data processing.
|
||||
48
etc/test-data/tfjs-diff.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
The key differences between TensorFlow.js and TensorFlow (commonly referred to as TensorFlow Python, as it's primarily used in Python) lie in their environments, use cases, performance capabilities, and how they interact with hardware. Here’s a breakdown of these differences:
|
||||
|
||||
### 1. **Environment and Usage**
|
||||
- **TensorFlow.js**:
|
||||
- **Environment**: Runs in JavaScript environments such as web browsers and Node.js.
|
||||
- **Usage**: Used primarily for web applications, client-side ML tasks, and JavaScript-based environments.
|
||||
- **Language**: Written in JavaScript/TypeScript.
|
||||
|
||||
- **TensorFlow**:
|
||||
- **Environment**: Primarily used in Python environments, although it also supports C++, Java, and other languages.
|
||||
- **Usage**: Commonly used in server-side applications, large-scale machine learning tasks, and research in AI. TensorFlow is a full-fledged machine learning library used for a wide range of applications from training deep learning models on large datasets to deploying models in production.
|
||||
- **Language**: Written in Python (with underlying C++ for performance-critical parts).
|
||||
|
||||
### 2. **Performance**
|
||||
- **TensorFlow.js**:
|
||||
- **Hardware Acceleration**: Utilizes WebGL for GPU acceleration in browsers, which provides decent performance for ML tasks, especially in real-time web applications. In Node.js, it can use the system’s GPU via the `tfjs-node-gpu` package.
|
||||
- **Performance Limitations**: While capable, TensorFlow.js is generally not as powerful as TensorFlow when dealing with very large datasets or extremely deep models due to the limitations of browser environments and JavaScript performance.
|
||||
|
||||
- **TensorFlow**:
|
||||
- **Hardware Acceleration**: Provides full support for GPUs, TPUs (Tensor Processing Units), and multi-core CPUs. TensorFlow can leverage CUDA (for NVIDIA GPUs) and other specialized hardware, making it highly performant for training and deploying complex deep learning models.
|
||||
- **Scalability**: TensorFlow is designed for scalability and high-performance computing, suitable for large datasets and complex models in production environments.
|
||||
|
||||
### 3. **Model Training and Deployment**
|
||||
- **TensorFlow.js**:
|
||||
- **Training**: Allows for in-browser or Node.js-based training, which is convenient for interactive or real-time applications but is generally limited to smaller models or datasets.
|
||||
- **Deployment**: Primarily used for deploying models in web applications where running the model on the client side is advantageous.
|
||||
- **Model Conversion**: You can convert TensorFlow models (originally trained in Python) to a format that TensorFlow.js can use (`.json` format).
|
||||
|
||||
- **TensorFlow**:
|
||||
- **Training**: Supports training on large datasets using distributed computing, advanced optimization algorithms, and robust support for various hardware accelerators.
|
||||
- **Deployment**: TensorFlow models are often deployed in production environments, either on servers, in cloud services, or on specialized devices like TPUs or edge devices using TensorFlow Lite.
|
||||
|
||||
### 4. **Ecosystem and Libraries**
|
||||
- **TensorFlow.js**:
|
||||
- **Ecosystem**: Includes a subset of TensorFlow functionalities, focusing on operations and models that are most relevant to web and JavaScript environments. It also integrates well with web development tools and frameworks.
|
||||
- **Libraries**: Provides specific tools for web-based ML tasks, such as the TensorFlow.js Layers API, which is similar to Keras (in TensorFlow), and tools for data manipulation that are optimized for JavaScript.
|
||||
|
||||
- **TensorFlow**:
|
||||
- **Ecosystem**: Extensive ecosystem including TensorFlow Hub, TensorFlow Lite (for mobile/embedded devices), TensorFlow Extended (for production pipelines), and TensorFlow Serving (for deploying models at scale). The library is vast, supporting a wide range of ML tasks and research.
|
||||
- **Libraries**: Offers a wide array of libraries for various tasks like TensorFlow Probability, TensorFlow Addons, and others, all optimized for different aspects of machine learning.
|
||||
|
||||
### 5. **Community and Support**
|
||||
- **TensorFlow.js**: Has a growing community, particularly among web developers and those working on front-end ML tasks.
|
||||
- **TensorFlow**: Has a large and mature community, with extensive resources, documentation, and support available for a wide range of machine learning applications.
|
||||
|
||||
### Summary
|
||||
- **TensorFlow.js** is ideal for web developers looking to integrate machine learning into web applications, with a focus on client-side execution and real-time interaction.
|
||||
- **TensorFlow** is better suited for heavy-duty machine learning tasks, including research, training large models, and deploying models in production environments, particularly where performance and scalability are critical.
|
||||
135
etc/test-data/tfjs-training.txt
Normal file
@@ -0,0 +1,135 @@
|
||||
Training a model in TensorFlow.js involves several steps, similar to training a model in the regular TensorFlow library. Here’s a step-by-step guide on how to do it:
|
||||
|
||||
### 1. **Set Up Your Environment**
|
||||
- Include TensorFlow.js in your project. If you are using it in a web browser, you can include it via a CDN:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
|
||||
```
|
||||
|
||||
- If you are using Node.js, install TensorFlow.js using npm:
|
||||
|
||||
```bash
|
||||
npm install @tensorflow/tfjs
|
||||
```
|
||||
|
||||
### 2. **Prepare Your Data**
|
||||
- Data in TensorFlow.js is represented as `tf.Tensor` objects. You can create tensors manually, load data from files, or use existing datasets.
|
||||
|
||||
Example of creating tensors:
|
||||
```javascript
|
||||
const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]);
|
||||
const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);
|
||||
```
|
||||
|
||||
- Alternatively, you can load data from an external source:
|
||||
|
||||
```javascript
|
||||
const data = tf.data.csv('path/to/your/csvfile.csv');
|
||||
```
|
||||
|
||||
### 3. **Define Your Model**
|
||||
- Create a sequential model and add layers to it. In TensorFlow.js, you can use high-level APIs similar to Keras:
|
||||
|
||||
```javascript
|
||||
const model = tf.sequential();
|
||||
model.add(tf.layers.dense({units: 1, inputShape: [1]}));
|
||||
```
|
||||
|
||||
- You can add more layers depending on your problem:
|
||||
|
||||
```javascript
|
||||
model.add(tf.layers.dense({units: 10, activation: 'relu'}));
|
||||
model.add(tf.layers.dense({units: 1}));
|
||||
```
|
||||
|
||||
### 4. **Compile the Model**
|
||||
- After defining the model, you need to compile it by specifying the optimizer, loss function, and optionally, metrics:
|
||||
|
||||
```javascript
|
||||
model.compile({
|
||||
optimizer: 'sgd',
|
||||
loss: 'meanSquaredError',
|
||||
metrics: ['mse']
|
||||
});
|
||||
```
|
||||
|
||||
### 5. **Train the Model**
|
||||
- Now, you can train the model using the `fit` method. This method is similar to TensorFlow in Python:
|
||||
|
||||
```javascript
|
||||
model.fit(xs, ys, {
|
||||
epochs: 100,
|
||||
callbacks: {
|
||||
onEpochEnd: (epoch, logs) => {
|
||||
console.log(`Epoch: ${epoch}, Loss: ${logs.loss}`);
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
console.log('Training complete');
|
||||
});
|
||||
```
|
||||
|
||||
- Here, `xs` is your input data (features), and `ys` is the target data (labels). The `epochs` parameter controls how many times the model sees the data during training.
|
||||
|
||||
### 6. **Evaluate the Model**
|
||||
- After training, you can evaluate the model on test data or use it to make predictions:
|
||||
|
||||
```javascript
|
||||
const output = model.predict(tf.tensor2d([5], [1, 1]));
|
||||
output.print(); // Display the prediction for input 5
|
||||
```
|
||||
|
||||
- For evaluating, if you have test data:
|
||||
|
||||
```javascript
|
||||
const loss = model.evaluate(xs_test, ys_test);
|
||||
loss.print(); // Print the loss on test data
|
||||
```
|
||||
|
||||
### 7. **Save or Load a Model**
|
||||
- After training, you might want to save your model:
|
||||
|
||||
```javascript
|
||||
await model.save('localstorage://my-model');
|
||||
```
|
||||
|
||||
- You can load a saved model later:
|
||||
|
||||
```javascript
|
||||
const model = await tf.loadLayersModel('localstorage://my-model');
|
||||
```
|
||||
|
||||
### 8. **Deploy the Model**
|
||||
- Once trained, you can deploy your model in a web application or a Node.js environment, making predictions in real-time.
|
||||
|
||||
### Example: Simple Linear Regression in TensorFlow.js
|
||||
Here’s a small complete example:
|
||||
|
||||
```javascript
|
||||
const tf = require('@tensorflow/tfjs');
|
||||
|
||||
// Define a model
|
||||
const model = tf.sequential();
|
||||
model.add(tf.layers.dense({units: 1, inputShape: [1]}));
|
||||
|
||||
// Compile the model
|
||||
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});
|
||||
|
||||
// Prepare the training data
|
||||
const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]);
|
||||
const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);
|
||||
|
||||
// Train the model
|
||||
model.fit(xs, ys, {epochs: 500}).then(() => {
|
||||
// Use the model to make predictions
|
||||
model.predict(tf.tensor2d([5], [1, 1])).print(); // Should output a value close to 9
|
||||
});
|
||||
```
|
||||
|
||||
### Key Considerations
|
||||
- **Data Handling**: Ensure your data is properly normalized or preprocessed as needed.
|
||||
- **Model Complexity**: TensorFlow.js is powerful but may not handle extremely complex models or very large datasets as efficiently as TensorFlow in Python.
|
||||
- **WebGL Acceleration**: When running in the browser, ensure that WebGL is available and enabled for GPU acceleration.
|
||||
|
||||
By following these steps, you can effectively train machine learning models directly in JavaScript using TensorFlow.js.
|
||||
21
etc/test-data/what-is-tfjs.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
TensorFlow.js is an open-source JavaScript library developed by Google that enables machine learning (ML) models to be defined, trained, and run directly in the browser or in Node.js. This allows developers to leverage the power of machine learning in JavaScript environments, making it possible to build and deploy ML models in web applications, on the server side, or even in mobile hybrid applications.
|
||||
|
||||
### Key Features of TensorFlow.js:
|
||||
1. **In-Browser Machine Learning**: TensorFlow.js allows for training and deploying ML models entirely in the browser. This makes it possible to leverage the user's hardware (like the GPU) for accelerated computations, reducing the need for server resources and enabling real-time interactive applications.
|
||||
|
||||
2. **Node.js Support**: TensorFlow.js can also run in Node.js, which allows for server-side machine learning or running ML tasks in environments where Python might not be available or preferred.
|
||||
|
||||
3. **Pre-trained Models**: TensorFlow.js offers a variety of pre-trained models that can be easily used out-of-the-box for tasks such as image classification, object detection, and text processing.
|
||||
|
||||
4. **Model Conversion**: It provides tools to convert TensorFlow or Keras models (originally written in Python) to a format that can be run in JavaScript. This means that models trained in Python can be deployed in web or Node.js environments.
|
||||
|
||||
5. **Custom Models**: Developers can build, train, and fine-tune custom machine learning models directly in JavaScript, leveraging the full capabilities of the TensorFlow.js library.
|
||||
|
||||
6. **Hardware Acceleration**: TensorFlow.js automatically uses WebGL to accelerate computations in the browser, enabling better performance on compatible devices.
|
||||
|
||||
### Use Cases:
|
||||
- **Web-based ML applications**: Such as image recognition, sentiment analysis, or real-time object detection within web pages.
|
||||
- **Server-side ML**: Running or training models in a Node.js environment.
|
||||
- **Hybrid Mobile Apps**: Integrating machine learning models into mobile applications that use web technologies.
|
||||
|
||||
TensorFlow.js opens up opportunities to integrate machine learning into a wide range of applications where Python might not be ideal, especially in environments that prioritize JavaScript.
|
||||
660
v-search.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,660 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
25057BBA2C8D1BF1007D6724 /* SimilaritySearchKit in Frameworks */ = {isa = PBXBuildFile; productRef = 25057BB92C8D1BF1007D6724 /* SimilaritySearchKit */; };
|
||||
25057BBD2C8D1C34007D6724 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 25057BBC2C8D1C34007D6724 /* ZIPFoundation */; };
|
||||
25057BC12C8D33F6007D6724 /* AppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25057BC02C8D33F6007D6724 /* AppViewModel.swift */; };
|
||||
DE418C042C8C6DE500EEB973 /* v_searchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE418C032C8C6DE500EEB973 /* v_searchApp.swift */; };
|
||||
DE418C062C8C6DE500EEB973 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE418C052C8C6DE500EEB973 /* ContentView.swift */; };
|
||||
DE418C082C8C6DE600EEB973 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DE418C072C8C6DE600EEB973 /* Assets.xcassets */; };
|
||||
DE418C0B2C8C6DE600EEB973 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DE418C0A2C8C6DE600EEB973 /* Preview Assets.xcassets */; };
|
||||
DE418C162C8C6DE600EEB973 /* v_searchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE418C152C8C6DE600EEB973 /* v_searchTests.swift */; };
|
||||
DE418C202C8C6DE600EEB973 /* v_searchUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE418C1F2C8C6DE600EEB973 /* v_searchUITests.swift */; };
|
||||
DE418C222C8C6DE600EEB973 /* v_searchUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE418C212C8C6DE600EEB973 /* v_searchUITestsLaunchTests.swift */; };
|
||||
DE418C342C8C6EDE00EEB973 /* FolderConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE418C2E2C8C6EDE00EEB973 /* FolderConfigView.swift */; };
|
||||
DE418C352C8C6EDE00EEB973 /* LanguageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE418C2F2C8C6EDE00EEB973 /* LanguageManager.swift */; };
|
||||
DE418C362C8C6EDE00EEB973 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DE418C302C8C6EDE00EEB973 /* Localizable.xcstrings */; };
|
||||
DE418C372C8C6EDE00EEB973 /* VectorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE418C312C8C6EDE00EEB973 /* VectorManager.swift */; };
|
||||
DE418C392C8C6EDE00EEB973 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE418C332C8C6EDE00EEB973 /* Logger.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
DE418C122C8C6DE600EEB973 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DE418BF82C8C6DE500EEB973 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = DE418BFF2C8C6DE500EEB973;
|
||||
remoteInfo = "v-search";
|
||||
};
|
||||
DE418C1C2C8C6DE600EEB973 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DE418BF82C8C6DE500EEB973 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = DE418BFF2C8C6DE500EEB973;
|
||||
remoteInfo = "v-search";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
25057BC02C8D33F6007D6724 /* AppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewModel.swift; sourceTree = "<group>"; };
|
||||
DE418C002C8C6DE500EEB973 /* v-search.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "v-search.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DE418C032C8C6DE500EEB973 /* v_searchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = v_searchApp.swift; sourceTree = "<group>"; };
|
||||
DE418C052C8C6DE500EEB973 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
DE418C072C8C6DE600EEB973 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
DE418C0A2C8C6DE600EEB973 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
DE418C0C2C8C6DE600EEB973 /* v_search.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = v_search.entitlements; sourceTree = "<group>"; };
|
||||
DE418C112C8C6DE600EEB973 /* v-searchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "v-searchTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DE418C152C8C6DE600EEB973 /* v_searchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = v_searchTests.swift; sourceTree = "<group>"; };
|
||||
DE418C1B2C8C6DE600EEB973 /* v-searchUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "v-searchUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DE418C1F2C8C6DE600EEB973 /* v_searchUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = v_searchUITests.swift; sourceTree = "<group>"; };
|
||||
DE418C212C8C6DE600EEB973 /* v_searchUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = v_searchUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||
DE418C2E2C8C6EDE00EEB973 /* FolderConfigView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderConfigView.swift; sourceTree = "<group>"; };
|
||||
DE418C2F2C8C6EDE00EEB973 /* LanguageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageManager.swift; sourceTree = "<group>"; };
|
||||
DE418C302C8C6EDE00EEB973 /* Localizable.xcstrings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||
DE418C312C8C6EDE00EEB973 /* VectorManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VectorManager.swift; sourceTree = "<group>"; };
|
||||
DE418C332C8C6EDE00EEB973 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
DE418BFD2C8C6DE500EEB973 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
25057BBA2C8D1BF1007D6724 /* SimilaritySearchKit in Frameworks */,
|
||||
25057BBD2C8D1C34007D6724 /* ZIPFoundation in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DE418C0E2C8C6DE600EEB973 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DE418C182C8C6DE600EEB973 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
DE418BF72C8C6DE500EEB973 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DE418C022C8C6DE500EEB973 /* v-search */,
|
||||
DE418C142C8C6DE600EEB973 /* v-searchTests */,
|
||||
DE418C1E2C8C6DE600EEB973 /* v-searchUITests */,
|
||||
DE418C012C8C6DE500EEB973 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DE418C012C8C6DE500EEB973 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DE418C002C8C6DE500EEB973 /* v-search.app */,
|
||||
DE418C112C8C6DE600EEB973 /* v-searchTests.xctest */,
|
||||
DE418C1B2C8C6DE600EEB973 /* v-searchUITests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DE418C022C8C6DE500EEB973 /* v-search */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DE418C2E2C8C6EDE00EEB973 /* FolderConfigView.swift */,
|
||||
DE418C2F2C8C6EDE00EEB973 /* LanguageManager.swift */,
|
||||
DE418C302C8C6EDE00EEB973 /* Localizable.xcstrings */,
|
||||
DE418C332C8C6EDE00EEB973 /* Logger.swift */,
|
||||
DE418C312C8C6EDE00EEB973 /* VectorManager.swift */,
|
||||
DE418C032C8C6DE500EEB973 /* v_searchApp.swift */,
|
||||
DE418C052C8C6DE500EEB973 /* ContentView.swift */,
|
||||
DE418C072C8C6DE600EEB973 /* Assets.xcassets */,
|
||||
DE418C0C2C8C6DE600EEB973 /* v_search.entitlements */,
|
||||
DE418C092C8C6DE600EEB973 /* Preview Content */,
|
||||
25057BC02C8D33F6007D6724 /* AppViewModel.swift */,
|
||||
);
|
||||
path = "v-search";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DE418C092C8C6DE600EEB973 /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DE418C0A2C8C6DE600EEB973 /* Preview Assets.xcassets */,
|
||||
);
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DE418C142C8C6DE600EEB973 /* v-searchTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DE418C152C8C6DE600EEB973 /* v_searchTests.swift */,
|
||||
);
|
||||
path = "v-searchTests";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DE418C1E2C8C6DE600EEB973 /* v-searchUITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DE418C1F2C8C6DE600EEB973 /* v_searchUITests.swift */,
|
||||
DE418C212C8C6DE600EEB973 /* v_searchUITestsLaunchTests.swift */,
|
||||
);
|
||||
path = "v-searchUITests";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
DE418BFF2C8C6DE500EEB973 /* v-search */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DE418C252C8C6DE600EEB973 /* Build configuration list for PBXNativeTarget "v-search" */;
|
||||
buildPhases = (
|
||||
DE418BFC2C8C6DE500EEB973 /* Sources */,
|
||||
DE418BFD2C8C6DE500EEB973 /* Frameworks */,
|
||||
DE418BFE2C8C6DE500EEB973 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "v-search";
|
||||
packageProductDependencies = (
|
||||
25057BB92C8D1BF1007D6724 /* SimilaritySearchKit */,
|
||||
25057BBC2C8D1C34007D6724 /* ZIPFoundation */,
|
||||
);
|
||||
productName = "v-search";
|
||||
productReference = DE418C002C8C6DE500EEB973 /* v-search.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
DE418C102C8C6DE600EEB973 /* v-searchTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DE418C282C8C6DE600EEB973 /* Build configuration list for PBXNativeTarget "v-searchTests" */;
|
||||
buildPhases = (
|
||||
DE418C0D2C8C6DE600EEB973 /* Sources */,
|
||||
DE418C0E2C8C6DE600EEB973 /* Frameworks */,
|
||||
DE418C0F2C8C6DE600EEB973 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
DE418C132C8C6DE600EEB973 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "v-searchTests";
|
||||
productName = "v-searchTests";
|
||||
productReference = DE418C112C8C6DE600EEB973 /* v-searchTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
DE418C1A2C8C6DE600EEB973 /* v-searchUITests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DE418C2B2C8C6DE600EEB973 /* Build configuration list for PBXNativeTarget "v-searchUITests" */;
|
||||
buildPhases = (
|
||||
DE418C172C8C6DE600EEB973 /* Sources */,
|
||||
DE418C182C8C6DE600EEB973 /* Frameworks */,
|
||||
DE418C192C8C6DE600EEB973 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
DE418C1D2C8C6DE600EEB973 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "v-searchUITests";
|
||||
productName = "v-searchUITests";
|
||||
productReference = DE418C1B2C8C6DE600EEB973 /* v-searchUITests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.ui-testing";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
DE418BF82C8C6DE500EEB973 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1540;
|
||||
LastUpgradeCheck = 1540;
|
||||
TargetAttributes = {
|
||||
DE418BFF2C8C6DE500EEB973 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
};
|
||||
DE418C102C8C6DE600EEB973 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
TestTargetID = DE418BFF2C8C6DE500EEB973;
|
||||
};
|
||||
DE418C1A2C8C6DE600EEB973 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
TestTargetID = DE418BFF2C8C6DE500EEB973;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = DE418BFB2C8C6DE500EEB973 /* Build configuration list for PBXProject "v-search" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = DE418BF72C8C6DE500EEB973;
|
||||
packageReferences = (
|
||||
25057BB82C8D1BF1007D6724 /* XCRemoteSwiftPackageReference "similarity-search-kit" */,
|
||||
25057BBB2C8D1C34007D6724 /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
|
||||
);
|
||||
productRefGroup = DE418C012C8C6DE500EEB973 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
DE418BFF2C8C6DE500EEB973 /* v-search */,
|
||||
DE418C102C8C6DE600EEB973 /* v-searchTests */,
|
||||
DE418C1A2C8C6DE600EEB973 /* v-searchUITests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
DE418BFE2C8C6DE500EEB973 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DE418C0B2C8C6DE600EEB973 /* Preview Assets.xcassets in Resources */,
|
||||
DE418C082C8C6DE600EEB973 /* Assets.xcassets in Resources */,
|
||||
DE418C362C8C6EDE00EEB973 /* Localizable.xcstrings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DE418C0F2C8C6DE600EEB973 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DE418C192C8C6DE600EEB973 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
DE418BFC2C8C6DE500EEB973 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DE418C372C8C6EDE00EEB973 /* VectorManager.swift in Sources */,
|
||||
DE418C342C8C6EDE00EEB973 /* FolderConfigView.swift in Sources */,
|
||||
25057BC12C8D33F6007D6724 /* AppViewModel.swift in Sources */,
|
||||
DE418C062C8C6DE500EEB973 /* ContentView.swift in Sources */,
|
||||
DE418C392C8C6EDE00EEB973 /* Logger.swift in Sources */,
|
||||
DE418C042C8C6DE500EEB973 /* v_searchApp.swift in Sources */,
|
||||
DE418C352C8C6EDE00EEB973 /* LanguageManager.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DE418C0D2C8C6DE600EEB973 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DE418C162C8C6DE600EEB973 /* v_searchTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DE418C172C8C6DE600EEB973 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DE418C222C8C6DE600EEB973 /* v_searchUITestsLaunchTests.swift in Sources */,
|
||||
DE418C202C8C6DE600EEB973 /* v_searchUITests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
DE418C132C8C6DE600EEB973 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = DE418BFF2C8C6DE500EEB973 /* v-search */;
|
||||
targetProxy = DE418C122C8C6DE600EEB973 /* PBXContainerItemProxy */;
|
||||
};
|
||||
DE418C1D2C8C6DE600EEB973 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = DE418BFF2C8C6DE500EEB973 /* v-search */;
|
||||
targetProxy = DE418C1C2C8C6DE600EEB973 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
DE418C232C8C6DE600EEB973 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DE418C242C8C6DE600EEB973 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
DE418C262C8C6DE600EEB973 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "v-search/v_search.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"v-search/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = RQQ6N82NA8;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "v-search";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dqj.vectorsearch;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DE418C272C8C6DE600EEB973 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "v-search/v_search.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"v-search/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = RQQ6N82NA8;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "v-search";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dqj.vectorsearch;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
DE418C292C8C6DE600EEB973 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = RQQ6N82NA8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.5;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "dqj.v-searchTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/v-search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/v-search";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DE418C2A2C8C6DE600EEB973 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = RQQ6N82NA8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.5;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "dqj.v-searchTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/v-search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/v-search";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
DE418C2C2C8C6DE600EEB973 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = RQQ6N82NA8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "dqj.v-searchUITests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_TARGET_NAME = "v-search";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DE418C2D2C8C6DE600EEB973 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = RQQ6N82NA8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "dqj.v-searchUITests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_TARGET_NAME = "v-search";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
DE418BFB2C8C6DE500EEB973 /* Build configuration list for PBXProject "v-search" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DE418C232C8C6DE600EEB973 /* Debug */,
|
||||
DE418C242C8C6DE600EEB973 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
DE418C252C8C6DE600EEB973 /* Build configuration list for PBXNativeTarget "v-search" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DE418C262C8C6DE600EEB973 /* Debug */,
|
||||
DE418C272C8C6DE600EEB973 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
DE418C282C8C6DE600EEB973 /* Build configuration list for PBXNativeTarget "v-searchTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DE418C292C8C6DE600EEB973 /* Debug */,
|
||||
DE418C2A2C8C6DE600EEB973 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
DE418C2B2C8C6DE600EEB973 /* Build configuration list for PBXNativeTarget "v-searchUITests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DE418C2C2C8C6DE600EEB973 /* Debug */,
|
||||
DE418C2D2C8C6DE600EEB973 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
25057BB82C8D1BF1007D6724 /* XCRemoteSwiftPackageReference "similarity-search-kit" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/ZachNagengast/similarity-search-kit";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.0.15;
|
||||
};
|
||||
};
|
||||
25057BBB2C8D1C34007D6724 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/weichsel/ZIPFoundation";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.9.19;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
25057BB92C8D1BF1007D6724 /* SimilaritySearchKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 25057BB82C8D1BF1007D6724 /* XCRemoteSwiftPackageReference "similarity-search-kit" */;
|
||||
productName = SimilaritySearchKit;
|
||||
};
|
||||
25057BBC2C8D1C34007D6724 /* ZIPFoundation */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 25057BBB2C8D1C34007D6724 /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
|
||||
productName = ZIPFoundation;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = DE418BF82C8C6DE500EEB973 /* Project object */;
|
||||
}
|
||||
7
v-search.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"originHash" : "d4c369718117691014a6fcccc278adf90f1e5cec8136acbde5c0af52a75444e9",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "similarity-search-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/ZachNagengast/similarity-search-kit.git",
|
||||
"state" : {
|
||||
"revision" : "9bec54706c8124bb9b20a3a06d566066a9f55712",
|
||||
"version" : "0.0.15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "zipfoundation",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/weichsel/ZIPFoundation",
|
||||
"state" : {
|
||||
"revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0",
|
||||
"version" : "0.9.19"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
18
v-search/AppViewModel.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// AppViewModel.swift
|
||||
// v-search
|
||||
//
|
||||
// Created by dqj on 2024/09/08.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
class AppViewModel: ObservableObject {
|
||||
@Published var statusMessage: String = ""
|
||||
|
||||
func updateStatusMessage(_ newMessage: String) {
|
||||
statusMessage = newMessage
|
||||
}
|
||||
}
|
||||
|
||||
11
v-search/Assets.xcassets/AccentColor.colorset/Contents.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
68
v-search/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_16x16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_32x32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_32x32 1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_64x64.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_128x128.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_256x256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_256x256 1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_512x512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_512x512 1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_1024x1024.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
v-search/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png
Normal file
|
After Width: | Height: | Size: 874 KiB |
BIN
v-search/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
v-search/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 904 B |
BIN
v-search/Assets.xcassets/AppIcon.appiconset/icon_256x256 1.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
v-search/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
v-search/Assets.xcassets/AppIcon.appiconset/icon_32x32 1.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
v-search/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
v-search/Assets.xcassets/AppIcon.appiconset/icon_512x512 1.png
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
v-search/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
v-search/Assets.xcassets/AppIcon.appiconset/icon_64x64.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
6
v-search/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
21
v-search/Assets.xcassets/TrayIcon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "trayIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
v-search/Assets.xcassets/TrayIcon.imageset/trayIcon.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
134
v-search/ContentView.swift
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// vectorsearch
|
||||
//
|
||||
// Created by Du Qingjie on 2024/08/31.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@ObservedObject var viewModel: AppViewModel
|
||||
|
||||
@StateObject private var languageManager = LanguageManager()
|
||||
@State private var searchText = ""
|
||||
@State private var results: [String] = []
|
||||
@State private var isFolderConfigPresented = false
|
||||
@State private var selectedLanguage = Locale.current.language.languageCode?.identifier ?? "en"
|
||||
//@State private var statusMessage: String = NSLocalizedString("status_ready", comment: "")
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
TextField(NSLocalizedString("Search", comment: "Search"), text: $searchText)
|
||||
.textFieldStyle(PlainTextFieldStyle()) // Flat, macOS-like style
|
||||
.padding()
|
||||
.onSubmit {
|
||||
performSearch(query: searchText)
|
||||
}
|
||||
Button(NSLocalizedString("Search", comment: "Search")) {
|
||||
performSearch(query: searchText)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // Flat button style for macOS
|
||||
.padding(.horizontal)
|
||||
|
||||
Button(NSLocalizedString("Folder Config", comment: "Folder Config")) {
|
||||
isFolderConfigPresented = true
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // Flat button style
|
||||
.padding(.horizontal)
|
||||
.sheet(isPresented: $isFolderConfigPresented) {
|
||||
FolderConfigView(isPresented: $isFolderConfigPresented)
|
||||
}
|
||||
|
||||
Picker("", selection: $languageManager.currentLanguage) {
|
||||
Text(NSLocalizedString("English", comment: "English")).tag("en")
|
||||
Text(NSLocalizedString("Japanese", comment: "Japanese")).tag("ja")
|
||||
}
|
||||
.labelsHidden()
|
||||
.pickerStyle(SegmentedPickerStyle()) // Use segmented picker for macOS-like feel
|
||||
}
|
||||
.padding()
|
||||
|
||||
List(results, id: \.self) { result in
|
||||
HStack {
|
||||
Text(result).textSelection(.enabled)
|
||||
Spacer()
|
||||
Button(action: {
|
||||
openFile(path: result)
|
||||
}) {
|
||||
Text(NSLocalizedString("Open", comment: "Open"))
|
||||
}
|
||||
.buttonStyle(LinkButtonStyle()) // macOS-native link style
|
||||
Button(action: {
|
||||
showInFinder(path: result)
|
||||
}) {
|
||||
Text(NSLocalizedString("Show in Finder", comment: "Show in Finder"))
|
||||
}
|
||||
.buttonStyle(LinkButtonStyle())
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle()) // Minimalist list style
|
||||
.padding(.horizontal)
|
||||
|
||||
Text(viewModel.statusMessage)
|
||||
.font(.footnote)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.frame(minWidth: 800, minHeight: 600)
|
||||
.onAppear(){
|
||||
if let window = NSApplication.shared.windows.first {
|
||||
window.title = "v-search"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func performSearch(query: String) {
|
||||
Task {
|
||||
viewModel.statusMessage = NSLocalizedString("status_search_start", comment: "")
|
||||
let searchResults = await VectorManager.shared.search(query: query)
|
||||
results = searchResults.map { $0.id }
|
||||
viewModel.statusMessage = NSLocalizedString("status_search_completed", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
func openFile(path: String) {
|
||||
let url = URL(fileURLWithPath: path)
|
||||
if let folder = findContainingFolder(for: url) {
|
||||
let bookmark = UserDefaults.standard.data(forKey: folder.path)
|
||||
var isStale = false
|
||||
do {
|
||||
let restoredURL = try URL(resolvingBookmarkData: bookmark!, options: [.withSecurityScope], relativeTo: nil, bookmarkDataIsStale: &isStale)
|
||||
if restoredURL.startAccessingSecurityScopedResource() {
|
||||
NSWorkspace.shared.open(url)
|
||||
restoredURL.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.err("Error resolving bookmark: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showInFinder(path: String) {
|
||||
let url = URL(fileURLWithPath: path)
|
||||
NSWorkspace.shared.activateFileViewerSelecting([url])
|
||||
}
|
||||
|
||||
func findContainingFolder(for fileURL: URL) -> URL? {
|
||||
if let savedFolders = UserDefaults.standard.array(forKey: "savedFolders") as? [String] {
|
||||
for folderPath in savedFolders {
|
||||
let folderURL = URL(fileURLWithPath: folderPath)
|
||||
if fileURL.path.hasPrefix(folderURL.path) {
|
||||
return folderURL
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}*/
|
||||
133
v-search/FolderConfigView.swift
Normal file
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// FolderConfigView.swift
|
||||
// vectorsearch
|
||||
//
|
||||
// Created by Du Qingjie on 2024/08/31.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct FolderConfigView: View {
|
||||
@Binding var isPresented: Bool
|
||||
|
||||
@State private var folders: [String] = UserDefaults.standard.stringArray(forKey: "savedFolders") ?? []
|
||||
@State private var windowSize: CGSize = .zero
|
||||
|
||||
@State private var showDeleteConfirmation = false
|
||||
@State private var folderToDelete: String?
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
// Header with title and close button
|
||||
HStack {
|
||||
Text(NSLocalizedString("Folder Configuration", comment: "Folder Configuration"))
|
||||
.font(.headline)
|
||||
.padding(.leading) // Align headline with flat style
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
isPresented = false
|
||||
}) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.resizable()
|
||||
.frame(width: 18, height: 18) // Smaller, more macOS-like size
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // Flat button style
|
||||
}
|
||||
.padding([.top, .trailing])
|
||||
|
||||
// Folder list
|
||||
List {
|
||||
ForEach(folders, id: \.self) { folder in
|
||||
HStack {
|
||||
Text(folder)
|
||||
Spacer()
|
||||
Button(action: {
|
||||
folderToDelete = folder
|
||||
showDeleteConfirmation = true
|
||||
}) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.resizable()
|
||||
.frame(width: 18, height: 18) // Smaller, minimalist design
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // Flat button style
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle()) // Flat list style
|
||||
|
||||
HStack {
|
||||
// Add Folder button
|
||||
Button(NSLocalizedString("Add Folder", comment: "Add Folder")) {
|
||||
addFolder()
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // Flat button style
|
||||
.padding(.top)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.frame(width: windowSize.width * 0.8, height: windowSize.height * 0.8) // 80% of main window size
|
||||
.onAppear {
|
||||
updateWindowSize()
|
||||
}
|
||||
.alert(isPresented: $showDeleteConfirmation) {
|
||||
Alert(
|
||||
title: Text(NSLocalizedString("Delete Folder", comment: "Delete Folder")),
|
||||
message: Text(String.localizedStringWithFormat(NSLocalizedString("delete_folder_message", comment: ""), folderToDelete ?? "")),
|
||||
primaryButton: .destructive(Text(NSLocalizedString("Delete", comment: ""))) {
|
||||
if let folder = folderToDelete {
|
||||
deleteFolder(path: folder)
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel(Text(NSLocalizedString("Cancel", comment: "")))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func updateWindowSize() {
|
||||
if let window = NSApplication.shared.windows.first {
|
||||
windowSize = window.frame.size
|
||||
}
|
||||
}
|
||||
|
||||
func addFolder() {
|
||||
let panel = NSOpenPanel()
|
||||
panel.canChooseDirectories = true
|
||||
panel.canChooseFiles = false
|
||||
panel.allowsMultipleSelection = true
|
||||
panel.prompt = NSLocalizedString("Select Folder", comment: "")
|
||||
|
||||
if panel.runModal() == .OK {
|
||||
for url in panel.urls {
|
||||
if !folders.contains(url.path) {
|
||||
folders.append(url.path)
|
||||
saveBookmark(for: url)
|
||||
}
|
||||
}
|
||||
saveFolders()
|
||||
}
|
||||
}
|
||||
|
||||
func deleteFolder(path: String) {
|
||||
VectorManager.shared.deleteFileItem(path: path)
|
||||
folders.removeAll(where: { $0 == path })
|
||||
saveFolders()
|
||||
}
|
||||
|
||||
func saveFolders() {
|
||||
UserDefaults.standard.setValue(folders, forKey: "savedFolders")
|
||||
// VectorManager.shared.scanDirectories() if needed
|
||||
}
|
||||
|
||||
func saveBookmark(for folderURL: URL) {
|
||||
do {
|
||||
let bookmarkData = try folderURL.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
|
||||
UserDefaults.standard.set(bookmarkData, forKey: folderURL.path)
|
||||
} catch {
|
||||
Logger.shared.err("Error creating bookmark: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
46
v-search/LanguageManager.swift
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// LanguageManager.swift
|
||||
// vectorsearch
|
||||
//
|
||||
// Created by Du Qingjie on 2024/08/31.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
class LanguageManager: ObservableObject {
|
||||
@Published var currentLanguage: String = Locale.current.language.languageCode?.identifier ?? "en" {
|
||||
didSet {
|
||||
updateLocalization()
|
||||
}
|
||||
}
|
||||
|
||||
func updateLocalization() {
|
||||
Bundle.setLanguage(currentLanguage)
|
||||
// Trigger UI update by changing @Published property
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
extension Bundle {
|
||||
private static var bundleKey: UInt8 = 0
|
||||
|
||||
static func setLanguage(_ language: String) {
|
||||
defer {
|
||||
object_setClass(Bundle.main, BundleEx.self)
|
||||
}
|
||||
objc_setAssociatedObject(Bundle.main, &bundleKey, language, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
|
||||
var localizedBundle: Bundle? {
|
||||
guard let language = objc_getAssociatedObject(self, &Bundle.bundleKey) as? String else {
|
||||
return nil
|
||||
}
|
||||
return Bundle(path: Bundle.main.path(forResource: language, ofType: "lproj")!)
|
||||
}
|
||||
}
|
||||
|
||||
private class BundleEx: Bundle {
|
||||
override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
|
||||
return localizedBundle?.localizedString(forKey: key, value: value, table: tableName) ?? super.localizedString(forKey: key, value: value, table: tableName)
|
||||
}
|
||||
}
|
||||
350
v-search/Localizable.xcstrings
Normal file
@@ -0,0 +1,350 @@
|
||||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"" : {
|
||||
|
||||
},
|
||||
"Add Folder" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "フォルダーを追加"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cancel" : {
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "キャンセル"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Delete" : {
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "削除"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Delete Folder" : {
|
||||
"comment" : "Delete Folder",
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "フォルダーを削除"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete_folder_cancel" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Cancel"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "キャンセル"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete_folder_confirm" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Remove"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "削除"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete_folder_message" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Are you sure you want to remove all index of the folder \\\"%@\\\"?"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "フォルダー「%@」のすべてのインデックスを削除してもよろしいですか?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete_folder_title" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Remove folder"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "フォルダーの削除"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"English" : {
|
||||
"comment" : "English",
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "English"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Folder Config" : {
|
||||
"comment" : "Folder Config",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Folder Config"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "フォルダー設定"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Folder Configuration" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "フォルダー設定"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Japanese" : {
|
||||
"comment" : "Japanese",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "日本語"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "needs_review",
|
||||
"value" : "日本語"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Language" : {
|
||||
"comment" : "Language",
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "言語"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Open" : {
|
||||
"comment" : "Open",
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "オープン"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scan_start" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Start scan..."
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "スキャン開始。。。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanned_folder" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Scan done: %@"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@をスキャン完了"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanning_folder" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Scan: %@"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "needs_review",
|
||||
"value" : "%@をスキャン。。。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Search" : {
|
||||
"comment" : "Search",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Search"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "検索"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Select Folder" : {
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "フォルダーを選択"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Show in Finder" : {
|
||||
"comment" : "Show in Finder",
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "フォルダーを表示"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status_loading" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Loading..."
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "読み込み中..."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status_ready" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ready"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "準備完了"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status_search_completed" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Search completed"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "検索完了"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status_search_start" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Start search..."
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "検索中。。。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
28
v-search/Logger.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Logger.swift
|
||||
// vectorsearch
|
||||
//
|
||||
// Created by Du Qingjie on 2024/08/31.
|
||||
//
|
||||
|
||||
import os
|
||||
import Foundation
|
||||
|
||||
class Logger {
|
||||
static let shared = Logger()
|
||||
|
||||
// Correctly initialize with subsystem and category
|
||||
private let logger = os.Logger(subsystem: Bundle.main.bundleIdentifier ?? "dqj.vectorsearch", category: "VectorSearch")
|
||||
|
||||
func info(_ message: String) {
|
||||
logger.log("\(message, privacy: .public)")
|
||||
}
|
||||
|
||||
func err(_ message: String) {
|
||||
logger.error("\(message, privacy: .public)")
|
||||
}
|
||||
|
||||
func debug(_ message: String) {
|
||||
logger.debug("\(message, privacy: .public)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
82
v-search/VectorManager.swift
Normal file
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// VectorManager.swift
|
||||
// vectorsearch
|
||||
//
|
||||
// Created by Du Qingjie on 2024/09/06.
|
||||
//
|
||||
|
||||
import SimilaritySearchKit
|
||||
import Foundation
|
||||
|
||||
class VectorManager: ObservableObject {
|
||||
static let shared = VectorManager()
|
||||
|
||||
private var similarityIndex: SimilarityIndex?
|
||||
|
||||
init() {
|
||||
Task {
|
||||
await load()
|
||||
}
|
||||
}
|
||||
|
||||
func load() async {
|
||||
similarityIndex = await SimilarityIndex(
|
||||
model: NativeEmbeddings(),
|
||||
metric: CosineSimilarity()
|
||||
)
|
||||
if let data = UserDefaults.standard.data(forKey: "vector_data") {
|
||||
let items = try? JSONDecoder().decode([SimilarityIndex.IndexItem].self, from: data)
|
||||
similarityIndex?.indexItems = items ?? []
|
||||
}else{
|
||||
similarityIndex?.indexItems = []
|
||||
}
|
||||
}
|
||||
|
||||
func setFileItem(path: String, text: String, modify_time: Int) async{
|
||||
guard let index = similarityIndex else { return }
|
||||
|
||||
let curItem = index.getItem(id: path)
|
||||
if(curItem != nil && Int(curItem?.metadata["mt"] ?? String(Int.max)) ?? Int.max > Int(modify_time)){
|
||||
index.updateItem(id: path, text: text, embedding: nil, metadata: ["mt": String(modify_time)] )
|
||||
if let encoded = try? JSONEncoder().encode(index.indexItems) {
|
||||
UserDefaults.standard.set(encoded, forKey: "vector_data")
|
||||
}
|
||||
}else if(curItem == nil){
|
||||
await index.addItem(id: path, text: text, metadata: ["mt": String(modify_time)])
|
||||
if let encoded = try? JSONEncoder().encode(index.indexItems) {
|
||||
UserDefaults.standard.set(encoded, forKey: "vector_data")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func search(query: String) async -> [SimilarityIndex.SearchResult]{
|
||||
guard let index = similarityIndex else { return []}
|
||||
|
||||
let results = await index.search(query, top: 100)
|
||||
return results
|
||||
}
|
||||
|
||||
func deleteFileItem(path: String){
|
||||
guard let index = similarityIndex else { return }
|
||||
|
||||
index.removeItem(id: path)
|
||||
UserDefaults.standard.setValue(index.indexItems, forKey: "vector_data")
|
||||
}
|
||||
|
||||
func clear() {
|
||||
guard let index = similarityIndex else { return }
|
||||
|
||||
index.removeAll()
|
||||
UserDefaults.standard.setValue(index.indexItems, forKey: "vector_data")
|
||||
}
|
||||
|
||||
func getFileItem(id: String) -> SimilarityIndex.IndexItem?{
|
||||
guard let index = similarityIndex else { return nil }
|
||||
|
||||
return index.getItem(id: id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
10
v-search/v_search.entitlements
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
378
v-search/v_searchApp.swift
Normal file
@@ -0,0 +1,378 @@
|
||||
//
|
||||
// v_searchApp.swift
|
||||
// v-search
|
||||
//
|
||||
// Created by Du Qingjie on 2024/09/07.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import PDFKit
|
||||
import Foundation
|
||||
import Compression
|
||||
import ZIPFoundation
|
||||
|
||||
@main
|
||||
struct v_searchApp: App {
|
||||
@StateObject var viewModel = AppViewModel()
|
||||
|
||||
// Store the timer
|
||||
@State private var scanTimer: Timer? = nil
|
||||
@State private var taskRunning: Bool = false
|
||||
@State private var statusItem: NSStatusItem? // Tray icon status item
|
||||
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView(viewModel: viewModel)
|
||||
.onAppear {
|
||||
startBackgroundScan()
|
||||
}
|
||||
.onDisappear {
|
||||
//stopBackgroundScan()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to start the background scan with a timer
|
||||
func startBackgroundScan() {
|
||||
viewModel.statusMessage = NSLocalizedString("status_loading", comment: "")
|
||||
appDelegate.viewModel = self.viewModel
|
||||
//scanSavedFolders()
|
||||
|
||||
// Schedule the timer to scan every 3 minutes (adjust interval as necessary)
|
||||
scanTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { _ in
|
||||
if !taskRunning {
|
||||
taskRunning = true
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
scanSavedFolders()
|
||||
DispatchQueue.main.async {
|
||||
taskRunning = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Logger.shared.debug("Background scan started.")
|
||||
}
|
||||
|
||||
// Function to stop the background scan when the app closes
|
||||
func stopBackgroundScan() {
|
||||
scanTimer?.invalidate()
|
||||
Logger.shared.debug("Background scan stopped.")
|
||||
}
|
||||
|
||||
// Function to scan the saved folders and add files to the VectorIndex
|
||||
func scanSavedFolders() {
|
||||
guard let folders = UserDefaults.standard.stringArray(forKey: "savedFolders") else {
|
||||
Logger.shared.debug("No folders found in config.")
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.statusMessage = NSLocalizedString("scan_start", comment: "")
|
||||
for folder in folders {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
Logger.shared.debug("Scanning folder: \(folder)")
|
||||
viewModel.statusMessage = String.localizedStringWithFormat(NSLocalizedString("scanning_folder", comment: ""), folder)
|
||||
Task {
|
||||
do {
|
||||
if let bookmarkData = UserDefaults.standard.data(forKey: folder) {
|
||||
//Logger.shared.debug("Got bookmarkData")
|
||||
var isStale = false
|
||||
let restoredURL = try URL(resolvingBookmarkData: bookmarkData, options: [.withSecurityScope], relativeTo: nil, bookmarkDataIsStale: &isStale)
|
||||
if restoredURL.startAccessingSecurityScopedResource() {
|
||||
//Logger.shared.debug("Start scanDirectory")
|
||||
await scanDirectory(path: restoredURL.path)
|
||||
//Logger.shared.debug("Done scanDirectory")
|
||||
restoredURL.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.err("Error resolving bookmark: \(error)")
|
||||
}
|
||||
//Logger.shared.debug("Calling signal")
|
||||
semaphore.signal() // Signal that the async task is finished
|
||||
Logger.shared.debug("Called signal")
|
||||
}
|
||||
|
||||
Logger.shared.debug("Waiting signal")
|
||||
// Wait for the async task to complete
|
||||
semaphore.wait()
|
||||
Logger.shared.debug("Done waiting")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
viewModel.statusMessage = String.localizedStringWithFormat(NSLocalizedString("scanned_folder", comment: ""), folder)
|
||||
Logger.shared.debug("Scan completed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum FileType {
|
||||
case pdf, docx, pptx, unknown
|
||||
}
|
||||
|
||||
// Recursively scan a directory and its subdirectories for text files
|
||||
func scanDirectory(path: String) async {
|
||||
let fileManager = FileManager.default
|
||||
do {
|
||||
let contents = try fileManager.contentsOfDirectory(atPath: path)
|
||||
var count = 0
|
||||
for item in contents {
|
||||
let itemPath = "\(path)/\(item)"
|
||||
var isDir: ObjCBool = false
|
||||
if fileManager.fileExists(atPath: itemPath, isDirectory: &isDir) {
|
||||
if isDir.boolValue {
|
||||
sleep(1)
|
||||
await scanDirectory(path: itemPath)
|
||||
} else {
|
||||
if count > 100 {
|
||||
count = 0
|
||||
sleep(1)
|
||||
}
|
||||
await processFile(path: itemPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.err("Error scanning directory: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
func detectFileType(fileURL: URL) -> FileType? {
|
||||
guard let fileData = try? Data(contentsOf: fileURL) else { return nil }
|
||||
|
||||
if fileData.prefix(4) == Data([0x25, 0x50, 0x44, 0x46]) {
|
||||
return .pdf
|
||||
}
|
||||
|
||||
if fileData.prefix(4) == Data([0x50, 0x4B, 0x03, 0x04]) {
|
||||
if checkZipContainsFile(fileURL: fileURL, fileName: "[Content_Types].xml") &&
|
||||
checkZipContainsFile(fileURL: fileURL, fileName:"word/document.xml") {
|
||||
return .docx
|
||||
}
|
||||
}
|
||||
|
||||
if fileData.prefix(4) == Data([0x50, 0x4B, 0x03, 0x04]) {
|
||||
if checkZipContainsFile(fileURL: fileURL, fileName: "[Content_Types].xml") {
|
||||
return .pptx
|
||||
}
|
||||
}
|
||||
|
||||
return .unknown
|
||||
}
|
||||
|
||||
func getFileModificationTime(atPath path: String) -> Int? {
|
||||
let fileManager = FileManager.default
|
||||
do {
|
||||
let attributes = try fileManager.attributesOfItem(atPath: path)
|
||||
if let modificationDate = attributes[.modificationDate] as? Date {
|
||||
// Convert to Unix time (TimeInterval is in seconds since 1970)
|
||||
let unixTime = modificationDate.timeIntervalSince1970
|
||||
return Int(unixTime)
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.err("Error retrieving file attributes: \(error.localizedDescription)")
|
||||
}
|
||||
return Int.max
|
||||
}
|
||||
|
||||
// Function to process a file and add it to the VectorIndex
|
||||
func processFile(path: String) async {
|
||||
do {
|
||||
if let fileType = detectFileType(fileURL: URL(fileURLWithPath: path)) {
|
||||
var fileContents: String = ""
|
||||
switch fileType {
|
||||
case .pdf:
|
||||
fileContents = extractTextFromPDF(fileURL: URL(fileURLWithPath: path)) ?? ""
|
||||
case .docx:
|
||||
fileContents = extractTextFromDocx(fileURL: URL(fileURLWithPath: path)) ?? ""
|
||||
case .pptx:
|
||||
fileContents = extractTextFromPPTX(fileURL: URL(fileURLWithPath: path)) ?? ""
|
||||
default:
|
||||
fileContents = try String(contentsOfFile: path, encoding: .utf8)
|
||||
}
|
||||
if !fileContents.isEmpty {
|
||||
await VectorManager.shared.setFileItem(path: path, text: String(fileContents.prefix(1000)),modify_time: getFileModificationTime(atPath: path) ?? Int.max)
|
||||
Logger.shared.debug("File added to VectorIndex: \(path)")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.debug("Error reading file at path: \(path) - \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
func checkZipContainsFile(fileURL: URL, fileName: String) -> Bool {
|
||||
do {
|
||||
// Use the new throwing initializer to open the ZIP file
|
||||
let archive = try Archive(url: fileURL, accessMode: .read)
|
||||
|
||||
// Iterate through the entries in the ZIP archive
|
||||
for entry in archive {
|
||||
if entry.path == fileName {
|
||||
Logger.shared.debug("\(fileName) found in ZIP archive.")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
Logger.shared.debug("\(fileName) not found in ZIP archive.")
|
||||
return false
|
||||
} catch {
|
||||
// Handle any errors that occur while trying to open the ZIP archive
|
||||
Logger.shared.err("Failed to open ZIP archive: \(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func extractTextFromPDF(fileURL: URL) -> String? {
|
||||
guard let pdfDocument = PDFDocument(url: fileURL) else { return nil }
|
||||
var extractedText = ""
|
||||
for i in 0..<pdfDocument.pageCount {
|
||||
if let page = pdfDocument.page(at: i), let pageText = page.string {
|
||||
extractedText += pageText
|
||||
}
|
||||
}
|
||||
return extractedText
|
||||
}
|
||||
|
||||
func extractTextFromDocx(fileURL: URL) -> String? {
|
||||
do {
|
||||
// Open the DOCX file as a ZIP archive
|
||||
let archive = try Archive(url: fileURL, accessMode: .read)
|
||||
|
||||
// Look for the 'word/document.xml' file in the archive
|
||||
if let documentEntry = archive["word/document.xml"] {
|
||||
// Extract the XML data from the entry
|
||||
var documentXML = Data()
|
||||
_ = try archive.extract(documentEntry) { data in
|
||||
documentXML.append(data)
|
||||
}
|
||||
|
||||
// Convert XML data to string
|
||||
if let documentString = String(data: documentXML, encoding: .utf8) {
|
||||
// Parse the XML and extract text content
|
||||
return extractTextFromXML(xmlString: documentString)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.err("Error extracting DOCX file: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractTextFromXML(xmlString: String) -> String {
|
||||
var extractedText = ""
|
||||
|
||||
do {
|
||||
// Parse the XML
|
||||
let xmlDoc = try XMLDocument(xmlString: xmlString, options: .documentTidyXML)
|
||||
|
||||
// Extract all text nodes from <w:t> (Word text) elements
|
||||
let textNodes = try xmlDoc.nodes(forXPath: "//w:t")
|
||||
for node in textNodes {
|
||||
if let textNode = node as? XMLElement {
|
||||
extractedText += textNode.stringValue ?? ""
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.err("Error parsing XML: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
return extractedText
|
||||
}
|
||||
|
||||
func extractTextFromPPTX(fileURL: URL) -> String? {
|
||||
do {
|
||||
// Open the PPTX file as a ZIP archive
|
||||
let archive = try Archive(url: fileURL, accessMode: .read)
|
||||
var extractedText = ""
|
||||
|
||||
// Look for all slide XML files (usually stored as ppt/slides/slide1.xml, slide2.xml, etc.)
|
||||
for entry in archive {
|
||||
if entry.path.hasPrefix("ppt/slides/slide") && entry.path.hasSuffix(".xml") {
|
||||
// Extract the XML data from each slide
|
||||
var slideXML = Data()
|
||||
_ = try archive.extract(entry) { data in
|
||||
slideXML.append(data)
|
||||
}
|
||||
|
||||
// Convert the slide XML data to string
|
||||
if let slideString = String(data: slideXML, encoding: .utf8) {
|
||||
// Parse the slide XML and extract text content
|
||||
extractedText += extractTextFromPPTXSlide(xmlString: slideString) + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extractedText.isEmpty ? nil : extractedText
|
||||
} catch {
|
||||
Logger.shared.err("Error extracting PPTX file: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function to extract text content from a slide's XML
|
||||
func extractTextFromPPTXSlide(xmlString: String) -> String {
|
||||
var extractedText = ""
|
||||
|
||||
do {
|
||||
// Parse the XML
|
||||
let xmlDoc = try XMLDocument(xmlString: xmlString, options: .documentTidyXML)
|
||||
|
||||
// Extract all text nodes from <a:t> (PowerPoint text) elements
|
||||
let textNodes = try xmlDoc.nodes(forXPath: "//a:t")
|
||||
for node in textNodes {
|
||||
if let textNode = node as? XMLElement {
|
||||
extractedText += textNode.stringValue ?? ""
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.err("Error parsing slide XML: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
return extractedText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
var statusItem: NSStatusItem!
|
||||
var mainWindow: NSWindow?
|
||||
|
||||
public var viewModel: AppViewModel?
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
// Initialize the status item (tray icon)
|
||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
|
||||
if let button = statusItem.button {
|
||||
button.image = NSImage(named: "TrayIcon")//, accessibilityDescription: "Tray Icon")
|
||||
button.action = #selector(toggleWindow)
|
||||
}
|
||||
|
||||
// Find the main window
|
||||
if let window = NSApplication.shared.windows.first {
|
||||
mainWindow = window
|
||||
}
|
||||
|
||||
Task {
|
||||
await VectorManager.shared.load()
|
||||
}
|
||||
|
||||
viewModel?.statusMessage = "Ready"
|
||||
}
|
||||
|
||||
@objc func toggleWindow() {
|
||||
guard let window = mainWindow else { return }
|
||||
|
||||
if window.isVisible {
|
||||
window.orderOut(nil) // Hide the window
|
||||
//Hide the icon on dock
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
} else {
|
||||
window.makeKeyAndOrderFront(nil) // Show the window
|
||||
//Show the icon on dock
|
||||
NSApp.setActivationPolicy(.regular)
|
||||
}
|
||||
}
|
||||
}
|
||||
36
v-searchTests/v_searchTests.swift
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// v_searchTests.swift
|
||||
// v-searchTests
|
||||
//
|
||||
// Created by Du Qingjie on 2024/09/07.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import v_search
|
||||
|
||||
final class v_searchTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
// Any test you write for XCTest can be annotated as throws and async.
|
||||
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
||||
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
|
||||
}
|
||||
|
||||
func testPerformanceExample() throws {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
41
v-searchUITests/v_searchUITests.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// v_searchUITests.swift
|
||||
// v-searchUITests
|
||||
//
|
||||
// Created by Du Qingjie on 2024/09/07.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
final class v_searchUITests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
|
||||
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||
continueAfterFailure = false
|
||||
|
||||
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() throws {
|
||||
// UI tests must launch the application that they test.
|
||||
let app = XCUIApplication()
|
||||
app.launch()
|
||||
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
func testLaunchPerformance() throws {
|
||||
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
|
||||
// This measures how long it takes to launch your application.
|
||||
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
||||
XCUIApplication().launch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
v-searchUITests/v_searchUITestsLaunchTests.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// v_searchUITestsLaunchTests.swift
|
||||
// v-searchUITests
|
||||
//
|
||||
// Created by Du Qingjie on 2024/09/07.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
final class v_searchUITestsLaunchTests: XCTestCase {
|
||||
|
||||
override class var runsForEachTargetApplicationUIConfiguration: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testLaunch() throws {
|
||||
let app = XCUIApplication()
|
||||
app.launch()
|
||||
|
||||
// Insert steps here to perform after app launch but before taking a screenshot,
|
||||
// such as logging into a test account or navigating somewhere in the app
|
||||
|
||||
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||
attachment.name = "Launch Screen"
|
||||
attachment.lifetime = .keepAlways
|
||||
add(attachment)
|
||||
}
|
||||
}
|
||||