Random access: image and audio files support
I extended the `/rows` API endpoint to also return image and audio files.

Similarly to the `first-rows` job, it writes the image and audio files to the NFS. But instead of using the `assets/` directory, it uses `cached-assets/`. The reason is that this directory is a cache: the image and audio files may be removed from time to time to free disk space.

I added a new config `CachedAssetsConfig` to define the cached assets URLs, storage directory and the cache parameters (e.g. the number of most recent rows to keep)

Because the API service now requires `datasets`, `Pillow` etc. I added those dependencies in libcommon and factorized the viewer assets and features code in libcommon in a `viewer_utils` submodule.

Finally I added tests for the regular `/rows` for both text and image cases.


## How it works

1. there's a request to an image dataset to `/rows`
2. (optional - in 5% of requests) the cached assets directory is cleaned to save disk space using a simple (?) heuristic:
    * it takes a big sample of rows from the cache using glob
    * it keeps the most recent ones (max 200)
    * it keeps the rows below a certain index (max 100)
    * it discards the rest
3. the arrow data are retrieved from the parquet files using the row indexer
4. the cached assets and URLs are created on disk using the same mechanism as the `first-rows` job
5. the response is returned and contains URLs to the cached assets

## Other details

- I updated `pip-audit` everywhere to fix issues with extras/duplicated requirements
- I introduced the `MockFileSystem` (originally from `datasets`) that can be used to test the parquet data indexing for random access
Random access: image and audio files support: I extended the `/rows` API endpoint to also return image and audio files.

Similarly to the `first-rows` job, it writes the image and audio files to the NFS. But instead of using the `assets/` directory, it uses `cached-assets/`. The reason is that this directory is a cache: the image and audio files may be removed from time to time to free disk space.

I added a new config `CachedAssetsConfig` to define the cached assets URLs, storage directory and the cache parameters (e.g. the number of most recent rows to keep)

Because the API service now requires `datasets`, `Pillow` etc. I added those dependencies in libcommon and factorized the viewer assets and features code in libcommon in a `viewer_utils` submodule.

Finally I added tests for the regular `/rows` for both text and image cases.


## How it works

1. there's a request to an image dataset to `/rows`
2. (optional - in 5% of requests) the cached assets directory is cleaned to save disk space using a simple (?) heuristic:
    * it takes a big sample of rows from the cache using glob
    * it keeps the most recent ones (max 200)
    * it keeps the rows below a certain index (max 100)
    * it discards the rest
3. the arrow data are retrieved from the parquet files using the row indexer
4. the cached assets and URLs are created on disk using the same mechanism as the `first-rows` job
5. the response is returned and contains URLs to the cached assets

## Other details

- I updated `pip-audit` everywhere to fix issues with extras/duplicated requirements
- I introduced the `MockFileSystem` (originally from `datasets`) that can be used to test the parquet data indexing for random access