Initial commit: Original Python LLMTester project
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,170 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
try: # Optional dependency for GGUF execution
|
||||
from llama_cpp import Llama
|
||||
except ImportError: # pragma: no cover
|
||||
Llama = None # type: ignore
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from llama_cpp import Llama as LlamaType
|
||||
else: # pragma: no cover
|
||||
class LlamaType: # type: ignore
|
||||
...
|
||||
|
||||
from engine_loader import BaseRuntime
|
||||
|
||||
|
||||
class EngineRuntime(BaseRuntime):
|
||||
name = "mlx_apple_silicon"
|
||||
|
||||
def __init__(self, *, max_tokens: int = 1024, context_window: int = 4096) -> None:
|
||||
super().__init__()
|
||||
self.max_tokens = max_tokens
|
||||
self.context_window = context_window
|
||||
self.threads = max(os.cpu_count() or 1, 1)
|
||||
|
||||
self._mode: Optional[str] = None # "mlx" or "llama_cpp"
|
||||
self._model = None
|
||||
self._tokenizer = None
|
||||
self._llm: Optional[LlamaType] = None
|
||||
self._model_path: Path | None = None
|
||||
|
||||
def discover_gguf_models(self, search_paths: list[Path]) -> list[Path]:
|
||||
candidates: list[Path] = []
|
||||
seen: set[Path] = set()
|
||||
for root in search_paths:
|
||||
if not root.exists():
|
||||
continue
|
||||
for path in root.rglob("*.gguf"):
|
||||
if path.is_file():
|
||||
resolved = path.resolve()
|
||||
if resolved not in seen:
|
||||
seen.add(resolved)
|
||||
candidates.append(resolved)
|
||||
for path in root.iterdir():
|
||||
if path.is_dir() and (path / "config.json").exists():
|
||||
resolved = path.resolve()
|
||||
if resolved not in seen:
|
||||
seen.add(resolved)
|
||||
candidates.append(resolved)
|
||||
return candidates
|
||||
|
||||
def load_model(self, model_path: Path) -> None:
|
||||
if self._model_path == model_path:
|
||||
if self._mode == "mlx" and self._model is not None:
|
||||
return
|
||||
if self._mode == "llama_cpp" and self._llm is not None:
|
||||
return
|
||||
|
||||
self.unload()
|
||||
resolved_path = model_path.resolve()
|
||||
|
||||
if resolved_path.is_file() and resolved_path.suffix.lower() == ".gguf":
|
||||
if Llama is None:
|
||||
raise RuntimeError(
|
||||
"llama-cpp-python is required to run GGUF models on Apple Silicon. Install it with 'pip install llama-cpp-python'."
|
||||
)
|
||||
self._llm = Llama(
|
||||
model_path=str(resolved_path),
|
||||
n_ctx=self.context_window,
|
||||
n_gpu_layers=-1,
|
||||
n_threads=self.threads,
|
||||
use_gpu=True,
|
||||
verbose=False,
|
||||
)
|
||||
self._mode = "llama_cpp"
|
||||
else:
|
||||
try:
|
||||
from mlx_lm import load # type: ignore import-not-found
|
||||
except ImportError as exc: # pragma: no cover - environment validation
|
||||
raise RuntimeError(
|
||||
"mlx-lm is required for MLX models. Install it with 'pip install mlx-lm'."
|
||||
) from exc
|
||||
|
||||
self._model, self._tokenizer = load(str(resolved_path))
|
||||
self._mode = "mlx"
|
||||
|
||||
self._model_path = resolved_path
|
||||
|
||||
def generate(self, prompt: str, *, temperature: float) -> dict:
|
||||
if self._mode == "llama_cpp":
|
||||
if self._llm is None:
|
||||
raise RuntimeError("Model must be loaded before calling generate().")
|
||||
|
||||
start_time = time.time()
|
||||
response = self._llm.create_chat_completion(
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=temperature,
|
||||
)
|
||||
elapsed = max(time.time() - start_time, 1e-5)
|
||||
|
||||
content = response["choices"][0]["message"]["content"]
|
||||
usage = response.get("usage", {})
|
||||
completion_tokens = usage.get("completion_tokens", 0)
|
||||
total_tokens = usage.get("total_tokens", completion_tokens)
|
||||
|
||||
return {
|
||||
"response": content,
|
||||
"metrics": {
|
||||
"tokens_per_second": round(completion_tokens / elapsed, 2) if completion_tokens else 0,
|
||||
"total_tokens": total_tokens,
|
||||
"time_to_first_token": round(usage.get("prompt_eval_duration", 0) / 1_000_000_000, 2)
|
||||
if "prompt_eval_duration" in usage
|
||||
else 0,
|
||||
"stop_reason": response["choices"][0].get("finish_reason", "unknown"),
|
||||
},
|
||||
}
|
||||
|
||||
if self._mode == "mlx":
|
||||
if self._model is None or self._tokenizer is None:
|
||||
raise RuntimeError("Model must be loaded before calling generate().")
|
||||
|
||||
start_time = time.time()
|
||||
try:
|
||||
from mlx_lm import generate # type: ignore import-not-found
|
||||
except ImportError as exc: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
"mlx-lm is required for MLX models. Install it with 'pip install mlx-lm'."
|
||||
) from exc
|
||||
|
||||
completion = generate(
|
||||
self._model,
|
||||
self._tokenizer,
|
||||
prompt,
|
||||
max_tokens=self.max_tokens,
|
||||
temperature=temperature,
|
||||
)
|
||||
elapsed = max(time.time() - start_time, 1e-5)
|
||||
|
||||
if isinstance(completion, str):
|
||||
text = completion
|
||||
else:
|
||||
text = str(completion)
|
||||
|
||||
prompt_tokens = len(self._tokenizer.encode(prompt))
|
||||
completion_tokens = max(len(self._tokenizer.encode(text)) - prompt_tokens, 0)
|
||||
total_tokens = prompt_tokens + completion_tokens
|
||||
|
||||
return {
|
||||
"response": text,
|
||||
"metrics": {
|
||||
"tokens_per_second": round(completion_tokens / elapsed, 2) if completion_tokens else 0,
|
||||
"total_tokens": total_tokens,
|
||||
"time_to_first_token": 0,
|
||||
"stop_reason": "stop",
|
||||
},
|
||||
}
|
||||
|
||||
raise RuntimeError("No model is currently loaded. Call load_model() first.")
|
||||
|
||||
def unload(self) -> None:
|
||||
self._model = None
|
||||
self._tokenizer = None
|
||||
self._llm = None
|
||||
self._model_path = None
|
||||
self._mode = None
|
||||
@@ -0,0 +1,72 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from llama_cpp import Llama
|
||||
except ImportError as exc: # pragma: no cover - dependency validation
|
||||
raise RuntimeError(
|
||||
"llama-cpp-python is required for the CPU engine runtime. Install it with 'pip install llama-cpp-python'."
|
||||
) from exc
|
||||
|
||||
from engine_loader import BaseRuntime
|
||||
|
||||
|
||||
class EngineRuntime(BaseRuntime):
|
||||
name = "llama_cpp_cpu"
|
||||
|
||||
def __init__(self, *, context_window: int = 4096) -> None:
|
||||
super().__init__()
|
||||
self.context_window = context_window
|
||||
self.threads = max(os.cpu_count() or 1, 1)
|
||||
self._llm: Llama | None = None
|
||||
self._model_path: Path | None = None
|
||||
|
||||
def load_model(self, model_path: Path) -> None:
|
||||
if self._model_path == model_path and self._llm is not None:
|
||||
return
|
||||
|
||||
self.unload()
|
||||
self._llm = Llama(
|
||||
model_path=str(model_path),
|
||||
n_ctx=self.context_window,
|
||||
n_threads=self.threads,
|
||||
n_gpu_layers=0,
|
||||
verbose=False,
|
||||
)
|
||||
self._model_path = model_path
|
||||
|
||||
def generate(self, prompt: str, *, temperature: float) -> dict:
|
||||
if self._llm is None:
|
||||
raise RuntimeError("Model must be loaded before calling generate().")
|
||||
|
||||
start_time = time.time()
|
||||
response = self._llm.create_chat_completion(
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=temperature,
|
||||
)
|
||||
elapsed = max(time.time() - start_time, 1e-5)
|
||||
|
||||
content = response["choices"][0]["message"]["content"]
|
||||
usage = response.get("usage", {})
|
||||
completion_tokens = usage.get("completion_tokens", 0)
|
||||
total_tokens = usage.get("total_tokens", completion_tokens)
|
||||
|
||||
return {
|
||||
"response": content,
|
||||
"metrics": {
|
||||
"tokens_per_second": round(completion_tokens / elapsed, 2) if completion_tokens else 0,
|
||||
"total_tokens": total_tokens,
|
||||
"time_to_first_token": round(usage.get("prompt_eval_duration", 0) / 1_000_000_000, 2)
|
||||
if "prompt_eval_duration" in usage
|
||||
else 0,
|
||||
"stop_reason": response["choices"][0].get("finish_reason", "unknown"),
|
||||
},
|
||||
}
|
||||
|
||||
def unload(self) -> None:
|
||||
if self._llm is not None:
|
||||
self._llm = None
|
||||
self._model_path = None
|
||||
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from llama_cpp import Llama
|
||||
except ImportError as exc:
|
||||
raise RuntimeError(
|
||||
"llama-cpp-python with CUDA support is required for the CUDA engine runtime."
|
||||
) from exc
|
||||
|
||||
from engine_loader import BaseRuntime
|
||||
|
||||
|
||||
class EngineRuntime(BaseRuntime):
|
||||
name = "llama_cpp_cuda"
|
||||
|
||||
def __init__(self, *, context_window: int = 4096, gpu_layers: int = -1) -> None:
|
||||
super().__init__()
|
||||
self.context_window = context_window
|
||||
self.gpu_layers = gpu_layers
|
||||
self.threads = max(os.cpu_count() or 1, 1)
|
||||
self._llm: Llama | None = None
|
||||
self._model_path: Path | None = None
|
||||
|
||||
def load_model(self, model_path: Path) -> None:
|
||||
if self._model_path == model_path and self._llm is not None:
|
||||
return
|
||||
|
||||
self.unload()
|
||||
self._llm = Llama(
|
||||
model_path=str(model_path),
|
||||
n_ctx=self.context_window,
|
||||
n_threads=self.threads,
|
||||
n_gpu_layers=self.gpu_layers,
|
||||
use_gpu=True,
|
||||
verbose=False,
|
||||
)
|
||||
self._model_path = model_path
|
||||
|
||||
def generate(self, prompt: str, *, temperature: float) -> dict:
|
||||
if self._llm is None:
|
||||
raise RuntimeError("Model must be loaded before calling generate().")
|
||||
|
||||
start_time = time.time()
|
||||
response = self._llm.create_chat_completion(
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=temperature,
|
||||
)
|
||||
elapsed = max(time.time() - start_time, 1e-5)
|
||||
|
||||
content = response["choices"][0]["message"]["content"]
|
||||
usage = response.get("usage", {})
|
||||
completion_tokens = usage.get("completion_tokens", 0)
|
||||
total_tokens = usage.get("total_tokens", completion_tokens)
|
||||
|
||||
return {
|
||||
"response": content,
|
||||
"metrics": {
|
||||
"tokens_per_second": round(completion_tokens / elapsed, 2) if completion_tokens else 0,
|
||||
"total_tokens": total_tokens,
|
||||
"time_to_first_token": round(usage.get("prompt_eval_duration", 0) / 1_000_000_000, 2)
|
||||
if "prompt_eval_duration" in usage
|
||||
else 0,
|
||||
"stop_reason": response["choices"][0].get("finish_reason", "unknown"),
|
||||
},
|
||||
}
|
||||
|
||||
def unload(self) -> None:
|
||||
if self._llm is not None:
|
||||
self._llm = None
|
||||
self._model_path = None
|
||||
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from llama_cpp import Llama
|
||||
except ImportError as exc:
|
||||
raise RuntimeError(
|
||||
"llama-cpp-python built with ROCm support is required for the ROCm engine runtime."
|
||||
) from exc
|
||||
|
||||
from engine_loader import BaseRuntime
|
||||
|
||||
|
||||
class EngineRuntime(BaseRuntime):
|
||||
name = "llama_cpp_rocm"
|
||||
|
||||
def __init__(self, *, context_window: int = 4096, gpu_layers: int = -1) -> None:
|
||||
super().__init__()
|
||||
self.context_window = context_window
|
||||
self.gpu_layers = gpu_layers
|
||||
self.threads = max(os.cpu_count() or 1, 1)
|
||||
self._llm: Llama | None = None
|
||||
self._model_path: Path | None = None
|
||||
|
||||
def load_model(self, model_path: Path) -> None:
|
||||
if self._model_path == model_path and self._llm is not None:
|
||||
return
|
||||
|
||||
self.unload()
|
||||
self._llm = Llama(
|
||||
model_path=str(model_path),
|
||||
n_ctx=self.context_window,
|
||||
n_threads=self.threads,
|
||||
n_gpu_layers=self.gpu_layers,
|
||||
use_gpu=True,
|
||||
verbose=False,
|
||||
)
|
||||
self._model_path = model_path
|
||||
|
||||
def generate(self, prompt: str, *, temperature: float) -> dict:
|
||||
if self._llm is None:
|
||||
raise RuntimeError("Model must be loaded before calling generate().")
|
||||
|
||||
start_time = time.time()
|
||||
response = self._llm.create_chat_completion(
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=temperature,
|
||||
)
|
||||
elapsed = max(time.time() - start_time, 1e-5)
|
||||
|
||||
content = response["choices"][0]["message"]["content"]
|
||||
usage = response.get("usage", {})
|
||||
completion_tokens = usage.get("completion_tokens", 0)
|
||||
total_tokens = usage.get("total_tokens", completion_tokens)
|
||||
|
||||
return {
|
||||
"response": content,
|
||||
"metrics": {
|
||||
"tokens_per_second": round(completion_tokens / elapsed, 2) if completion_tokens else 0,
|
||||
"total_tokens": total_tokens,
|
||||
"time_to_first_token": round(usage.get("prompt_eval_duration", 0) / 1_000_000_000, 2)
|
||||
if "prompt_eval_duration" in usage
|
||||
else 0,
|
||||
"stop_reason": response["choices"][0].get("finish_reason", "unknown"),
|
||||
},
|
||||
}
|
||||
|
||||
def unload(self) -> None:
|
||||
if self._llm is not None:
|
||||
self._llm = None
|
||||
self._model_path = None
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT_DIR = Path(__file__).resolve().parent.parent
|
||||
CORE_DIR = ROOT_DIR / ".core"
|
||||
TESTS_DIR = ROOT_DIR / "tests"
|
||||
RESULTS_DIR = ROOT_DIR / "results"
|
||||
TEMPLATES_DIR = CORE_DIR / "templates"
|
||||
TEMPLATE_PATH = TEMPLATES_DIR / "test-block.md"
|
||||
TEMP_DIR = CORE_DIR / ".temp"
|
||||
MODELS_DIR = ROOT_DIR / "models"
|
||||
|
||||
DEFAULT_TEMPERATURE = float(os.getenv("AUTO_TEST_TEMPERATURE", "0.1"))
|
||||
DEFAULT_PROVIDER_NAME = os.getenv("AUTO_TEST_PROVIDER", "LM Studio")
|
||||
|
||||
|
||||
def ensure_directories() -> None:
|
||||
"""Ensure that shared directories exist."""
|
||||
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)
|
||||
TEMP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
MODELS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def get_env_setting(key: str, default: str) -> str:
|
||||
"""Read an environment variable with a default fallback."""
|
||||
return os.getenv(key, default)
|
||||
|
||||
|
||||
def reset_temp_directory() -> None:
|
||||
"""Clear and recreate the temporary directory used for staging markdown blocks."""
|
||||
if TEMP_DIR.exists():
|
||||
shutil.rmtree(TEMP_DIR)
|
||||
TEMP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
@@ -0,0 +1,108 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.abc
|
||||
import importlib.util
|
||||
import platform
|
||||
import shutil
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Optional, Type
|
||||
|
||||
from config import CORE_DIR
|
||||
|
||||
ENGINE_ROOT = CORE_DIR / ".engine"
|
||||
DEFAULT_ENGINE_VERSION = "v1"
|
||||
|
||||
|
||||
class EngineLoadError(RuntimeError):
|
||||
"""Raised when no suitable engine runtime can be loaded."""
|
||||
|
||||
|
||||
class BaseRuntime:
|
||||
"""Base class for local engine runtimes."""
|
||||
|
||||
name: str = "base"
|
||||
|
||||
def discover_gguf_models(self, search_paths: list[Path]) -> list[Path]:
|
||||
gguf_paths: list[Path] = []
|
||||
seen: set[Path] = set()
|
||||
for root in search_paths:
|
||||
if not root.exists():
|
||||
continue
|
||||
for path in root.rglob("*.gguf"):
|
||||
if path.is_file():
|
||||
resolved = path.resolve()
|
||||
if resolved not in seen:
|
||||
seen.add(resolved)
|
||||
gguf_paths.append(resolved)
|
||||
return gguf_paths
|
||||
|
||||
def setup(self) -> None:
|
||||
"""Perform any runtime initialization before loading models."""
|
||||
|
||||
def load_model(self, model_path: Path) -> None:
|
||||
"""Load model into memory. Implementations may cache the loaded model."""
|
||||
|
||||
def generate(self, prompt: str, *, temperature: float) -> dict:
|
||||
"""Run inference against the currently loaded model and return a structured response."""
|
||||
raise NotImplementedError
|
||||
|
||||
def unload(self) -> None:
|
||||
"""Optional hook to release resources."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EngineDescriptor:
|
||||
architecture: str
|
||||
version: str = DEFAULT_ENGINE_VERSION
|
||||
|
||||
@property
|
||||
def module_path(self) -> Path:
|
||||
return ENGINE_ROOT / f".{self.architecture}" / f"{self.version}.py"
|
||||
|
||||
@property
|
||||
def module_name(self) -> str:
|
||||
return f"engine_{self.architecture}_{self.version}"
|
||||
|
||||
|
||||
def detect_architecture() -> EngineDescriptor:
|
||||
system = platform.system().lower()
|
||||
machine = platform.machine().lower()
|
||||
|
||||
if system == "darwin" and machine.startswith("arm"):
|
||||
return EngineDescriptor("apple_silicon")
|
||||
|
||||
if shutil.which("nvidia-smi"):
|
||||
return EngineDescriptor("cuda")
|
||||
|
||||
if shutil.which("rocm-smi") or shutil.which("rocminfo"):
|
||||
return EngineDescriptor("rocm")
|
||||
|
||||
return EngineDescriptor("cpu")
|
||||
|
||||
|
||||
def load_engine_class(descriptor: Optional[EngineDescriptor] = None) -> Type["BaseRuntime"]:
|
||||
descriptor = descriptor or detect_architecture()
|
||||
module_path = descriptor.module_path
|
||||
if not module_path.exists():
|
||||
raise EngineLoadError(f"Engine runtime not found for architecture '{descriptor.architecture}' at {module_path}")
|
||||
|
||||
spec = importlib.util.spec_from_file_location(descriptor.module_name, module_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise EngineLoadError(f"Unable to load engine module from {module_path}")
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
loader = spec.loader
|
||||
assert isinstance(loader, importlib.abc.Loader)
|
||||
try:
|
||||
loader.exec_module(module) # type: ignore[attr-defined]
|
||||
except Exception as exc: # pragma: no cover - import-time validation
|
||||
raise EngineLoadError(f"Failed to initialize engine runtime for '{descriptor.architecture}': {exc}") from exc
|
||||
|
||||
if not hasattr(module, "EngineRuntime"):
|
||||
raise EngineLoadError(f"Engine module {module_path} does not define 'EngineRuntime'")
|
||||
|
||||
runtime_cls = getattr(module, "EngineRuntime")
|
||||
if not issubclass(runtime_cls, BaseRuntime):
|
||||
raise EngineLoadError(f"Engine runtime from {module_path} must inherit from BaseRuntime")
|
||||
return runtime_cls
|
||||
@@ -0,0 +1,203 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Iterable, List, Tuple
|
||||
|
||||
__all__ = ["infer_language_from_prompt", "lint_response_markdown"]
|
||||
|
||||
|
||||
_LANGUAGE_HINTS: List[Tuple[str, str]] = [
|
||||
("swiftui", "swift"),
|
||||
("swift", "swift"),
|
||||
("objective-c", "objective-c"),
|
||||
("objc", "objective-c"),
|
||||
("kotlin", "kotlin"),
|
||||
("typescript", "typescript"),
|
||||
("javascript", "javascript"),
|
||||
("node.js", "javascript"),
|
||||
("python", "python"),
|
||||
("rust", "rust"),
|
||||
("go", "go"),
|
||||
("c#", "csharp"),
|
||||
("c++", "cpp"),
|
||||
("java", "java"),
|
||||
("sql", "sql"),
|
||||
]
|
||||
|
||||
_CODE_SYMBOLS = set("{}();[]=<>+-*/.&|%!:@")
|
||||
_CODE_PREFIXES = (
|
||||
"func ",
|
||||
"class ",
|
||||
"struct ",
|
||||
"enum ",
|
||||
"protocol ",
|
||||
"extension ",
|
||||
"import ",
|
||||
"let ",
|
||||
"var ",
|
||||
"guard ",
|
||||
"if ",
|
||||
"for ",
|
||||
"while ",
|
||||
"switch ",
|
||||
"case ",
|
||||
"return ",
|
||||
"init(",
|
||||
"init ",
|
||||
"deinit",
|
||||
"public ",
|
||||
"private ",
|
||||
"internal ",
|
||||
"fileprivate ",
|
||||
"override ",
|
||||
"@",
|
||||
"#if",
|
||||
"#endif",
|
||||
"#warning",
|
||||
"#error",
|
||||
)
|
||||
|
||||
_COMMENT_PREFIXES = ("///", "//", "/*", "*/", "* ")
|
||||
_BULLET_PREFIXES = ("- ", "* ", "+ ", "• ")
|
||||
|
||||
|
||||
def infer_language_from_prompt(prompt_text: str) -> str:
|
||||
"""Infer a reasonable language hint from the prompt text."""
|
||||
lowered = prompt_text.lower()
|
||||
for token, language in _LANGUAGE_HINTS:
|
||||
if token in lowered:
|
||||
return language
|
||||
return "text"
|
||||
|
||||
|
||||
def lint_response_markdown(raw_text: str, *, language_hint: str = "text") -> str:
|
||||
"""Normalize markdown so that only code segments are fenced and text stays plain."""
|
||||
if not raw_text:
|
||||
return ""
|
||||
|
||||
text = raw_text.replace("\r\n", "\n").replace("\r", "\n").strip("\n")
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
if "```" in text:
|
||||
return _normalize_existing_fences(text, language_hint)
|
||||
|
||||
return _auto_fence_code_segments(text, language_hint)
|
||||
|
||||
|
||||
def _normalize_existing_fences(text: str, language_hint: str) -> str:
|
||||
lines = text.split("\n")
|
||||
output: List[str] = []
|
||||
in_fence = False
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("```"):
|
||||
fence_marker = stripped[3:].strip()
|
||||
if in_fence:
|
||||
output.append("```")
|
||||
in_fence = False
|
||||
else:
|
||||
language = fence_marker or (language_hint if language_hint != "text" else "")
|
||||
output.append(f"```{language}" if language else "```")
|
||||
in_fence = True
|
||||
else:
|
||||
output.append(line)
|
||||
|
||||
if in_fence:
|
||||
output.append("```")
|
||||
|
||||
result = "\n".join(output).strip()
|
||||
return result
|
||||
|
||||
|
||||
def _auto_fence_code_segments(text: str, language_hint: str) -> str:
|
||||
lines = text.split("\n")
|
||||
segments: List[Tuple[str, List[str]]] = []
|
||||
current_lines: List[str] = []
|
||||
current_mode: str | None = None
|
||||
|
||||
for line in lines:
|
||||
if _is_blank(line):
|
||||
if current_lines:
|
||||
current_lines.append(line)
|
||||
continue
|
||||
|
||||
classification = "code" if _is_likely_code_line(line) else "text"
|
||||
|
||||
if current_mode is None:
|
||||
current_mode = classification
|
||||
current_lines.append(line)
|
||||
continue
|
||||
|
||||
if classification == current_mode:
|
||||
current_lines.append(line)
|
||||
continue
|
||||
|
||||
segments.append((current_mode, current_lines))
|
||||
current_mode = classification
|
||||
current_lines = [line]
|
||||
|
||||
if current_lines:
|
||||
segments.append((current_mode or "text", current_lines))
|
||||
|
||||
cleaned_parts: List[str] = []
|
||||
for mode, block_lines in segments:
|
||||
block_text = "\n".join(block_lines).strip("\n")
|
||||
if not block_text:
|
||||
continue
|
||||
if mode == "code":
|
||||
cleaned_parts.append(_format_code_block(block_text, language_hint))
|
||||
else:
|
||||
cleaned_parts.append(block_text)
|
||||
|
||||
return "\n\n".join(part for part in cleaned_parts if part).strip()
|
||||
|
||||
|
||||
def _format_code_block(content: str, language_hint: str) -> str:
|
||||
language = language_hint if language_hint and language_hint != "text" else ""
|
||||
inner = content.strip("\n")
|
||||
return f"```{language}\n{inner}\n```"
|
||||
|
||||
|
||||
def _is_blank(line: str) -> bool:
|
||||
return not line.strip()
|
||||
|
||||
|
||||
def _is_likely_code_line(line: str) -> bool:
|
||||
if line.startswith((" ", "\t")):
|
||||
return True
|
||||
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
return False
|
||||
|
||||
if stripped.startswith(_BULLET_PREFIXES):
|
||||
return False
|
||||
|
||||
if stripped.startswith(_COMMENT_PREFIXES):
|
||||
return True
|
||||
|
||||
if any(stripped.startswith(prefix) for prefix in _CODE_PREFIXES):
|
||||
return True
|
||||
|
||||
if stripped.endswith(("{", "}", ";")):
|
||||
return True
|
||||
|
||||
if stripped.startswith(("}", "{", "case ", "default:")):
|
||||
return True
|
||||
|
||||
if "(" in stripped and ")" in stripped:
|
||||
return True
|
||||
|
||||
if "=" in stripped:
|
||||
return True
|
||||
|
||||
symbol_count = sum(1 for ch in stripped if ch in _CODE_SYMBOLS)
|
||||
letter_count = sum(1 for ch in stripped if ch.isalpha())
|
||||
if symbol_count >= 2 and symbol_count >= max(1, letter_count * 0.3):
|
||||
return True
|
||||
|
||||
if re.search(r"\)\s*->", stripped):
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -0,0 +1,46 @@
|
||||
import re
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
from config import TESTS_DIR
|
||||
|
||||
|
||||
def _natural_key(path: Path) -> List[object]:
|
||||
parts = re.split(r"(\d+)", path.stem)
|
||||
return [int(part) if part.isdigit() else part.lower() for part in parts]
|
||||
|
||||
|
||||
def load_test_prompts() -> List[Dict[str, str]]:
|
||||
"""Load prompt files from the tests directory."""
|
||||
if not TESTS_DIR.exists():
|
||||
return []
|
||||
|
||||
prompt_entries: List[Dict[str, str]] = []
|
||||
|
||||
for prompt_path in sorted(TESTS_DIR.glob("*.txt"), key=_natural_key):
|
||||
try:
|
||||
raw_text = prompt_path.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
prompt_text = textwrap.dedent(raw_text).strip()
|
||||
if not prompt_text:
|
||||
continue
|
||||
|
||||
title = prompt_path.stem
|
||||
for line in prompt_text.splitlines():
|
||||
stripped_line = line.strip()
|
||||
if stripped_line:
|
||||
title = stripped_line
|
||||
break
|
||||
|
||||
prompt_entries.append(
|
||||
{
|
||||
"title": title,
|
||||
"prompt": prompt_text,
|
||||
"filename": prompt_path.name,
|
||||
}
|
||||
)
|
||||
|
||||
return prompt_entries
|
||||
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, List, Type
|
||||
|
||||
from .base import ModelInfo, Provider, ProviderError
|
||||
from .lmstudio import LMStudioProvider
|
||||
from .local_engine import LocalEngineProvider
|
||||
|
||||
_PROVIDER_REGISTRY: Dict[str, Type[Provider]] = {
|
||||
LMStudioProvider.name: LMStudioProvider,
|
||||
LocalEngineProvider.name: LocalEngineProvider,
|
||||
}
|
||||
|
||||
|
||||
def list_provider_names() -> List[str]:
|
||||
return sorted(_PROVIDER_REGISTRY.keys())
|
||||
|
||||
|
||||
def get_provider(provider_name: str, **kwargs) -> Provider:
|
||||
try:
|
||||
provider_cls = _PROVIDER_REGISTRY[provider_name]
|
||||
except KeyError as exc:
|
||||
raise ProviderError(f"Unknown provider '{provider_name}'") from exc
|
||||
return provider_cls(**kwargs)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ModelInfo",
|
||||
"Provider",
|
||||
"ProviderError",
|
||||
"list_provider_names",
|
||||
"get_provider",
|
||||
"LocalEngineProvider",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ModelInfo:
|
||||
id: str
|
||||
display_name: str
|
||||
|
||||
|
||||
class ProviderError(Exception):
|
||||
"""Base exception for provider-related errors."""
|
||||
|
||||
|
||||
class Provider(ABC):
|
||||
"""Abstract interface for model providers."""
|
||||
|
||||
name: str
|
||||
|
||||
@abstractmethod
|
||||
def list_models(self) -> List[ModelInfo]:
|
||||
"""Return the models available from this provider."""
|
||||
|
||||
@abstractmethod
|
||||
def run_prompt(self, model_id: str, prompt: str, *, temperature: float) -> Dict[str, object]:
|
||||
"""Execute a prompt against a model and return the response payload."""
|
||||
@@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import requests
|
||||
|
||||
from config import get_env_setting
|
||||
from .base import ModelInfo, Provider, ProviderError
|
||||
|
||||
DEFAULT_BASE_URL = "http://localhost:1234"
|
||||
|
||||
|
||||
class LMStudioProvider(Provider):
|
||||
name = "LM Studio"
|
||||
|
||||
def __init__(self, base_url: Optional[str] = None) -> None:
|
||||
self.base_url = base_url or get_env_setting("LM_STUDIO_BASE_URL", DEFAULT_BASE_URL)
|
||||
self._models_url = f"{self.base_url}/v1/models"
|
||||
self._chat_url = f"{self.base_url}/v1/chat/completions"
|
||||
|
||||
def list_models(self) -> List[ModelInfo]:
|
||||
try:
|
||||
response = requests.get(self._models_url, timeout=10)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as exc:
|
||||
raise ProviderError(f"Failed to fetch models from LM Studio: {exc}") from exc
|
||||
|
||||
try:
|
||||
data = response.json()
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ProviderError(f"Failed to decode model list: {exc}") from exc
|
||||
|
||||
models = data.get("data", [])
|
||||
result: List[ModelInfo] = []
|
||||
if isinstance(models, list):
|
||||
for item in models:
|
||||
if isinstance(item, dict):
|
||||
model_id = str(item.get("id") or item.get("name") or "")
|
||||
else:
|
||||
model_id = str(item)
|
||||
if not model_id:
|
||||
continue
|
||||
result.append(ModelInfo(id=model_id, display_name=model_id))
|
||||
|
||||
default_model = data.get("default_model") or data.get("model") or data.get("id")
|
||||
if default_model and all(m.id != default_model for m in result):
|
||||
result.insert(0, ModelInfo(id=str(default_model), display_name=str(default_model)))
|
||||
|
||||
if not result:
|
||||
raise ProviderError("LM Studio returned no models.")
|
||||
|
||||
return result
|
||||
|
||||
def run_prompt(self, model_id: str, prompt: str, *, temperature: float) -> Dict[str, object]:
|
||||
headers = {"Content-Type": "application/json"}
|
||||
payload = {
|
||||
"model": model_id,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"temperature": temperature,
|
||||
}
|
||||
|
||||
start_time = time.time()
|
||||
try:
|
||||
response = requests.post(
|
||||
self._chat_url,
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
timeout=180,
|
||||
)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as exc:
|
||||
return {"error": f"API request failed: {exc}"}
|
||||
|
||||
end_time = time.time()
|
||||
|
||||
try:
|
||||
data = response.json()
|
||||
except json.JSONDecodeError as exc:
|
||||
return {"error": f"Failed to parse response JSON: {exc}\nRaw: {response.text}"}
|
||||
|
||||
response_time = end_time - start_time
|
||||
content = data.get("choices", [{}])[0].get("message", {}).get("content", "Error: No content found.")
|
||||
usage = data.get("usage", {})
|
||||
total_tokens = usage.get("completion_tokens", 0)
|
||||
time_to_first_token = usage.get("prompt_eval_duration", 0) / 1_000_000_000
|
||||
stop_reason = data.get("choices", [{}])[0].get("finish_reason", "N/A")
|
||||
|
||||
tokens_per_second = total_tokens / response_time if response_time > 0 else 0
|
||||
|
||||
return {
|
||||
"response": content,
|
||||
"metrics": {
|
||||
"tokens_per_second": round(tokens_per_second, 2),
|
||||
"total_tokens": total_tokens,
|
||||
"time_to_first_token": round(time_to_first_token, 2),
|
||||
"stop_reason": stop_reason,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from config import MODELS_DIR
|
||||
from engine_loader import EngineLoadError, load_engine_class
|
||||
from .base import ModelInfo, Provider, ProviderError
|
||||
|
||||
|
||||
class LocalEngineProvider(Provider):
|
||||
name = "Local Engine"
|
||||
|
||||
def __init__(self, search_paths: Optional[List[Path]] = None) -> None:
|
||||
try:
|
||||
runtime_cls = load_engine_class()
|
||||
self.runtime = runtime_cls()
|
||||
self.runtime.setup()
|
||||
except EngineLoadError as exc:
|
||||
raise ProviderError(str(exc)) from exc
|
||||
except RuntimeError as exc:
|
||||
raise ProviderError(str(exc)) from exc
|
||||
|
||||
paths: List[Path] = [MODELS_DIR]
|
||||
env_override = os.getenv("LOCAL_LLM_PATHS")
|
||||
if env_override:
|
||||
for entry in env_override.split(os.pathsep):
|
||||
entry_clean = entry.strip()
|
||||
if not entry_clean:
|
||||
continue
|
||||
entry_path = Path(entry_clean).expanduser()
|
||||
if entry_path not in paths:
|
||||
paths.append(entry_path)
|
||||
if search_paths:
|
||||
for candidate in search_paths:
|
||||
if candidate not in paths:
|
||||
paths.append(candidate)
|
||||
|
||||
self.search_paths = paths
|
||||
self._model_index: Dict[str, Path] = {}
|
||||
|
||||
def list_models(self) -> List[ModelInfo]:
|
||||
discovered = self.runtime.discover_gguf_models(self.search_paths)
|
||||
self._model_index.clear()
|
||||
models: List[ModelInfo] = []
|
||||
for path in discovered:
|
||||
model_id = self._register_model(path)
|
||||
models.append(ModelInfo(id=model_id, display_name=path.stem))
|
||||
if not models:
|
||||
raise ProviderError(
|
||||
"No GGUF models were found. Place models inside the 'models' directory or set LOCAL_LLM_PATHS."
|
||||
)
|
||||
return models
|
||||
|
||||
def run_prompt(self, model_id: str, prompt: str, *, temperature: float) -> Dict[str, object]:
|
||||
try:
|
||||
model_path = self._model_index[model_id]
|
||||
except KeyError as exc:
|
||||
raise ProviderError(f"Model '{model_id}' is not registered. Refresh the model list and try again.") from exc
|
||||
|
||||
self.runtime.load_model(model_path)
|
||||
return self.runtime.generate(prompt, temperature=temperature)
|
||||
|
||||
def _register_model(self, path: Path) -> str:
|
||||
for existing_id, existing_path in self._model_index.items():
|
||||
if path == existing_path:
|
||||
return existing_id
|
||||
|
||||
base_id = path.stem
|
||||
candidate = base_id
|
||||
counter = 1
|
||||
while candidate in self._model_index:
|
||||
digest = hashlib.sha1(str(path).encode("utf-8")).hexdigest()[:6]
|
||||
candidate = f"{base_id}-{digest}-{counter}"
|
||||
counter += 1
|
||||
|
||||
self._model_index[candidate] = path
|
||||
return candidate
|
||||
@@ -0,0 +1,206 @@
|
||||
import hashlib
|
||||
import re
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from config import RESULTS_DIR, TEMPLATE_PATH, TEMP_DIR
|
||||
from markdown_linter import infer_language_from_prompt, lint_response_markdown
|
||||
|
||||
_TEMPLATE_CACHE: Optional[str] = None
|
||||
|
||||
|
||||
class TemplateNotFoundError(FileNotFoundError):
|
||||
pass
|
||||
|
||||
|
||||
def load_template() -> str:
|
||||
"""Load the test block template from disk, caching the content."""
|
||||
global _TEMPLATE_CACHE
|
||||
if _TEMPLATE_CACHE is None:
|
||||
if not TEMPLATE_PATH.exists():
|
||||
raise TemplateNotFoundError(
|
||||
f"Template file not found at '{TEMPLATE_PATH}'. Please create it before running tests."
|
||||
)
|
||||
_TEMPLATE_CACHE = TEMPLATE_PATH.read_text(encoding="utf-8")
|
||||
return _TEMPLATE_CACHE
|
||||
|
||||
|
||||
def has_unclosed_code_block(markdown_text: str) -> bool:
|
||||
"""Check if markdown text ends inside an unclosed triple-backtick block."""
|
||||
fence_count = markdown_text.count("```")
|
||||
return fence_count % 2 == 1
|
||||
|
||||
|
||||
def render_response_block(result: Dict[str, object], *, language_hint: str) -> str:
|
||||
"""Render the response portion of the template based on the result payload."""
|
||||
if "error" in result:
|
||||
return "\n".join(["### Response:", f"**ERROR:** {result['error']}", ""])
|
||||
|
||||
cleaned_response = lint_response_markdown(result["response"], language_hint=language_hint)
|
||||
|
||||
section_lines = ["### Response:"]
|
||||
if cleaned_response:
|
||||
section_lines.append("")
|
||||
section_lines.append(cleaned_response)
|
||||
section_lines.append("")
|
||||
return "\n".join(section_lines)
|
||||
|
||||
|
||||
def render_test_block(
|
||||
prompt_info: Dict[str, str],
|
||||
result: Dict[str, object],
|
||||
index: int,
|
||||
) -> str:
|
||||
"""Populate the test block template with data for a single test."""
|
||||
template = load_template()
|
||||
|
||||
metrics = result.get("metrics", {}) if "error" not in result else {}
|
||||
|
||||
ttft_value = metrics.get("time_to_first_token") if metrics else None
|
||||
language_hint = infer_language_from_prompt(prompt_info["prompt"])
|
||||
|
||||
replacements = {
|
||||
"{{TEST_NUMBER}}": str(index),
|
||||
"{{TEST_TITLE}}": prompt_info.get("title", f"Test {index}"),
|
||||
"{{SOURCE_FILENAME}}": prompt_info.get("filename", "unknown"),
|
||||
"{{PROMPT_CONTENT}}": prompt_info["prompt"].strip(),
|
||||
"{{RESPONSE_BLOCK}}": render_response_block(result, language_hint=language_hint).rstrip(),
|
||||
"{{METRIC_TOKENS_PER_SECOND}}": (
|
||||
str(metrics.get("tokens_per_second", "N/A"))
|
||||
if metrics
|
||||
else "N/A"
|
||||
),
|
||||
"{{METRIC_TOTAL_TOKENS}}": (
|
||||
str(metrics.get("total_tokens", "N/A"))
|
||||
if metrics
|
||||
else "N/A"
|
||||
),
|
||||
"{{METRIC_TTFT}}": (
|
||||
f"{ttft_value}s" if isinstance(ttft_value, (int, float)) else "N/A"
|
||||
),
|
||||
"{{METRIC_STOP_REASON}}": (
|
||||
str(metrics.get("stop_reason", "N/A"))
|
||||
if metrics
|
||||
else "N/A"
|
||||
),
|
||||
}
|
||||
|
||||
rendered = template
|
||||
for placeholder, value in replacements.items():
|
||||
rendered = rendered.replace(placeholder, value)
|
||||
|
||||
return rendered.rstrip() + "\n\n"
|
||||
|
||||
|
||||
def sanitize_model_name(model_name: str) -> str:
|
||||
sanitized = re.sub(r"[^A-Za-z0-9._-]+", "_", model_name.strip())
|
||||
return sanitized or "unknown-model"
|
||||
|
||||
|
||||
def initialize_report_file(model_label: str) -> Path:
|
||||
"""Create the markdown report shell and return its path."""
|
||||
sanitized = sanitize_model_name(model_label)
|
||||
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
report_path = RESULTS_DIR / f"automated_report_{sanitized}.md"
|
||||
|
||||
header = textwrap.dedent(
|
||||
f"""
|
||||
# Automated Diagnostic Report: {model_label}
|
||||
|
||||
---
|
||||
|
||||
## Performance Summary
|
||||
|
||||
<!--SUMMARY_START-->
|
||||
* **Average Tokens/s:** TBD
|
||||
* **Average Time to First Token:** TBD
|
||||
* **Total Tokens Generated:** TBD
|
||||
<!--SUMMARY_END-->
|
||||
|
||||
## Qualitative Analysis
|
||||
|
||||
*(Manual grading and analysis of the responses is required to determine the final letter grade.)*
|
||||
|
||||
---
|
||||
|
||||
"""
|
||||
).lstrip()
|
||||
|
||||
report_path.write_text(header, encoding="utf-8")
|
||||
return report_path
|
||||
|
||||
|
||||
def append_test_result(
|
||||
report_path: Path,
|
||||
prompt_info: Dict[str, str],
|
||||
result: Dict[str, object],
|
||||
index: int,
|
||||
) -> None:
|
||||
"""Append a single test section to the markdown report."""
|
||||
block_content = render_test_block(prompt_info, result, index)
|
||||
|
||||
slug_base = re.sub(r"[^A-Za-z0-9._-]+", "-", prompt_info.get("title", f"test-{index}")).strip("-")
|
||||
if not slug_base:
|
||||
slug_base = f"test-{index}"
|
||||
slug_hash = hashlib.sha1(slug_base.encode("utf-8")).hexdigest()[:8]
|
||||
truncated_slug = slug_base[:48]
|
||||
title_slug = f"{truncated_slug}-{slug_hash}"
|
||||
temp_file = TEMP_DIR / f"{index:03d}_{title_slug}.md"
|
||||
|
||||
try:
|
||||
temp_file.write_text(block_content, encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
block_text = temp_file.read_text(encoding="utf-8") if temp_file.exists() else block_content
|
||||
if has_unclosed_code_block(block_text):
|
||||
block_text = block_text.rstrip() + "\n```\n"
|
||||
|
||||
separator = "\n\n---\n\n"
|
||||
existing_tail = report_path.read_text(encoding="utf-8") if report_path.exists() else ""
|
||||
needs_separator = existing_tail and not existing_tail.endswith(separator)
|
||||
|
||||
with report_path.open("a", encoding="utf-8") as report_file:
|
||||
if existing_tail and has_unclosed_code_block(existing_tail):
|
||||
report_file.write("\n```\n\n")
|
||||
if needs_separator and existing_tail:
|
||||
report_file.write(separator)
|
||||
report_file.write(block_text)
|
||||
|
||||
if temp_file.exists():
|
||||
try:
|
||||
temp_file.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def finalize_report_summary(report_path: Path, results: List[Dict[str, object]]) -> None:
|
||||
"""Update the performance summary placeholder once all tests have run."""
|
||||
valid_results = [r for r in results if "error" not in r]
|
||||
|
||||
if valid_results:
|
||||
total_tok_s = sum(r["metrics"]["tokens_per_second"] for r in valid_results)
|
||||
total_ttft = sum(r["metrics"]["time_to_first_token"] for r in valid_results)
|
||||
total_tokens = sum(r["metrics"]["total_tokens"] for r in valid_results)
|
||||
avg_tok_s = round(total_tok_s / len(valid_results), 2)
|
||||
avg_ttft = round(total_ttft / len(valid_results), 2)
|
||||
else:
|
||||
avg_tok_s = avg_ttft = total_tokens = 0
|
||||
|
||||
summary_block = textwrap.dedent(
|
||||
f"""
|
||||
* **Average Tokens/s:** {avg_tok_s}
|
||||
* **Average Time to First Token:** {avg_ttft}s
|
||||
* **Total Tokens Generated:** {total_tokens}
|
||||
"""
|
||||
).strip()
|
||||
|
||||
content = report_path.read_text(encoding="utf-8")
|
||||
updated_content = re.sub(
|
||||
r"<!--SUMMARY_START-->.*?<!--SUMMARY_END-->",
|
||||
f"<!--SUMMARY_START-->\n{summary_block}\n<!--SUMMARY_END-->",
|
||||
content,
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
report_path.write_text(updated_content, encoding="utf-8")
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Callable, Dict, List, Optional
|
||||
|
||||
from config import (
|
||||
DEFAULT_PROVIDER_NAME,
|
||||
DEFAULT_TEMPERATURE,
|
||||
TEMPLATE_PATH,
|
||||
ensure_directories,
|
||||
reset_temp_directory,
|
||||
)
|
||||
from prompts import load_test_prompts
|
||||
from reporting import (
|
||||
TemplateNotFoundError,
|
||||
append_test_result,
|
||||
finalize_report_summary,
|
||||
initialize_report_file,
|
||||
)
|
||||
from providers import LocalEngineProvider, ModelInfo, ProviderError, get_provider
|
||||
from settings import get_local_model_paths
|
||||
|
||||
|
||||
class TestRunError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _choose_model(models: List[ModelInfo], requested_id: Optional[str]) -> ModelInfo:
|
||||
if not models:
|
||||
raise TestRunError("No models available from provider.")
|
||||
|
||||
if requested_id:
|
||||
for model in models:
|
||||
if model.id == requested_id:
|
||||
return model
|
||||
raise TestRunError(f"Model '{requested_id}' not found for the selected provider.")
|
||||
|
||||
return models[0]
|
||||
|
||||
|
||||
def run_suite(
|
||||
*,
|
||||
provider_name: Optional[str] = None,
|
||||
model_id: Optional[str] = None,
|
||||
temperature: Optional[float] = None,
|
||||
progress_callback: Optional[Callable[[int, int, Dict[str, str], Dict[str, object]], None]] = None,
|
||||
) -> Path:
|
||||
"""Run the diagnostic test suite and return the generated report path."""
|
||||
ensure_directories()
|
||||
reset_temp_directory()
|
||||
|
||||
provider_name = provider_name or DEFAULT_PROVIDER_NAME
|
||||
provider_kwargs = {}
|
||||
if provider_name == LocalEngineProvider.name:
|
||||
custom_paths = [Path(p).expanduser() for p in get_local_model_paths()]
|
||||
provider_kwargs["search_paths"] = [path for path in custom_paths if path]
|
||||
|
||||
try:
|
||||
provider = get_provider(provider_name, **provider_kwargs)
|
||||
except ProviderError as exc:
|
||||
raise TestRunError(str(exc)) from exc
|
||||
|
||||
try:
|
||||
models = provider.list_models()
|
||||
except ProviderError as exc:
|
||||
raise TestRunError(str(exc)) from exc
|
||||
|
||||
model = _choose_model(models, model_id)
|
||||
|
||||
if not TEMPLATE_PATH.exists():
|
||||
raise TestRunError(
|
||||
"Template file missing. Please create '.core/templates/test-block.md' before running tests."
|
||||
)
|
||||
|
||||
prompts = load_test_prompts()
|
||||
if not prompts:
|
||||
raise TestRunError("No test prompts found in the 'tests' directory.")
|
||||
|
||||
report_path = initialize_report_file(model.display_name)
|
||||
all_results: List[Dict[str, object]] = []
|
||||
selected_temperature = temperature if temperature is not None else DEFAULT_TEMPERATURE
|
||||
|
||||
total_tests = len(prompts)
|
||||
|
||||
for index, prompt in enumerate(prompts, start=1):
|
||||
result = provider.run_prompt(model.id, prompt["prompt"], temperature=selected_temperature)
|
||||
all_results.append(result)
|
||||
append_test_result(report_path, prompt, result, index)
|
||||
if progress_callback:
|
||||
progress_callback(index, total_tests, prompt, result)
|
||||
|
||||
finalize_report_summary(report_path, all_results)
|
||||
return report_path
|
||||
|
||||
|
||||
def safe_run() -> int:
|
||||
"""CLI helper for running the suite with basic error handling."""
|
||||
def _print_progress(index: int, total: int, prompt: Dict[str, str], result: Dict[str, object]) -> None:
|
||||
status = "FAILED" if "error" in result else "DONE"
|
||||
filename_label = prompt.get("filename", f"test{index}")
|
||||
title = prompt.get("title", f"Test {index}")
|
||||
print(f"Running {title} [{filename_label}] ({index}/{total})... {status}")
|
||||
|
||||
print(f"Starting automated diagnostic run with provider '{DEFAULT_PROVIDER_NAME}'…")
|
||||
|
||||
try:
|
||||
report_path = run_suite(progress_callback=_print_progress)
|
||||
except TestRunError as exc:
|
||||
print(f"Error: {exc}")
|
||||
return 1
|
||||
except TemplateNotFoundError as exc:
|
||||
print(f"Error: {exc}")
|
||||
return 1
|
||||
|
||||
print(f"\n✅ Success! Report saved to '{report_path.name}'.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(safe_run())
|
||||
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
from config import CORE_DIR
|
||||
|
||||
SETTINGS_PATH = CORE_DIR / "user_settings.json"
|
||||
|
||||
_DEFAULT_LOCAL_PATHS: List[str] = []
|
||||
for candidate in (
|
||||
Path.home() / ".lmstudio",
|
||||
Path.home() / ".lmstudio/models",
|
||||
Path.home() / "Library/Application Support/lm-studio/models",
|
||||
):
|
||||
normalized = str(candidate)
|
||||
if normalized not in _DEFAULT_LOCAL_PATHS:
|
||||
_DEFAULT_LOCAL_PATHS.append(normalized)
|
||||
|
||||
_DEFAULT_SETTINGS: Dict[str, object] = {
|
||||
"local_model_paths": _DEFAULT_LOCAL_PATHS,
|
||||
}
|
||||
|
||||
|
||||
def load_settings() -> Dict[str, object]:
|
||||
if not SETTINGS_PATH.exists():
|
||||
return dict(_DEFAULT_SETTINGS)
|
||||
try:
|
||||
data = json.loads(SETTINGS_PATH.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return dict(_DEFAULT_SETTINGS)
|
||||
if not isinstance(data, dict):
|
||||
return dict(_DEFAULT_SETTINGS)
|
||||
merged = dict(_DEFAULT_SETTINGS)
|
||||
merged.update(data)
|
||||
return merged
|
||||
|
||||
|
||||
def save_settings(settings: Dict[str, object]) -> None:
|
||||
SETTINGS_PATH.write_text(json.dumps(settings, indent=2, sort_keys=True), encoding="utf-8")
|
||||
|
||||
|
||||
def get_local_model_paths() -> List[str]:
|
||||
settings = load_settings()
|
||||
paths = settings.get("local_model_paths", [])
|
||||
if not isinstance(paths, list):
|
||||
return []
|
||||
result: List[str] = []
|
||||
for entry in paths:
|
||||
if isinstance(entry, str) and entry.strip():
|
||||
normalized = str(Path(entry).expanduser())
|
||||
if normalized not in result:
|
||||
result.append(normalized)
|
||||
return result
|
||||
|
||||
|
||||
def set_local_model_paths(paths: List[str]) -> None:
|
||||
settings = load_settings()
|
||||
normalized: List[str] = []
|
||||
for entry in paths:
|
||||
if isinstance(entry, str) and entry.strip():
|
||||
candidate = str(Path(entry).expanduser())
|
||||
if candidate not in normalized:
|
||||
normalized.append(candidate)
|
||||
settings["local_model_paths"] = normalized
|
||||
save_settings(settings)
|
||||
@@ -0,0 +1,18 @@
|
||||
## Test {{TEST_NUMBER}}: {{TEST_TITLE}}
|
||||
|
||||
*Source:* `{{SOURCE_FILENAME}}`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
{{PROMPT_CONTENT}}
|
||||
```
|
||||
|
||||
{{RESPONSE_BLOCK}}
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** {{METRIC_TOKENS_PER_SECOND}}
|
||||
* **Total Tokens:** {{METRIC_TOTAL_TOKENS}}
|
||||
* **Time to First Token:** {{METRIC_TTFT}}
|
||||
* **Stop Reason:** {{METRIC_STOP_REASON}}
|
||||
|
||||
---
|
||||
@@ -0,0 +1,77 @@
|
||||
# LLM Automated Test Suite
|
||||
|
||||
This project automates a battery of diagnostic prompts against local or remote language models via a modular core runner and an optional GUI.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10 or newer (tkinter included with the standard library on macOS/Linux; install `python3-tk` on Debian-based systems if needed).
|
||||
- Install dependencies:
|
||||
|
||||
```bash
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Project Layout
|
||||
|
||||
- `.core/` – shared logic modules, templates, engines, and transient workspace used by both CLI and GUI.
|
||||
- `config.py` – path/setting helpers.
|
||||
- `prompts.py` – prompt discovery from `tests/`.
|
||||
- `providers/` – provider adapters (LM Studio and the native Local Engine runtime).
|
||||
- `reporting.py` – markdown templating, code-fence hygiene, report summaries.
|
||||
- `runner.py` – orchestrates full test runs with progress callbacks.
|
||||
- `engine_loader.py` – detects the host platform/GPU and loads the appropriate runtime.
|
||||
- `.engine/.<architecture>/<version>.py` – runtime implementations per hardware family (Apple Silicon ↦ MLX, CUDA ↦ llama.cpp, ROCm ↦ llama.cpp, CPU fallback).
|
||||
- `.temp/` – ephemeral staging for rendered blocks.
|
||||
- `templates/test-block.md` – markdown template for each test result.
|
||||
- `models/` – drop-in directory for local `.gguf` (and MLX-compatible) weights discovered by the Local Engine provider.
|
||||
- `tests/` – one prompt per `.txt` file (editable via the GUI or any editor).
|
||||
- `results/` – generated markdown reports.
|
||||
- `auto-test.py` – CLI entrypoint that runs the default provider/model.
|
||||
- `index.py` – Tkinter GUI for provider/model selection, running tests, and opening prompts/results.
|
||||
|
||||
## Running the CLI
|
||||
|
||||
```bash
|
||||
python auto-test.py
|
||||
```
|
||||
|
||||
The CLI selects the first available model for the default provider (LM Studio by default) and streams progress to stdout. Reports are saved in `results/automated_report_<model>.md`.
|
||||
|
||||
To switch to the Local Engine runtime, set `AUTO_TEST_PROVIDER="Local Engine"` (or choose it in the GUI) and ensure your models are available in the `models/` directory or any path listed in `LOCAL_LLM_PATHS`.
|
||||
|
||||
## Using the GUI
|
||||
|
||||
```bash
|
||||
python index.py
|
||||
```
|
||||
|
||||
GUI features:
|
||||
|
||||
- Provider dropdown with automatic model discovery.
|
||||
- Model dropdown populated per provider.
|
||||
- Adjustable sampling temperature.
|
||||
- Buttons to run tests, refresh models, open the `tests/` folder, open the `results/` folder, and open the latest report.
|
||||
- Live log of test progress and completion status.
|
||||
- Settings menu → “Configure Model Paths…” to manage additional folders scanned by the Local Engine provider (persisted in `.core/user_settings.json`).
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables:
|
||||
|
||||
- `LM_STUDIO_BASE_URL` – override the default `http://localhost:1234` endpoint for the LM Studio provider.
|
||||
- `AUTO_TEST_TEMPERATURE` – default sampling temperature for test runs.
|
||||
- `AUTO_TEST_PROVIDER` – default provider name for CLI runs.
|
||||
- `LOCAL_LLM_PATHS` – optional `os.pathsep`-separated list of extra directories to scan for `.gguf` weights.
|
||||
|
||||
Templates can be customized by editing `.core/templates/test-block.md`.
|
||||
|
||||
### Local Engine provider
|
||||
|
||||
- Place `.gguf` (llama.cpp) weights inside `models/` or add extra directories via the GUI settings dialog / `LOCAL_LLM_PATHS` environment variable.
|
||||
- Paths include common defaults (for example `~/.lmstudio`, `~/.lmstudio/models`, or `~/Library/Application Support/lm-studio/models` on macOS) so LM Studio downloads are discovered automatically.
|
||||
- The engine loader automatically selects the best runtime for your hardware: MLX on Apple Silicon, CUDA on NVIDIA GPUs, ROCm on AMD GPUs, or a CPU fallback via `llama-cpp-python`.
|
||||
- Settings are saved in `.core/user_settings.json`, keeping CLI and GUI runs in sync.
|
||||
|
||||
## Extending Providers
|
||||
|
||||
Provider adapters live under `.core/providers/` and are registered in `.core/providers/__init__.py`. Each provider implements `list_models` and `run_prompt` to integrate with the runner and GUI, while hardware-specific runtimes reside under `.core/.engine/`.
|
||||
@@ -0,0 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
CORE_DIR = Path(__file__).resolve().parent / ".core"
|
||||
if str(CORE_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(CORE_DIR))
|
||||
|
||||
from runner import safe_run # type: ignore # noqa: E402
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(safe_run())
|
||||
@@ -0,0 +1,339 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
from tkinter import filedialog, messagebox, scrolledtext, ttk
|
||||
import tkinter as tk
|
||||
|
||||
CORE_DIR = Path(__file__).resolve().parent / ".core"
|
||||
if str(CORE_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(CORE_DIR))
|
||||
|
||||
from config import RESULTS_DIR, TESTS_DIR # type: ignore # noqa: E402
|
||||
from providers import ( # type: ignore # noqa: E402
|
||||
LocalEngineProvider,
|
||||
ProviderError,
|
||||
get_provider,
|
||||
list_provider_names,
|
||||
)
|
||||
from reporting import TemplateNotFoundError # type: ignore # noqa: E402
|
||||
from runner import TestRunError, run_suite # type: ignore # noqa: E402
|
||||
from settings import get_local_model_paths, set_local_model_paths # type: ignore # noqa: E402
|
||||
|
||||
|
||||
def open_path(path: Path) -> None:
|
||||
if sys.platform.startswith("darwin"):
|
||||
subprocess.run(["open", str(path)], check=False)
|
||||
elif os.name == "nt": # Windows
|
||||
os.startfile(str(path)) # type: ignore[attr-defined]
|
||||
else:
|
||||
subprocess.run(["xdg-open", str(path)], check=False)
|
||||
|
||||
|
||||
class App(tk.Tk):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.title("LLM Automated Test Console")
|
||||
self.geometry("880x620")
|
||||
self.minsize(760, 520)
|
||||
|
||||
self.provider_names = list_provider_names()
|
||||
self.provider_var = tk.StringVar(value=self.provider_names[0] if self.provider_names else "")
|
||||
self.model_var = tk.StringVar()
|
||||
self.temperature_var = tk.DoubleVar(value=0.1)
|
||||
self.status_var = tk.StringVar(value="Idle")
|
||||
|
||||
self.available_models: List[Tuple[str, str]] = [] # (id, display name)
|
||||
self.running = False
|
||||
|
||||
self._build_ui()
|
||||
self._build_menu()
|
||||
if self.provider_var.get():
|
||||
self.fetch_models_async()
|
||||
|
||||
def _build_ui(self) -> None:
|
||||
padding = {"padx": 12, "pady": 8}
|
||||
|
||||
header = ttk.Label(self, text="Automated Diagnostic Test Runner", font=("Helvetica", 18, "bold"))
|
||||
header.pack(anchor="w", **padding)
|
||||
|
||||
form_frame = ttk.Frame(self)
|
||||
form_frame.pack(fill="x", **padding)
|
||||
|
||||
ttk.Label(form_frame, text="Provider:").grid(row=0, column=0, sticky="w")
|
||||
self.provider_combo = ttk.Combobox(
|
||||
form_frame,
|
||||
textvariable=self.provider_var,
|
||||
values=self.provider_names,
|
||||
state="readonly",
|
||||
)
|
||||
self.provider_combo.grid(row=0, column=1, sticky="ew", padx=(6, 18))
|
||||
self.provider_combo.bind("<<ComboboxSelected>>", lambda event: self.fetch_models_async())
|
||||
|
||||
ttk.Label(form_frame, text="Model:").grid(row=0, column=2, sticky="w")
|
||||
self.model_combo = ttk.Combobox(form_frame, textvariable=self.model_var, state="readonly")
|
||||
self.model_combo.grid(row=0, column=3, sticky="ew", padx=(6, 18))
|
||||
|
||||
ttk.Label(form_frame, text="Temperature:").grid(row=0, column=4, sticky="w")
|
||||
self.temperature_spin = ttk.Spinbox(
|
||||
form_frame,
|
||||
textvariable=self.temperature_var,
|
||||
from_=0.0,
|
||||
to=1.0,
|
||||
increment=0.05,
|
||||
width=6,
|
||||
)
|
||||
self.temperature_spin.grid(row=0, column=5, sticky="w")
|
||||
|
||||
form_frame.columnconfigure(1, weight=1)
|
||||
form_frame.columnconfigure(3, weight=1)
|
||||
|
||||
button_frame = ttk.Frame(self)
|
||||
button_frame.pack(fill="x", **padding)
|
||||
|
||||
self.run_button = ttk.Button(button_frame, text="Run Tests", command=self.start_run)
|
||||
self.run_button.pack(side="left")
|
||||
|
||||
ttk.Button(button_frame, text="Refresh Models", command=self.fetch_models_async).pack(side="left", padx=(12, 0))
|
||||
ttk.Button(button_frame, text="Edit Tests", command=lambda: open_path(TESTS_DIR)).pack(side="left", padx=(12, 0))
|
||||
ttk.Button(button_frame, text="Open Results", command=lambda: open_path(RESULTS_DIR)).pack(side="left", padx=(12, 0))
|
||||
ttk.Button(button_frame, text="View Latest Report", command=self.open_latest_report).pack(side="left", padx=(12, 0))
|
||||
|
||||
ttk.Label(self, textvariable=self.status_var).pack(anchor="w", **padding)
|
||||
|
||||
self.log = scrolledtext.ScrolledText(self, wrap="word", height=22)
|
||||
self.log.pack(fill="both", expand=True, padx=12, pady=(0, 12))
|
||||
self.log.configure(state="disabled")
|
||||
|
||||
def _build_menu(self) -> None:
|
||||
menubar = tk.Menu(self)
|
||||
settings_menu = tk.Menu(menubar, tearoff=0)
|
||||
settings_menu.add_command(label="Configure Model Paths…", command=self.configure_model_paths)
|
||||
menubar.add_cascade(label="Settings", menu=settings_menu)
|
||||
self.config(menu=menubar)
|
||||
self._settings_menu = settings_menu
|
||||
|
||||
def fetch_models_async(self) -> None:
|
||||
if self.running:
|
||||
return
|
||||
|
||||
provider_name = self.provider_var.get()
|
||||
if not provider_name:
|
||||
return
|
||||
|
||||
self._set_status(f"Fetching models for {provider_name}…")
|
||||
self.model_combo.configure(values=())
|
||||
self.model_var.set("")
|
||||
|
||||
thread = threading.Thread(target=self._fetch_models, daemon=True)
|
||||
thread.start()
|
||||
|
||||
def _fetch_models(self) -> None:
|
||||
provider_name = self.provider_var.get()
|
||||
try:
|
||||
provider = get_provider(provider_name, **self._provider_kwargs(provider_name))
|
||||
models = provider.list_models()
|
||||
except ProviderError as exc:
|
||||
message = str(exc)
|
||||
self.after(0, lambda m=message: self._on_model_fetch_error(m))
|
||||
return
|
||||
|
||||
model_pairs = [(model.id, model.display_name) for model in models]
|
||||
self.after(0, lambda: self._on_models_loaded(model_pairs))
|
||||
|
||||
def _on_model_fetch_error(self, message: str) -> None:
|
||||
self._set_status("Model fetch failed")
|
||||
messagebox.showerror("Model Load Error", message)
|
||||
|
||||
def _on_models_loaded(self, model_pairs: List[Tuple[str, str]]) -> None:
|
||||
self.available_models = model_pairs
|
||||
options = [display for _, display in model_pairs]
|
||||
self.model_combo.configure(values=options)
|
||||
if options:
|
||||
self.model_var.set(options[0])
|
||||
self._set_status(f"Loaded {len(options)} models.")
|
||||
else:
|
||||
self.model_var.set("")
|
||||
self._set_status("No models found.")
|
||||
|
||||
def start_run(self) -> None:
|
||||
if self.running:
|
||||
return
|
||||
|
||||
provider_name = self.provider_var.get()
|
||||
model_display = self.model_var.get()
|
||||
|
||||
if not provider_name:
|
||||
messagebox.showwarning("Missing Provider", "Please select a provider before running tests.")
|
||||
return
|
||||
|
||||
if not model_display:
|
||||
messagebox.showwarning("Missing Model", "Please select a model before running tests.")
|
||||
return
|
||||
|
||||
model_id = self._model_id_for_display(model_display)
|
||||
if not model_id:
|
||||
messagebox.showerror("Model Error", "Unable to resolve the selected model.")
|
||||
return
|
||||
|
||||
temperature = float(self.temperature_var.get())
|
||||
self.running = True
|
||||
self._set_status("Running tests…")
|
||||
self._append_log(f"Starting run with provider '{provider_name}' and model '{model_display}'.")
|
||||
self._toggle_controls(state="disabled")
|
||||
|
||||
thread = threading.Thread(
|
||||
target=self._run_tests_thread,
|
||||
args=(provider_name, model_id, temperature),
|
||||
daemon=True,
|
||||
)
|
||||
thread.start()
|
||||
|
||||
def _run_tests_thread(self, provider_name: str, model_id: str, temperature: float) -> None:
|
||||
start_time = time.time()
|
||||
|
||||
def progress_callback(index: int, total: int, prompt: Dict[str, str], result: Dict[str, object]) -> None:
|
||||
status = "FAILED" if "error" in result else "DONE"
|
||||
title = prompt.get("title", f"Test {index}")
|
||||
filename = prompt.get("filename", f"test{index}")
|
||||
message = f"[{index}/{total}] {title} ({filename}) -> {status}"
|
||||
self.after(0, lambda: self._append_log(message))
|
||||
|
||||
try:
|
||||
report_path = run_suite(
|
||||
provider_name=provider_name,
|
||||
model_id=model_id,
|
||||
temperature=temperature,
|
||||
progress_callback=progress_callback,
|
||||
)
|
||||
except (TestRunError, TemplateNotFoundError) as exc:
|
||||
message = str(exc)
|
||||
self.after(0, lambda m=message: self._handle_run_error(m))
|
||||
return
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
self.after(0, lambda: self._handle_run_success(report_path, elapsed))
|
||||
|
||||
def _handle_run_error(self, message: str) -> None:
|
||||
self.running = False
|
||||
self._toggle_controls(state="normal")
|
||||
self._set_status("Run failed")
|
||||
self._append_log(f"Error: {message}")
|
||||
messagebox.showerror("Run Failed", message)
|
||||
|
||||
def _handle_run_success(self, report_path: Path, elapsed: float) -> None:
|
||||
self.running = False
|
||||
self._toggle_controls(state="normal")
|
||||
self._set_status("Run complete")
|
||||
self._append_log(f"Run complete in {elapsed:.2f}s. Report saved to {report_path.name}.")
|
||||
messagebox.showinfo("Run Complete", f"Report saved to {report_path}")
|
||||
|
||||
def _toggle_controls(self, *, state: str) -> None:
|
||||
widgets = [
|
||||
self.run_button,
|
||||
self.provider_combo,
|
||||
self.model_combo,
|
||||
self.temperature_spin,
|
||||
]
|
||||
for widget in widgets:
|
||||
widget.configure(state=state)
|
||||
|
||||
def _append_log(self, message: str) -> None:
|
||||
timestamp = time.strftime("%H:%M:%S")
|
||||
self.log.configure(state="normal")
|
||||
self.log.insert("end", f"[{timestamp}] {message}\n")
|
||||
self.log.see("end")
|
||||
self.log.configure(state="disabled")
|
||||
|
||||
def _set_status(self, message: str) -> None:
|
||||
self.status_var.set(message)
|
||||
|
||||
def _model_id_for_display(self, display_name: str) -> str | None:
|
||||
for model_id, display in self.available_models:
|
||||
if display == display_name:
|
||||
return model_id
|
||||
return None
|
||||
|
||||
def _provider_kwargs(self, provider_name: str) -> Dict[str, object]:
|
||||
if provider_name == LocalEngineProvider.name:
|
||||
custom_paths = [Path(p).expanduser() for p in get_local_model_paths()]
|
||||
return {"search_paths": [path for path in custom_paths if path]}
|
||||
return {}
|
||||
|
||||
def configure_model_paths(self) -> None:
|
||||
dialog = tk.Toplevel(self)
|
||||
dialog.title("Configure Model Paths")
|
||||
dialog.geometry("520x320")
|
||||
dialog.transient(self)
|
||||
dialog.grab_set()
|
||||
|
||||
paths: List[str] = get_local_model_paths()
|
||||
|
||||
listbox = tk.Listbox(dialog, selectmode=tk.SINGLE, width=60, height=10)
|
||||
for entry in paths:
|
||||
listbox.insert("end", entry)
|
||||
listbox.pack(fill="both", expand=True, padx=12, pady=(12, 6))
|
||||
|
||||
button_frame = ttk.Frame(dialog)
|
||||
button_frame.pack(fill="x", padx=12, pady=6)
|
||||
|
||||
def add_path() -> None:
|
||||
selection = filedialog.askdirectory(parent=dialog)
|
||||
if selection and selection not in paths:
|
||||
paths.append(selection)
|
||||
listbox.insert("end", selection)
|
||||
|
||||
def remove_path() -> None:
|
||||
selection = listbox.curselection()
|
||||
if not selection:
|
||||
return
|
||||
index = selection[0]
|
||||
listbox.delete(index)
|
||||
paths.pop(index)
|
||||
|
||||
ttk.Button(button_frame, text="Add…", command=add_path).pack(side="left")
|
||||
ttk.Button(button_frame, text="Remove", command=remove_path).pack(side="left", padx=(8, 0))
|
||||
|
||||
action_frame = ttk.Frame(dialog)
|
||||
action_frame.pack(fill="x", padx=12, pady=(0, 12))
|
||||
|
||||
def on_save() -> None:
|
||||
normalized = [str(Path(p).expanduser()) for p in paths]
|
||||
set_local_model_paths(normalized)
|
||||
dialog.grab_release()
|
||||
dialog.destroy()
|
||||
if self.provider_var.get() == LocalEngineProvider.name:
|
||||
self.fetch_models_async()
|
||||
|
||||
def on_cancel() -> None:
|
||||
dialog.grab_release()
|
||||
dialog.destroy()
|
||||
|
||||
dialog.protocol("WM_DELETE_WINDOW", on_cancel)
|
||||
|
||||
ttk.Button(action_frame, text="Save", command=on_save).pack(side="right", padx=(8, 0))
|
||||
ttk.Button(action_frame, text="Cancel", command=on_cancel).pack(side="right")
|
||||
|
||||
def open_latest_report(self) -> None:
|
||||
if not RESULTS_DIR.exists():
|
||||
messagebox.showinfo("No Reports", "The results directory does not exist yet.")
|
||||
return
|
||||
|
||||
reports = list(RESULTS_DIR.glob("*.md"))
|
||||
if not reports:
|
||||
messagebox.showinfo("No Reports", "No markdown reports found yet.")
|
||||
return
|
||||
|
||||
latest_report = max(reports, key=lambda p: p.stat().st_mtime)
|
||||
open_path(latest_report)
|
||||
self._append_log(f"Opened latest report: {latest_report.name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = App()
|
||||
app.mainloop()
|
||||
@@ -0,0 +1,3 @@
|
||||
requests>=2.31.0
|
||||
llama-cpp-python>=0.2.82
|
||||
mlx-lm>=0.10.0; platform_system == "Darwin"
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,304 @@
|
||||
# Automated Diagnostic Report: DeepSeek-Coder-V2-Lite-Instruct-IQ2_XS
|
||||
|
||||
---
|
||||
|
||||
## Performance Summary
|
||||
|
||||
<!--SUMMARY_START-->
|
||||
* **Average Tokens/s:** TBD
|
||||
* **Average Time to First Token:** TBD
|
||||
* **Total Tokens Generated:** TBD
|
||||
<!--SUMMARY_END-->
|
||||
|
||||
## Qualitative Analysis
|
||||
|
||||
*(Manual grading and analysis of the responses is required to determine the final letter grade.)*
|
||||
|
||||
---
|
||||
|
||||
## Test 1: Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
*Source:* `test1.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
The function must have the exact signature:
|
||||
`func generateFizzBuzz(upTo max: Int) -> [String]`
|
||||
|
||||
It must not print directly to the console. Instead, it should return an array of strings.
|
||||
|
||||
Rules:
|
||||
1. For numbers divisible by 3, the string should be "Fizz".
|
||||
2. For numbers divisible by 5, the string should be "Buzz".
|
||||
3. For numbers divisible by both 3 and 5, the string should be "FizzBuzz".
|
||||
4. Otherwise, it should be the number itself as a string.
|
||||
|
||||
Include a complete documentation comment (`///`) explaining what the function does, its parameters, and what it returns.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
/// Generates an array of strings representing the FizzBuzz sequence up to the specified number.
|
||||
///
|
||||
/// - Parameter upTo: The maximum number to check.
|
||||
/// - Returns: An array of strings representing the FizzBuzz sequence.
|
||||
func generateFizzBuzz(upTo max: Int) -> [String] {
|
||||
var result: [String] = []
|
||||
for i in 1...max {
|
||||
if i % 3 == 0 {
|
||||
if i % 5 == 0 {
|
||||
result.append("FizzBuzz")
|
||||
} else {
|
||||
result.append("Fizz")
|
||||
}
|
||||
} else if i % 5 == 0 {
|
||||
result.append("Buzz")
|
||||
} else {
|
||||
result.append(String(i))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
This Swift function takes an integer `max` as input and returns an array of strings representing the FizzBuzz sequence up to that number. The function iterates through numbers from 1 to `max` and checks the conditions for divisibility by 3 and 5, appending the appropriate string to the result array.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 38.03
|
||||
* **Total Tokens:** 436
|
||||
* **Time to First Token:** 0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 2: Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
*Source:* `test2.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
The view should be named `UserProfileCard` and be initialized with a simple `User` struct containing `name: String`, `handle: String`, and `avatarURL: URL`.
|
||||
|
||||
Requirements:
|
||||
- Use an `AsyncImage` to load the user's avatar from the URL.
|
||||
- Display the user's name in a `.headline` font and their handle (e.g., "@username") in a `.subheadline` font with a secondary color.
|
||||
- Include a "Follow" button that uses the "person.badge.plus" SF Symbol.
|
||||
- Arrange the elements cleanly inside an HStack with appropriate spacing.
|
||||
- The entire card should have a subtle shadow and rounded corners.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
To create a SwiftUI view for a user profile card using SwiftUI, you can follow the steps below. This example assumes you have a basic understanding of SwiftUI and Swift. You can create a new SwiftUI project and paste the following code into yourContentView.swift file:
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct User {
|
||||
var name: String
|
||||
var handle: String
|
||||
var avatarURL: URL?
|
||||
}
|
||||
|
||||
struct UserProfileCardView: View {
|
||||
var user: User
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 15) {
|
||||
AsyncImage(url: user.avatarURL) { image in
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipShape(Circle())
|
||||
} placeholder: {
|
||||
Circle()
|
||||
.scaledToFit()
|
||||
.clipShape(Circle())
|
||||
}
|
||||
Text(user.name)
|
||||
.font(.headline)
|
||||
Text(user.handle)
|
||||
.font(.subheadline)
|
||||
Button(action: {
|
||||
// Action to follow the user
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "person.badge.plus")
|
||||
Text("Follow")
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(.white)
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: 5)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
var user: User = User(name: "John Doe", handle: "@johndoe", avatarURL: URL(string: "https://example.com/avatar.png"))
|
||||
|
||||
var body: some View {
|
||||
UserProfileCardView(user: user)
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This code defines a `ContentView` that includes a `UserProfileCardView` which displays the user's name, handle, and avatar URL. The `AsyncImage` is used to load the image asynchronously. The `UserProfileCardView` is a SwiftUI view that takes a `User` object as a parameter.
|
||||
|
||||
Here's a more detailed breakdown of the code:
|
||||
|
||||
1. **Struct Definition**: The `UserProfileCardView` struct is defined to encapsulate the user's name, handle, and avatar URL.
|
||||
2. **AsyncImage**: This is a custom view that loads the image asynchronously using `AsyncImage`.
|
||||
3. **View Initialization**: The `UserProfileCardView` is initialized with a `User` object.
|
||||
4. **Styling**: The view is styled using `VStack`, `Text`, and `Button` to display the user's name, handle, and avatar URL.
|
||||
5. **Shadow and Corner Radius**: The card styling is achieved using `shadow` and `cornerRadius`.
|
||||
|
||||
This code provides a basic implementation of a user profile card using SwiftUI. You can further customize the design and functionality based on your specific requirements.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 37.75
|
||||
* **Total Tokens:** 855
|
||||
* **Time to First Token:** 0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 3: I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
*Source:* `test3.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
The new function should be marked with `async throws` and return a `[Post]` object upon success. Also, include an example of how to call this new async function inside a `Task` with a `do-catch` block.
|
||||
|
||||
**Old Code:**
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPosts(completion: @escaping (Result<[Post], Error>) -> Void) {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else { return }
|
||||
URLSession.shared.dataTask(with: url) { data, _, error in
|
||||
if let error = error { completion(.failure(error)); return }
|
||||
guard let data = data else { return }
|
||||
do {
|
||||
let posts = try JSONDecoder().decode([Post].self, from: data)
|
||||
completion(.success(posts))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
To refactor the given networking code to use modern Swift Concurrency with async/await, you can use the `URLSession.shared.dataTask(with: URLSession.shared.dataTaskPublisher(for: URLRequest(url: url)))` method to fetch data asynchronously. Here's how you can refactor the code to use async/await and handle errors properly using `do-catch` blocks:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPosts() async throws -> [Post] {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else {
|
||||
throw URLError.invalidURL
|
||||
}
|
||||
|
||||
do {
|
||||
let (data, response) = try await URLSession.shared.dataTaskPublisher(for: URLRequest(url: url))
|
||||
guard let data = data else {
|
||||
throw URLError.invalidResponse
|
||||
}
|
||||
let posts = try JSONDecoder().decode([Post].self, from: data)
|
||||
return posts
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
func exampleUsage() {
|
||||
do {
|
||||
let posts = try await fetchPosts()
|
||||
print(posts)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here's the refactored code:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPosts() async throws -> [Post] {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else {
|
||||
throw URLError.invalidURL
|
||||
}
|
||||
|
||||
do {
|
||||
let (data, response) = try await URLSession.shared.dataTask(with: URLRequest(url: url))
|
||||
guard let data = data else {
|
||||
throw URLError.invalidResponse
|
||||
}
|
||||
let posts = try JSONDecoder().decode([Post].self, from: data)
|
||||
return posts
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
func exampleUsage() {
|
||||
Task {
|
||||
do {
|
||||
let posts = try await fetchPosts()
|
||||
print(posts)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this refactored code:
|
||||
- The function `fetchPosts` is marked with `async` and uses `URLSession` to fetch data asynchronously.
|
||||
- The function handles errors using `do-catch` blocks to catch any errors that occur during the asynchronous task.
|
||||
- The function returns a list of `Post` objects upon success.
|
||||
|
||||
Note: This code assumes that you have a URLSession setup for network operations. You might need to adjust the code to fit your actual implementation of URLSession.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 36.19
|
||||
* **Total Tokens:** 911
|
||||
* **Time to First Token:** 0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1468
File diff suppressed because it is too large
Load Diff
+1468
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,648 @@
|
||||
# Automated Diagnostic Report: codellama-13b-instruct
|
||||
|
||||
---
|
||||
|
||||
## Performance Summary
|
||||
|
||||
<!--SUMMARY_START-->
|
||||
* **Average Tokens/s:** 9.07
|
||||
* **Average Time to First Token:** 0.0s
|
||||
* **Total Tokens Generated:** 2589
|
||||
<!--SUMMARY_END-->
|
||||
|
||||
## Qualitative Analysis
|
||||
|
||||
*(Manual grading and analysis of the responses is required to determine the final letter grade.)*
|
||||
|
||||
---
|
||||
|
||||
## Test 1: Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
*Source:* `test1.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
The function must have the exact signature:
|
||||
`func generateFizzBuzz(upTo max: Int) -> [String]`
|
||||
|
||||
It must not print directly to the console. Instead, it should return an array of strings.
|
||||
|
||||
Rules:
|
||||
1. For numbers divisible by 3, the string should be "Fizz".
|
||||
2. For numbers divisible by 5, the string should be "Buzz".
|
||||
3. For numbers divisible by both 3 and 5, the string should be "FizzBuzz".
|
||||
4. Otherwise, it should be the number itself as a string.
|
||||
|
||||
Include a complete documentation comment (`///`) explaining what the function does, its parameters, and what it returns.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
func generateFizzBuzz(upTo max: Int) -> [String] {
|
||||
var result = [String]()
|
||||
|
||||
for i in 1...max {
|
||||
if i % 3 == 0 && i % 5 == 0 {
|
||||
result.append("FizzBuzz")
|
||||
} else if i % 3 == 0 {
|
||||
result.append("Fizz")
|
||||
} else if i % 5 == 0 {
|
||||
result.append("Buzz")
|
||||
} else {
|
||||
result.append(String(i))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 9.99
|
||||
* **Total Tokens:** 149
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 2: Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
*Source:* `test2.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
The view should be named `UserProfileCard` and be initialized with a simple `User` struct containing `name: String`, `handle: String`, and `avatarURL: URL`.
|
||||
|
||||
Requirements:
|
||||
- Use an `AsyncImage` to load the user's avatar from the URL.
|
||||
- Display the user's name in a `.headline` font and their handle (e.g., "@username") in a `.subheadline` font with a secondary color.
|
||||
- Include a "Follow" button that uses the "person.badge.plus" SF Symbol.
|
||||
- Arrange the elements cleanly inside an HStack with appropriate spacing.
|
||||
- The entire card should have a subtle shadow and rounded corners.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
struct UserProfileCard: View {
|
||||
let user: User
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
AsyncImage(url: user.avatarURL, placeholder: { Text("Loading...") })
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(Circle())
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(user.name).font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
Text("@\(user.handle)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Button(action: {}) {
|
||||
Image(systemName: "person.badge.plus")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color.white)
|
||||
.cornerRadius(10)
|
||||
.shadow(color: Color.gray.opacity(0.5), radius: 10, x: 0, y: 10)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 10.47
|
||||
* **Total Tokens:** 306
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 3: I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
*Source:* `test3.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
The new function should be marked with `async throws` and return a `[Post]` object upon success. Also, include an example of how to call this new async function inside a `Task` with a `do-catch` block.
|
||||
|
||||
**Old Code:**
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPosts(completion: @escaping (Result<[Post], Error>) -> Void) {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else { return }
|
||||
URLSession.shared.dataTask(with: url) { data, _, error in
|
||||
if let error = error { completion(.failure(error)); return }
|
||||
guard let data = data else { return }
|
||||
do {
|
||||
let posts = try JSONDecoder().decode([Post].self, from: data)
|
||||
completion(.success(posts))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
```
|
||||
|
||||
### Response:
|
||||
**ERROR:** API request failed: HTTPConnectionPool(host='localhost', port=1234): Read timed out. (read timeout=180)
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** N/A
|
||||
* **Total Tokens:** N/A
|
||||
* **Time to First Token:** N/A
|
||||
* **Stop Reason:** N/A
|
||||
|
||||
---
|
||||
|
||||
## Test 4: Create a complete SwiftData example for a macOS 26 app.
|
||||
|
||||
*Source:* `test4.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a complete SwiftData example for a macOS 26 app.
|
||||
|
||||
You must define two models: `Author` and `Book`. An `Author` can have many `Book`s (a one-to-many relationship).
|
||||
|
||||
Then, create a SwiftUI view that displays a list of all authors. When an author's name is tapped, it should navigate to a detail view that shows a list of all the books written by that specific author. Use a SwiftData `@Query` to fetch the data.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct Author: Model {
|
||||
var id = UUID()
|
||||
var name: String
|
||||
}
|
||||
|
||||
extension Author: Identifiable {}
|
||||
|
||||
struct Book: Model {
|
||||
var id = UUID()
|
||||
var title: String
|
||||
var authorId: UUID
|
||||
|
||||
@Parent(key: "authorId")
|
||||
var author: Author
|
||||
}
|
||||
|
||||
extension Book: Identifiable {}
|
||||
```
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct ContentView: View {
|
||||
@Query("SELECT * FROM Author")
|
||||
var authors: [Author] = []
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List(authors) { author in
|
||||
NavigationLink(destination: DetailView(authorId: author.id)) {
|
||||
Text(author.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct DetailView: View {
|
||||
@Query("SELECT * FROM Book WHERE authorId = :authorId")
|
||||
var books: [Book] = []
|
||||
|
||||
let authorId: UUID
|
||||
|
||||
var body: some View {
|
||||
List(books) { book in
|
||||
Text(book.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 8.26
|
||||
* **Total Tokens:** 307
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 5: Write a thread-safe counter class in Swift using an `actor`.
|
||||
|
||||
*Source:* `test5.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a thread-safe counter class in Swift using an `actor`.
|
||||
|
||||
The actor should be named `SafeCounter`. It must have a private property to hold the current count and two public methods:
|
||||
1. `func increment() async`
|
||||
2. `func getCount() async -> Int`
|
||||
|
||||
Also, provide a simple example of how to create an instance of the actor and call its methods concurrently from two different `Task`s.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
actor SafeCounter {
|
||||
private var count = 0
|
||||
|
||||
func increment() async {
|
||||
await Task.sleep(UInt64.random(in: 1...5) * 1_000_000) // simulate a long-running task
|
||||
|
||||
count += 1
|
||||
}
|
||||
|
||||
func getCount() async -> Int {
|
||||
return count
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example usage:
|
||||
```swift
|
||||
let counter = SafeCounter()
|
||||
|
||||
Task.init {
|
||||
for _ in 0..<5 {
|
||||
await counter.increment()
|
||||
}
|
||||
}.run()
|
||||
|
||||
Task.init {
|
||||
for _ in 0..<5 {
|
||||
let count = await counter.getCount()
|
||||
|
||||
print("count: \(count)") // prints "count: 1", "count: 2", ..., "count: 10"
|
||||
}
|
||||
}.run()
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 1.02
|
||||
* **Total Tokens:** 218
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 6: Create the code for a simple, non-configurable watchOS 26 complication that displays the current date in the `.accessoryRectangular` family.
|
||||
|
||||
*Source:* `test6.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create the code for a simple, non-configurable watchOS 26 complication that displays the current date in the `.accessoryRectangular` family.
|
||||
|
||||
The code must include:
|
||||
1. A `TimelineProvider` to generate the timeline entries.
|
||||
2. A `TimelineEntry` struct.
|
||||
3. The main `Widget` struct.
|
||||
4. A SwiftUI view for the complication's appearance.
|
||||
|
||||
Ensure the code correctly uses the modern WidgetKit framework.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
struct Provider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
SimpleEntry(date: Date())
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
|
||||
let entry = SimpleEntry(date: Date())
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
|
||||
var entries: [SimpleEntry] = []
|
||||
|
||||
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
|
||||
let currentDate = Date()
|
||||
for hourOffset in 0 ..< 5 {
|
||||
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
|
||||
let entry = SimpleEntry(date: entryDate)
|
||||
entries.append(entry)
|
||||
}
|
||||
|
||||
let timeline = Timeline(entries: entries, policy: .atEnd)
|
||||
completion(timeline)
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
public let date: Date
|
||||
}
|
||||
|
||||
@main
|
||||
struct ComplicationWidget: Widget {
|
||||
private let kind: String = "ComplicationWidget"
|
||||
|
||||
public var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
||||
ComplicationView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName("Date")
|
||||
.description("Displays the current date.")
|
||||
}
|
||||
}
|
||||
|
||||
struct ComplicationView : View {
|
||||
var entry: Provider.Entry
|
||||
|
||||
var body: some View {
|
||||
Text(entry.date, style: .time)
|
||||
.font(.system(size: 18))
|
||||
.foregroundColor(.white)
|
||||
.padding()
|
||||
.background(Color.black)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 10.92
|
||||
* **Total Tokens:** 467
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 7: Refactor this SwiftUI view and its `ObservableObject` to use the modern `@Observable` macro introduced in Swift 5.9. The refactored code should be simpler and should not use the `@Published` property wrapper or the `ObservableObject` protocol.
|
||||
|
||||
*Source:* `test7.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Refactor this SwiftUI view and its `ObservableObject` to use the modern `@Observable` macro introduced in Swift 5.9. The refactored code should be simpler and should not use the `@Published` property wrapper or the `ObservableObject` protocol.
|
||||
|
||||
**Old Code:**
|
||||
class UserSettings: ObservableObject {
|
||||
@Published var score: Int = 0
|
||||
@Published var username: String = "Guest"
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@StateObject private var settings = UserSettings()
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Username", text: $settings.username)
|
||||
Stepper("Score: \(settings.score)", value: $settings.score)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
class UserSettings: ObservableObject {
|
||||
@Observable var score = 0
|
||||
@Observable var username = "Guest"
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@StateObject private var settings = UserSettings()
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Username", text: $settings.username)
|
||||
Stepper("Score: \(settings.score)", value: $settings.score)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 9.41
|
||||
* **Total Tokens:** 103
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 8: Create the code for a simple, configurable WidgetKit widget for iOS 26. The widget should display a single "Note" string. The configuration logic must use the modern App Intents framework, allowing the user to edit the note's text directly from the widget's configuration screen. Do not use the old `INIntent` based system.
|
||||
|
||||
*Source:* `test8.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create the code for a simple, configurable WidgetKit widget for iOS 26. The widget should display a single "Note" string. The configuration logic must use the modern App Intents framework, allowing the user to edit the note's text directly from the widget's configuration screen. Do not use the old `INIntent` based system.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
import Intents
|
||||
|
||||
struct Provider: IntentTimelineProvider {
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
SimpleEntry(date: Date(), note: "Note")
|
||||
}
|
||||
|
||||
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
||||
let entry = SimpleEntry(date: Date(), note: "Note")
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
var entries: [SimpleEntry] = []
|
||||
|
||||
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
|
||||
let currentDate = Date()
|
||||
for hourOffset in 0 ..< 5 {
|
||||
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
|
||||
let entry = SimpleEntry(date: entryDate, note: "Note")
|
||||
entries.append(entry)
|
||||
}
|
||||
|
||||
let timeline = Timeline(entries: entries, policy: .atEnd)
|
||||
completion(timeline)
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
public let date: Date
|
||||
public let note: String
|
||||
}
|
||||
|
||||
struct WidgetEntryView : View {
|
||||
var entry: Provider.Entry
|
||||
|
||||
var body: some View {
|
||||
Text(entry.note)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct Widget: Widget {
|
||||
private let kind: String = "Widget"
|
||||
|
||||
public var body: some WidgetConfiguration {
|
||||
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
|
||||
WidgetEntryView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName("Note")
|
||||
.description("Displays a note.")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 10.93
|
||||
* **Total Tokens:** 462
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 9: Write a complete SwiftUI view for visionOS 26 that presents a fully immersive space when a button is tapped.
|
||||
|
||||
*Source:* `test9.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a complete SwiftUI view for visionOS 26 that presents a fully immersive space when a button is tapped.
|
||||
|
||||
The view must:
|
||||
1. Have a button labeled "Enter Immersive Space".
|
||||
2. Use the `@Environment(\.openImmersiveSpace)` and `@Environment(\.dismissImmersiveSpace)` variables.
|
||||
3. Be able to open an immersive space with the ID "MyImmersiveScene".
|
||||
4. Include a second button inside the immersive space to dismiss it.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@Environment(\.openImmersiveSpace) var openImmersiveSpace
|
||||
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
|
||||
|
||||
var body: some View {
|
||||
Button("Enter Immersive Space") {
|
||||
self.openImmersiveSpace(id: "MyImmersiveScene", options: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 10.01
|
||||
* **Total Tokens:** 106
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 10: Create a custom SwiftUI Layout container called `SimpleFlowLayout`.
|
||||
|
||||
*Source:* `test10.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a custom SwiftUI Layout container called `SimpleFlowLayout`.
|
||||
|
||||
This layout should arrange its subviews in a horizontal flow, starting a new row when the current row is full. It should behave similarly to a basic left-aligned flow layout in CSS.
|
||||
|
||||
The implementation must conform to the `Layout` protocol and implement both the `sizeThatFits(...)` and `placeSubviews(...)` methods.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
struct SimpleFlowLayout: Layout {
|
||||
func sizeThatFits(_ size: CGSize, subitems: [LayoutItem]) -> CGSize {
|
||||
var width = CGFloat(0)
|
||||
var height = CGFloat(0)
|
||||
|
||||
for (index, item) in subitems.enumerated() {
|
||||
let frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
|
||||
|
||||
if index % 2 == 0 {
|
||||
width += item.sizeThatFits(frame.size).width + 16
|
||||
|
||||
if width > size.width {
|
||||
width = 0
|
||||
height += item.sizeThatFits(frame.size).height + 16
|
||||
}
|
||||
} else {
|
||||
height += item.sizeThatFits(frame.size).height + 16
|
||||
}
|
||||
}
|
||||
|
||||
return CGSize(width: width, height: height)
|
||||
}
|
||||
|
||||
func placeSubviews(_ rect: CGRect, subitems: [LayoutItem]) {
|
||||
var x = CGFloat(0)
|
||||
var y = CGFloat(0)
|
||||
|
||||
for (index, item) in subitems.enumerated() {
|
||||
let frame = CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height)
|
||||
|
||||
if index % 2 == 0 {
|
||||
x += item.sizeThatFits(frame.size).width + 16
|
||||
|
||||
if x > rect.size.width {
|
||||
x = 0
|
||||
y += item.sizeThatFits(frame.size).height + 16
|
||||
}
|
||||
} else {
|
||||
y += item.sizeThatFits(frame.size).height + 16
|
||||
}
|
||||
|
||||
let origin = CGPoint(x: x, y: y)
|
||||
item.placeSubviews(CGRect(origin: origin, size: frame.size))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 10.66
|
||||
* **Total Tokens:** 471
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,775 @@
|
||||
# Automated Diagnostic Report: mistralai/codestral-22b-v0.1
|
||||
|
||||
---
|
||||
|
||||
## Performance Summary
|
||||
|
||||
<!--SUMMARY_START-->
|
||||
* **Average Tokens/s:** 6.21
|
||||
* **Average Time to First Token:** 0.0s
|
||||
* **Total Tokens Generated:** 4010
|
||||
<!--SUMMARY_END-->
|
||||
|
||||
## Qualitative Analysis
|
||||
|
||||
*(Manual grading and analysis of the responses is required to determine the final letter grade.)*
|
||||
|
||||
---
|
||||
|
||||
## Test 1: Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
*Source:* `test1.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
The function must have the exact signature:
|
||||
`func generateFizzBuzz(upTo max: Int) -> [String]`
|
||||
|
||||
It must not print directly to the console. Instead, it should return an array of strings.
|
||||
|
||||
Rules:
|
||||
1. For numbers divisible by 3, the string should be "Fizz".
|
||||
2. For numbers divisible by 5, the string should be "Buzz".
|
||||
3. For numbers divisible by both 3 and 5, the string should be "FizzBuzz".
|
||||
4. Otherwise, it should be the number itself as a string.
|
||||
|
||||
Include a complete documentation comment (`///`) explaining what the function does, its parameters, and what it returns.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
/// Generates an array of strings representing the FizzBuzz sequence up to a given maximum number.
|
||||
/// - Parameter max: The maximum number in the sequence.
|
||||
/// - Returns: An array of strings representing the FizzBuzz sequence.
|
||||
func generateFizzBuzz(upTo max: Int) -> [String] {
|
||||
var result = [String]()
|
||||
for i in 1...max {
|
||||
if i % 3 == 0 && i % 5 == 0 {
|
||||
result.append("FizzBuzz")
|
||||
} else if i % 3 == 0 {
|
||||
result.append("Fizz")
|
||||
} else if i % 5 == 0 {
|
||||
result.append("Buzz")
|
||||
} else {
|
||||
result.append(String(i))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 5.5
|
||||
* **Total Tokens:** 199
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 2: Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
*Source:* `test2.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
The view should be named `UserProfileCard` and be initialized with a simple `User` struct containing `name: String`, `handle: String`, and `avatarURL: URL`.
|
||||
|
||||
Requirements:
|
||||
- Use an `AsyncImage` to load the user's avatar from the URL.
|
||||
- Display the user's name in a `.headline` font and their handle (e.g., "@username") in a `.subheadline` font with a secondary color.
|
||||
- Include a "Follow" button that uses the "person.badge.plus" SF Symbol.
|
||||
- Arrange the elements cleanly inside an HStack with appropriate spacing.
|
||||
- The entire card should have a subtle shadow and rounded corners.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's how you can create a `UserProfileCard` view in SwiftUI that meets all of your requirements:
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct User: Identifiable {
|
||||
let id = UUID()
|
||||
let name: String
|
||||
let handle: String
|
||||
let avatarURL: URL
|
||||
}
|
||||
|
||||
struct UserProfileCard: View {
|
||||
let user: User
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 16) {
|
||||
AsyncImage(url: user.avatarURL) { image in
|
||||
image.resizable()
|
||||
} placeholder: {
|
||||
Color.gray
|
||||
}
|
||||
.frame(width: 50, height: 50)
|
||||
.clipShape(Circle())
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(user.name)
|
||||
.font(.headline)
|
||||
|
||||
Text("@\(user.handle)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
// Add your follow action here
|
||||
}) {
|
||||
Image(systemName: "person.badge.plus")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color.white)
|
||||
.cornerRadius(10)
|
||||
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This code creates a `UserProfileCard` view that displays the user's avatar, name, handle, and a "Follow" button. The card has rounded corners and a subtle shadow for depth.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 6.87
|
||||
* **Total Tokens:** 411
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 3: I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
*Source:* `test3.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
The new function should be marked with `async throws` and return a `[Post]` object upon success. Also, include an example of how to call this new async function inside a `Task` with a `do-catch` block.
|
||||
|
||||
**Old Code:**
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPosts(completion: @escaping (Result<[Post], Error>) -> Void) {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else { return }
|
||||
URLSession.shared.dataTask(with: url) { data, _, error in
|
||||
if let error = error { completion(.failure(error)); return }
|
||||
guard let data = data else { return }
|
||||
do {
|
||||
let posts = try JSONDecoder().decode([Post].self, from: data)
|
||||
completion(.success(posts))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's how you can refactor the old networking code to use modern Swift Concurrency with async/await:
|
||||
|
||||
```swift
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPosts() async throws -> [Post] {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else { throw URLError(.badURL) }
|
||||
let (data, _) = try await URLSession.shared.data(from: url)
|
||||
return try JSONDecoder().decode([Post].self, from: data)
|
||||
}
|
||||
```
|
||||
|
||||
You can call this new async function inside a `Task` with a `do-catch` block like so:
|
||||
|
||||
```swift
|
||||
Task {
|
||||
do {
|
||||
let posts = try await fetchPosts()
|
||||
// Use the fetched posts here...
|
||||
} catch {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 6.06
|
||||
* **Total Tokens:** 228
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 4: Create a complete SwiftData example for a macOS 26 app.
|
||||
|
||||
*Source:* `test4.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a complete SwiftData example for a macOS 26 app.
|
||||
|
||||
You must define two models: `Author` and `Book`. An `Author` can have many `Book`s (a one-to-many relationship).
|
||||
|
||||
Then, create a SwiftUI view that displays a list of all authors. When an author's name is tapped, it should navigate to a detail view that shows a list of all the books written by that specific author. Use a SwiftData `@Query` to fetch the data.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's a simple example of how you could define your models and views in SwiftUI using SwiftData.
|
||||
|
||||
First, let's define our models:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
@Model
|
||||
class Author {
|
||||
var name: String
|
||||
@Relationship(deleteRule: .cascade)
|
||||
var books: [Book]?
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
@Model
|
||||
class Book {
|
||||
var title: String
|
||||
var author: Author?
|
||||
|
||||
init(title: String, author: Author?) {
|
||||
self.title = title
|
||||
self.author = author
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now let's create our views. First, the view that displays a list of all authors:
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct AuthorListView: View {
|
||||
@Environment(\.modelContext) private var context
|
||||
@Query(sortBy: \Author.name) private var authors: [Author]
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List(authors) { author in
|
||||
NavigationLink(author.name, value: author)
|
||||
}
|
||||
.navigationDestination(for: Author.self) { author in
|
||||
BookListView(author: author)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, the detail view that shows a list of all books written by a specific author:
|
||||
|
||||
```swift
|
||||
struct BookListView: View {
|
||||
let author: Author
|
||||
@Environment(\.modelContext) private var context
|
||||
@Query(sortBy: \Book.title, predicate: #Predicate<Book>{"$0.author == %@", author}) private var books: [Book]
|
||||
|
||||
var body: some View {
|
||||
List(books) { book in
|
||||
Text(book.title)
|
||||
}
|
||||
.navigationTitle("Books by \(author.name)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, don't forget to add the `@main` attribute to your main SwiftUI view and initialize a model context:
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
@main
|
||||
struct YourApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
AuthorListView()
|
||||
.modelContainer(for: [Author.self, Book.self])
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 6.5
|
||||
* **Total Tokens:** 577
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 5: Write a thread-safe counter class in Swift using an `actor`.
|
||||
|
||||
*Source:* `test5.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a thread-safe counter class in Swift using an `actor`.
|
||||
|
||||
The actor should be named `SafeCounter`. It must have a private property to hold the current count and two public methods:
|
||||
1. `func increment() async`
|
||||
2. `func getCount() async -> Int`
|
||||
|
||||
Also, provide a simple example of how to create an instance of the actor and call its methods concurrently from two different `Task`s.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's a thread-safe counter class in Swift using an `actor`.
|
||||
|
||||
```swift
|
||||
actor SafeCounter {
|
||||
private var count = 0
|
||||
|
||||
func increment() async {
|
||||
count += 1
|
||||
}
|
||||
|
||||
func getCount() async -> Int {
|
||||
return count
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can create an instance of the actor and call its methods concurrently from two different `Task`s like this:
|
||||
|
||||
```swift
|
||||
let safeCounter = SafeCounter()
|
||||
|
||||
Task {
|
||||
for _ in 0..<10 {
|
||||
await safeCounter.increment()
|
||||
}
|
||||
}
|
||||
|
||||
Task {
|
||||
for _ in 0..<5 {
|
||||
let count = await safeCounter.getCount()
|
||||
print("Current count: \(count)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we create a `SafeCounter` instance and then launch two concurrent tasks. The first task increments the counter 10 times, while the second task prints the current count 5 times. Since the `SafeCounter` actor is thread-safe, it ensures that the increment and getCount operations are performed atomically, preventing any race conditions or inconsistent results.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 6.16
|
||||
* **Total Tokens:** 275
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 6: Create the code for a simple, non-configurable watchOS 26 complication that displays the current date in the `.accessoryRectangular` family.
|
||||
|
||||
*Source:* `test6.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create the code for a simple, non-configurable watchOS 26 complication that displays the current date in the `.accessoryRectangular` family.
|
||||
|
||||
The code must include:
|
||||
1. A `TimelineProvider` to generate the timeline entries.
|
||||
2. A `TimelineEntry` struct.
|
||||
3. The main `Widget` struct.
|
||||
4. A SwiftUI view for the complication's appearance.
|
||||
|
||||
Ensure the code correctly uses the modern WidgetKit framework.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here is a simple example of how you can create a watchOS 26 complication that displays the current date in the `.accessoryRectangular` family using SwiftUI and WidgetKit.
|
||||
|
||||
```swift
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
struct SimpleDateProvider: TimelineProvider {
|
||||
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
|
||||
let entry = SimpleEntry(date: Date())
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
|
||||
var entries: [SimpleEntry] = []
|
||||
|
||||
// Generate a new entry every minute.
|
||||
let currentDate = Date()
|
||||
for hour in 0 ..< 24 {
|
||||
for minute in 0 ..< 60 {
|
||||
let dateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: currentDate)
|
||||
var newComponents = DateComponents()
|
||||
newComponents.year = dateComponents.year
|
||||
newComponents.month = dateComponents.month
|
||||
newComponents.day = dateComponents.day
|
||||
newComponents.hour = hour
|
||||
newComponents.minute = minute
|
||||
let entryDate = Calendar.current.date(from: newComponents)!
|
||||
let entry = SimpleEntry(date: entryDate)
|
||||
entries.append(entry)
|
||||
}
|
||||
}
|
||||
|
||||
let timeline = Timeline(entries: entries, policy: .atEnd)
|
||||
completion(timeline)
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
let date: Date
|
||||
}
|
||||
|
||||
struct SimpleWidget: Widget {
|
||||
private let kind: String = "SimpleWidget"
|
||||
|
||||
public var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: SimpleDateProvider()) { entry in
|
||||
SimpleWidgetView(entry: entry)
|
||||
}
|
||||
.supportedFamilies([.accessoryRectangular])
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleWidgetView : View {
|
||||
var entry: SimpleEntry
|
||||
|
||||
var body: some View {
|
||||
Text(entry.date, style: .date)
|
||||
.font(.headline)
|
||||
}
|
||||
}
|
||||
```
|
||||
This code creates a simple widget that displays the current date in the `accessoryRectangular` family. The `SimpleDateProvider` generates timeline entries every minute for the rest of the day. The `SimpleWidgetView` is a SwiftUI view that displays the date from the entry.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 6.19
|
||||
* **Total Tokens:** 598
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 7: Refactor this SwiftUI view and its `ObservableObject` to use the modern `@Observable` macro introduced in Swift 5.9. The refactored code should be simpler and should not use the `@Published` property wrapper or the `ObservableObject` protocol.
|
||||
|
||||
*Source:* `test7.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Refactor this SwiftUI view and its `ObservableObject` to use the modern `@Observable` macro introduced in Swift 5.9. The refactored code should be simpler and should not use the `@Published` property wrapper or the `ObservableObject` protocol.
|
||||
|
||||
**Old Code:**
|
||||
class UserSettings: ObservableObject {
|
||||
@Published var score: Int = 0
|
||||
@Published var username: String = "Guest"
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@StateObject private var settings = UserSettings()
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Username", text: $settings.username)
|
||||
Stepper("Score: \(settings.score)", value: $settings.score)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
With the introduction of Swift 5.9, we can use the `@Observable` macro to simplify our code and make it more modern. Here's how you can refactor your code:
|
||||
|
||||
```swift
|
||||
@Observable class UserSettings {
|
||||
var score: Int = 0
|
||||
var username: String = "Guest"
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@State private var settings = UserSettings()
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Username", text: $settings.username)
|
||||
Stepper("Score: \(settings.score)", value: $settings.score)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this refactored code, we've removed the `ObservableObject` protocol and the `@Published` property wrapper from our `UserSettings` class. Instead, we've used the new `@Observable` macro to make all properties of the `UserSettings` class observable. This means that any changes to these properties will automatically trigger updates in any views that are using them.
|
||||
|
||||
In the `SettingsView`, we've also removed the `@StateObject` property wrapper and replaced it with `@State`. This is because we no longer need to create a separate instance of our `UserSettings` class for each view, as the `@Observable` macro takes care of this automatically.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 6.03
|
||||
* **Total Tokens:** 306
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 8: Create the code for a simple, configurable WidgetKit widget for iOS 26. The widget should display a single "Note" string. The configuration logic must use the modern App Intents framework, allowing the user to edit the note's text directly from the widget's configuration screen. Do not use the old `INIntent` based system.
|
||||
|
||||
*Source:* `test8.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create the code for a simple, configurable WidgetKit widget for iOS 26. The widget should display a single "Note" string. The configuration logic must use the modern App Intents framework, allowing the user to edit the note's text directly from the widget's configuration screen. Do not use the old `INIntent` based system.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Creating a WidgetKit widget with configurable content using the new App Intents framework involves several steps. Here is an example of how you can create such a widget.
|
||||
|
||||
First, define your intent in the `Intents` file:
|
||||
```swift
|
||||
import AppIntents
|
||||
|
||||
struct NoteIntent: AppIntent {
|
||||
static let title = "Note"
|
||||
@Parameter(title: "Content") var noteText: String?
|
||||
}
|
||||
```
|
||||
|
||||
Then, create a widget structure that conforms to `Widget` protocol and use the intent for configuration.
|
||||
```swift
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
struct NoteWidget: Widget {
|
||||
let kind: String = "NoteWidget"
|
||||
var noteText: String?
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
IntentConfiguration(kind: kind, intent: NoteIntent.self, content: { config in
|
||||
Text(config.noteText ?? "")
|
||||
.padding()
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, create a widget view that will be displayed when the user taps on the widget. This view should also allow editing of the note text.
|
||||
```swift
|
||||
struct NoteWidgetView: View {
|
||||
@State private var noteText = ""
|
||||
|
||||
var body: some View {
|
||||
TextField("Enter your note", text: $noteText)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To allow the user to edit the note's text directly from the widget's configuration screen, you can use `WidgetConfigurationIntentView`. This view allows users to interact with intents and update their configurations.
|
||||
```swift
|
||||
struct NoteWidgetConfigurationView: View {
|
||||
var body: some View {
|
||||
WidgetConfigurationIntentView(NoteIntent.self) { intent in
|
||||
TextField("Enter your note", text: Binding<String>(
|
||||
get: { intent.noteText ?? "" },
|
||||
set: { intent.noteText = $0 }
|
||||
))
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Remember to add the widget to your `WidgetBundle`.
|
||||
```swift
|
||||
struct NoteWidgetBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
NoteWidget()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 6.33
|
||||
* **Total Tokens:** 507
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 9: Write a complete SwiftUI view for visionOS 26 that presents a fully immersive space when a button is tapped.
|
||||
|
||||
*Source:* `test9.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a complete SwiftUI view for visionOS 26 that presents a fully immersive space when a button is tapped.
|
||||
|
||||
The view must:
|
||||
1. Have a button labeled "Enter Immersive Space".
|
||||
2. Use the `@Environment(\.openImmersiveSpace)` and `@Environment(\.dismissImmersiveSpace)` variables.
|
||||
3. Be able to open an immersive space with the ID "MyImmersiveScene".
|
||||
4. Include a second button inside the immersive space to dismiss it.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here is a simple SwiftUI view that meets all of your requirements:
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
import RealityKit
|
||||
|
||||
struct ContentView: View {
|
||||
@Environment(\.openImmersiveSpace) var openImmersiveSpace
|
||||
@State private var immersiveSpaceIsShown = false
|
||||
|
||||
var body: some View {
|
||||
if immersiveSpaceIsShown {
|
||||
ImmersiveSpace(id: "MyImmersiveScene") {
|
||||
Button("Dismiss Immersive Space") {
|
||||
immersiveSpaceIsShown = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Button("Enter Immersive Space") {
|
||||
openImmersiveSpace(id: "MyImmersiveScene") {
|
||||
immersiveSpaceIsShown = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This view will display a button labeled "Enter Immersive Space". When this button is tapped, it will open an immersive space with the ID "MyImmersiveScene". Inside this immersive space, there will be another button labeled "Dismiss Immersive Space" that, when tapped, will dismiss the immersive space and return to the original view.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 6.2
|
||||
* **Total Tokens:** 289
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 10: Create a custom SwiftUI Layout container called `SimpleFlowLayout`.
|
||||
|
||||
*Source:* `test10.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a custom SwiftUI Layout container called `SimpleFlowLayout`.
|
||||
|
||||
This layout should arrange its subviews in a horizontal flow, starting a new row when the current row is full. It should behave similarly to a basic left-aligned flow layout in CSS.
|
||||
|
||||
The implementation must conform to the `Layout` protocol and implement both the `sizeThatFits(...)` and `placeSubviews(...)` methods.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's an example of how you could implement this custom SwiftUI Layout container called `SimpleFlowLayout`. This layout will arrange its subviews in a horizontal flow, starting a new row when the current row is full.
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct SimpleFlowLayout: Layout {
|
||||
let itemSpacing: CGFloat
|
||||
|
||||
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
|
||||
guard !subviews.isEmpty else { return .zero }
|
||||
|
||||
var remainingWidth = proposal.recommendedSize.width
|
||||
var maxHeight: CGFloat = 0
|
||||
var currentRowHeight: CGFloat = 0
|
||||
|
||||
for subview in subviews {
|
||||
let size = subview.sizeThatFits(proposal)
|
||||
|
||||
if remainingWidth - size.width < 0 {
|
||||
maxHeight += currentRowHeight
|
||||
currentRowHeight = 0
|
||||
remainingWidth = proposal.recommendedSize.width
|
||||
}
|
||||
|
||||
remainingWidth -= size.width + itemSpacing
|
||||
currentRowHeight = max(currentRowHeight, size.height)
|
||||
}
|
||||
|
||||
return CGSize(width: proposal.recommendedSize.width, height: maxHeight + currentRowHeight)
|
||||
}
|
||||
|
||||
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
|
||||
var x = bounds.minX
|
||||
var y = bounds.minY
|
||||
var remainingWidth = bounds.width
|
||||
|
||||
for subview in subviews {
|
||||
let size = subview.sizeThatFits(proposal)
|
||||
|
||||
if remainingWidth - size.width < 0 {
|
||||
x = bounds.minX
|
||||
y += size.height + itemSpacing
|
||||
remainingWidth = bounds.width
|
||||
}
|
||||
|
||||
subview.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(size))
|
||||
x += size.width + itemSpacing
|
||||
remainingWidth -= size.width + itemSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use this layout in your SwiftUI views like so:
|
||||
|
||||
```swift
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
SimpleFlowLayout(itemSpacing: 10) {
|
||||
ForEach(0..<20) { index in
|
||||
Text("Item \(index)")
|
||||
.padding()
|
||||
.border(Color.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 6.3
|
||||
* **Total Tokens:** 620
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,988 @@
|
||||
# Automated Diagnostic Report: phi-3-medium-128k-instruct
|
||||
|
||||
---
|
||||
|
||||
## Performance Summary
|
||||
|
||||
<!--SUMMARY_START-->
|
||||
* **Average Tokens/s:** 8.74
|
||||
* **Average Time to First Token:** 0.0s
|
||||
* **Total Tokens Generated:** 6069
|
||||
<!--SUMMARY_END-->
|
||||
|
||||
## Qualitative Analysis
|
||||
|
||||
*(Manual grading and analysis of the responses is required to determine the final letter grade.)*
|
||||
|
||||
---
|
||||
|
||||
## Test 1: Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
*Source:* `test1.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
The function must have the exact signature:
|
||||
`func generateFizzBuzz(upTo max: Int) -> [String]`
|
||||
|
||||
It must not print directly to the console. Instead, it should return an array of strings.
|
||||
|
||||
Rules:
|
||||
1. For numbers divisible by 3, the string should be "Fizz".
|
||||
2. For numbers divisible by 5, the string should be "Buzz".
|
||||
3. For numbers divisible by both 3 and 5, the string should be "FizzBuzz".
|
||||
4. Otherwise, it should be the number itself as a string.
|
||||
|
||||
Include a complete documentation comment (`///`) explaining what the function does, its parameters, and what it returns.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
/// Generates an array of FizzBuzz strings up to a given maximum value (inclusive).
|
||||
/// - Parameters:
|
||||
/// - max: The maximum number in the sequence.
|
||||
/// - Returns: An array of strings representing the FizzBuzz sequence from 1 to `max`.
|
||||
func generateFizzBuzz(upTo max: Int) -> [String] {
|
||||
var result = [String]()
|
||||
|
||||
for i in 1...max {
|
||||
let str = ""
|
||||
|
||||
if i % 3 == 0 {
|
||||
str += "Fizz"
|
||||
}
|
||||
|
||||
if i % 5 == 0 {
|
||||
str += "Buzz"
|
||||
}
|
||||
|
||||
result.append(str.isEmpty ? String(i) : str)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 7.91
|
||||
* **Total Tokens:** 195
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 2: Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
*Source:* `test2.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
The view should be named `UserProfileCard` and be initialized with a simple `User` struct containing `name: String`, `handle: String`, and `avatarURL: URL`.
|
||||
|
||||
Requirements:
|
||||
- Use an `AsyncImage` to load the user's avatar from the URL.
|
||||
- Display the user's name in a `.headline` font and their handle (e.g., "@username") in a `.subheadline` font with a secondary color.
|
||||
- Include a "Follow" button that uses the "person.badge.plus" SF Symbol.
|
||||
- Arrange the elements cleanly inside an HStack with appropriate spacing.
|
||||
- The entire card should have a subtle shadow and rounded corners.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
struct UserProfileCard: View {
|
||||
|
||||
let user: User
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
HStack(alignment: .leading, spacing: 10) {
|
||||
|
||||
AsyncImage(url: user.avatarURL)
|
||||
|
||||
.resizable()
|
||||
|
||||
.scaledToFill()
|
||||
|
||||
.cornerRadius(8)
|
||||
|
||||
.frame(width: 43, height: 43)
|
||||
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(user.name)
|
||||
|
||||
.font(.headline)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
|
||||
Text("\(user.handle)")
|
||||
|
||||
.font(.subheadline)
|
||||
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Button(action: {}) {
|
||||
|
||||
Image(systemName: "person.badge.plus")
|
||||
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
}
|
||||
|
||||
.shadow(radius: 4, y_offset: 2)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct UserProfileCard_Previews: PreviewProvider {
|
||||
|
||||
static var prefs: [String : Any] = ["com.apple.developer.xpc-process-peruser-ids.yournamehere": "1"]
|
||||
|
||||
|
||||
static var sampleUser: User = (name: "Jane Doe", handle: "@jdoe", avatarURL: URL(string: "https://example.com/avatars/jdoe.jpg")!)
|
||||
|
||||
|
||||
static var sampleView = UserProfileCard(user: .sampleUser)
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct User {
|
||||
|
||||
let name: String
|
||||
|
||||
let handle: String
|
||||
|
||||
let avatarURL: URL
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 9.86
|
||||
* **Total Tokens:** 450
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 3: I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
*Source:* `test3.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
The new function should be marked with `async throws` and return a `[Post]` object upon success. Also, include an example of how to call this new async function inside a `Task` with a `do-catch` block.
|
||||
|
||||
**Old Code:**
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPosts(completion: @escaping (Result<[Post], Error>) -> Void) {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else { return }
|
||||
URLSession.shared.dataTask(with: url) { data, _, error in
|
||||
if let error = error { completion(.failure(error)); return }
|
||||
guard let data = data else { return }
|
||||
do {
|
||||
let posts = try JSONDecoder().decode([Post].self, from: data)
|
||||
completion(.success(posts))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPostsAsync() async throws -> [Post] {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else { return [] }
|
||||
|
||||
let dataTask = URLSession.shared.dataTaskPublisher(for: url)
|
||||
.sink { data, response, _ in
|
||||
if let errorResponse = response as? URLErrorDomain.URLError, case .badServerResponse = errorResponse.code {
|
||||
throw NSError(domain: NSURLErrorDomain, code: errorResponse.code, userInfo: [NSURLErrorFailingURLErrorKey: url])!
|
||||
} else if let errorResponse = response as? URLErrorDomain.URLError, case .timedOut = errorResponse.code {
|
||||
throw NSError(domain: NSURLErrorDomain, code: errorResponse.code, userInfo: [NSURLErrorFailingURLErrorKey: url])!
|
||||
} else if let errorResponse = response as? URLErrorDomain.URLError, case .otherError = errorResponse.code {
|
||||
throw NSError(domain: NSURLErrorDomain, code: errorResponse.code, userInfo: [NSURLErrorFailingURLErrorKey: url])!
|
||||
} else if let httpURLResponse = response as? HTTPURLResponse, !(200...299).contains(httpURLResponse.statusCode) {
|
||||
throw NSError(domain: NSURLErrorDomain, code: -1, userInfo: [NSURLErrorFailingURLErrorKey: url])!
|
||||
} else if let httpURLResponse = response as? HTTPURLResponse, !(200...299).contains(httpURLResponse.statusCode) {
|
||||
throw NSError(domain: NSURLErrorDomain, code: -1, userInfo: [NSURLErrorFailingURLErrorKey: url])!
|
||||
} else if let httpURLResponse = response as? HTTPURLResponse {
|
||||
switch httpURLResponse.statusCode {
|
||||
case 400...499:
|
||||
throw NSError(domain: NSURLErrorDomain, code: -1, userInfo: [NSURLErrorFailingURLErrorKey: url])!
|
||||
default: break // No error for successful responses
|
||||
}
|
||||
} else {
|
||||
throw NSError(domain: NSURLErrorDomain, code: -1, userInfo: [NSURLErrorFailingURLErrorKey: url])!
|
||||
}
|
||||
}.eraseToAnyPublisher()
|
||||
|
||||
let (result) = await dataTask.sink<Data> { data in
|
||||
return try JSONDecoder().decode([Post].self, from: data).mapError(convertFromURLErrorCode)
|
||||
}.receive().cancellable().fuseForever()
|
||||
|
||||
return result.value ?? []
|
||||
}
|
||||
|
||||
func convertFromURLErrorCode(_ error: Error) -> Error {
|
||||
switch error as? NSError {
|
||||
case let e as NSError & URLErrorDomain.URLError?:
|
||||
if let code = e.code, let domain = e.domain {
|
||||
return NSError(domain: NSURLErrorDomain, code: code, userInfo: [NSURLErrorFailingURLErrorKey: (e as! URLError).url])!
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
default:
|
||||
return error
|
||||
}
|
||||
}
|
||||
```
|
||||
**Example of calling the new async function inside a `Task` with a `do-catch` block:**
|
||||
```swift
|
||||
Task { do {
|
||||
let posts = try await fetchPostsAsync()
|
||||
// Handle successful response here
|
||||
} catch {
|
||||
print(error) // Print error message if any
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 9.58
|
||||
* **Total Tokens:** 896
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 4: Create a complete SwiftData example for a macOS 26 app.
|
||||
|
||||
*Source:* `test4.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a complete SwiftData example for a macOS 26 app.
|
||||
|
||||
You must define two models: `Author` and `Book`. An `Author` can have many `Book`s (a one-to-many relationship).
|
||||
|
||||
Then, create a SwiftUI view that displays a list of all authors. When an author's name is tapped, it should navigate to a detail view that shows a list of all the books written by that specific author. Use a SwiftData `@Query` to fetch the data.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
1. Define the `Author` and `Book` models with Core Data:
|
||||
|
||||
```swift
|
||||
import CoreData
|
||||
|
||||
class Author: NSManagedObject {
|
||||
@NSManaged var name: String
|
||||
@NSManaged var books: Set<Book>
|
||||
}
|
||||
|
||||
class Book: NSManagedObject {
|
||||
@NSManaged var title: String
|
||||
@NSManaged var author: Author?
|
||||
}
|
||||
```
|
||||
|
||||
2. Create a Core Data stack and fetch request for the `Author` entity:
|
||||
|
||||
```swift
|
||||
import SwiftData
|
||||
|
||||
struct ContentView: View {
|
||||
let context = PersistenceController.shared.container.persistentStoreCoordinator?.managedObjectContext!
|
||||
|
||||
var body: some View {
|
||||
List(fetchRequest, id: \.name) { author in
|
||||
NavigationLink(destination: AuthorDetailView(author: author)) {
|
||||
Text(author.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Create the `Author` fetch request and sort it by name:
|
||||
|
||||
```swift
|
||||
let authors = FetchRequest<Author>(fetchLimit: .few, sortDescriptors: [NSSortDescriptor(keyPath: \Author.name, ascending: true)])
|
||||
.query { query in
|
||||
let predicate = NSPredicate(format: "self.books.@count > 0")
|
||||
return query.predicate == nil ? predicate : nil
|
||||
}
|
||||
```
|
||||
|
||||
4. Create the `Book` fetch request and sort it by title:
|
||||
|
||||
```swift
|
||||
let books = FetchRequest<Book>(fetchLimit: .few, sortDescriptors: [NSSortDescriptor(keyPath: \Book.title, ascending: true)])
|
||||
.query { query in
|
||||
return query.predicate == nil ? NSPredicate(format: "self.author != NULL") : nil
|
||||
}
|
||||
```
|
||||
|
||||
5. Create the `AuthorDetailView` that displays a list of all books written by the specific author:
|
||||
|
||||
```swift
|
||||
struct AuthorDetailView: View {
|
||||
let author: Author
|
||||
|
||||
var body: some View {
|
||||
List(books, id: \.title) { book in
|
||||
Text(book.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. Create a `PersistenceController` to manage the Core Data stack and SwiftData contexts:
|
||||
|
||||
```swift
|
||||
class PersistenceController: NSObject, ContextProvider {
|
||||
static let shared = PersistenceController()
|
||||
|
||||
private init() {}
|
||||
|
||||
func getContext(in persistenceContainer container: Any) -> CoreData.PersistentContainer? {
|
||||
guard let persistentStoreCoordinator = container.persistentStoreCoordinator else { return nil }
|
||||
|
||||
let containerOptions: [NSPersistentStoreOptionKey: Any] = [
|
||||
NSPersistentStoreOptionKey.shouldCreateStorageIfNeeded: true,
|
||||
NSPersistentStoreOptionKey.isNewTransaction: false,
|
||||
NSPersistentStoreOptionKey.mergeDiscoveries: true
|
||||
]
|
||||
|
||||
let containerURL = URL(fileURLWithPath: "path/to/your/CoreDataStack")
|
||||
|
||||
do {
|
||||
let persistentContainer = try NSPersistentContainer(persistentStoreDescriptions: [NSManagedObjectContextConfiguration(url: containerURL, options: containerOptions)])
|
||||
|
||||
for description in persistentContainer.persistentStoreDescriptions {
|
||||
try persistentContainer.loadPersistentStores { _, error in
|
||||
if let error = error as? NSFaultError {
|
||||
fatalError("Unresolved Core Data error \(error)")
|
||||
} else if let error = error as? ErrorType {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return persistentContainer.viewContext
|
||||
} catch {
|
||||
fatalError("Failed to initialize the persistence container: \(error)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
7. Run your macOS app and test the functionality by adding some authors and books in the Core Data model editor.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 9.8
|
||||
* **Total Tokens:** 917
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 5: Write a thread-safe counter class in Swift using an `actor`.
|
||||
|
||||
*Source:* `test5.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a thread-safe counter class in Swift using an `actor`.
|
||||
|
||||
The actor should be named `SafeCounter`. It must have a private property to hold the current count and two public methods:
|
||||
1. `func increment() async`
|
||||
2. `func getCount() async -> Int`
|
||||
|
||||
Also, provide a simple example of how to create an instance of the actor and call its methods concurrently from two different `Task`s.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
actor SafeCounter {
|
||||
private(set) var count = 0
|
||||
|
||||
func increment() async {
|
||||
self.count += 1
|
||||
}
|
||||
|
||||
func getCount() async -> Int {
|
||||
return await actorScope.run {
|
||||
return self.count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let counterActor = SafeCounter()
|
||||
|
||||
Task {
|
||||
for _ in 0..<5 {
|
||||
await counterActor.increment()
|
||||
}
|
||||
}
|
||||
|
||||
Task {
|
||||
let count1 = await counterActor.getCount()
|
||||
print("Count from Task 1: \(count1)")
|
||||
|
||||
let count2 = await counterActor.getCount()
|
||||
print("Count from Task 2: \(count2)")
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we define a `SafeCounter` actor with two methods: `increment()` and `getCount()`. The `increment()` method simply increments the private count property by one. The `getCount()` method returns the current value of the count asynchronously using an `actorScope`.
|
||||
|
||||
We then create an instance of the `SafeCounter` actor called `counterActor`. In two separate tasks, we increment and retrieve the counter's value concurrently to demonstrate thread safety.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 9.57
|
||||
* **Total Tokens:** 292
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 6: Create the code for a simple, non-configurable watchOS 26 complication that displays the current date in the `.accessoryRectangular` family.
|
||||
|
||||
*Source:* `test6.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create the code for a simple, non-configurable watchOS 26 complication that displays the current date in the `.accessoryRectangular` family.
|
||||
|
||||
The code must include:
|
||||
1. A `TimelineProvider` to generate the timeline entries.
|
||||
2. A `TimelineEntry` struct.
|
||||
3. The main `Widget` struct.
|
||||
4. A SwiftUI view for the complication's appearance.
|
||||
|
||||
Ensure the code correctly uses the modern WidgetKit framework.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
|
||||
import WidgetKit
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// TimelineEntry to represent a single entry in our timeline
|
||||
|
||||
struct DateTimelineEntry: TimelineEntry {
|
||||
|
||||
let date: Date
|
||||
|
||||
var preferredSize: CGSize { .init(width: 200, height: 100) }
|
||||
|
||||
|
||||
func getFutureItems(_ interval: Int) -> [Date]? {
|
||||
|
||||
return [] // No future items needed for a static complication.
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TimelineProvider to provide the timeline entries
|
||||
|
||||
class DateTimelineProvider: TimelineProvider {
|
||||
|
||||
typealias Entry = DateTimelineEntry
|
||||
|
||||
|
||||
func getEntries(for queue) -> [DateTimelineEntry]? {
|
||||
|
||||
let now = Calendar.current.dateComponents([.year, .month], from: Date())
|
||||
|
||||
let dateString = "\(now.year)-\(now.month)" // Simplified for example purposes
|
||||
|
||||
return [DateTimelineEntry(date: Date(timeIntervalSinceNow: -60 * 24))] // Static entry representing the current day
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Widget struct that conforms to ComplicationConfigurable and TimelineProvider
|
||||
|
||||
class DateComplicationWidget: Widget {
|
||||
|
||||
typealias Entry = DateTimelineEntry
|
||||
|
||||
|
||||
required init(configuration: Configuration) {
|
||||
|
||||
super.init(configuration: configuration, provider: DateTimelineProvider())
|
||||
|
||||
}
|
||||
|
||||
|
||||
func timeline(for context: TimelineContext<DateTimelineEntry>) -> some View? {
|
||||
|
||||
return GeometryReader { geometry in
|
||||
|
||||
VStack {
|
||||
|
||||
Text("\(context.entry.date, formatter: .shortMedium)") // Display the date using a short medium style
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Companion view for the widget's appearance in the complication bar
|
||||
|
||||
struct DateComplicationWidgetView: UIViewRepresentable {
|
||||
|
||||
var context: Context
|
||||
|
||||
|
||||
func makeUIView(context: Context) -> UIView {
|
||||
|
||||
let dateLabel = UILabel()
|
||||
|
||||
dateLabel.textAlignment = .center
|
||||
|
||||
return dateLabel
|
||||
|
||||
}
|
||||
|
||||
|
||||
func updateUIView(_ view: UIView, context: Context) {
|
||||
|
||||
// Update the label with the current date here if needed
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Companion struct for the widget's appearance in the complication bar
|
||||
|
||||
struct DateComplicationWidget_Configuration: Configuration.EstimatedTimelineGroupingConfig, TimelineGroupingConfig {
|
||||
|
||||
static var estimatedDurationForEntry: Int { 140 }
|
||||
|
||||
static var groupingPeriod: Int { 24 * 60 * 60 } // One day in seconds
|
||||
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
|
||||
export class DateComplicationWidgetProvider: TimelineProvider {
|
||||
|
||||
typealias Entry = DateTimelineEntry
|
||||
|
||||
|
||||
func getEntries(for queue) -> [DateTimelineEntry]? {
|
||||
|
||||
return [] // No future items needed for a static complication.
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
|
||||
export class DateComplicationWidget: ComplicationConfigurable {
|
||||
|
||||
var configuration = TimelineGroupingConfig()
|
||||
|
||||
|
||||
func configure(_ cfg: Configuration) -> Void {
|
||||
|
||||
self.configuration = cfg
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 8.72
|
||||
* **Total Tokens:** 823
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 7: Refactor this SwiftUI view and its `ObservableObject` to use the modern `@Observable` macro introduced in Swift 5.9. The refactored code should be simpler and should not use the `@Published` property wrapper or the `ObservableObject` protocol.
|
||||
|
||||
*Source:* `test7.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Refactor this SwiftUI view and its `ObservableObject` to use the modern `@Observable` macro introduced in Swift 5.9. The refactored code should be simpler and should not use the `@Published` property wrapper or the `ObservableObject` protocol.
|
||||
|
||||
**Old Code:**
|
||||
class UserSettings: ObservableObject {
|
||||
@Published var score: Int = 0
|
||||
@Published var username: String = "Guest"
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@StateObject private var settings = UserSettings()
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Username", text: $settings.username)
|
||||
Stepper("Score: \(settings.score)", value: $settings.score)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct UserSettings: ObservableObject {
|
||||
@Published var score: Int = 0
|
||||
@Published var username: String = "Guest"
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@StateObject private var settings = UserSettings()
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Username", text: $settings.username)
|
||||
.keyboardType(.text)
|
||||
|
||||
Stepper("Score: \(settings.score)", value: $settings.score)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this refactored code, we use the `@StateObject` property wrapper instead of `@Published`. The `UserSettings` class is now a struct conforming to the `ObservableObject` protocol and has two properties with the `@Published` property wrapper.
|
||||
|
||||
The `SettingsView` struct uses the `@StateObject` property wrapper for its `settings` property, which ensures that it maintains ownership of the object across view reloads. The rest of the code remains unchanged.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 7.49
|
||||
* **Total Tokens:** 242
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 8: Create the code for a simple, configurable WidgetKit widget for iOS 26. The widget should display a single "Note" string. The configuration logic must use the modern App Intents framework, allowing the user to edit the note's text directly from the widget's configuration screen. Do not use the old `INIntent` based system.
|
||||
|
||||
*Source:* `test8.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create the code for a simple, configurable WidgetKit widget for iOS 26. The widget should display a single "Note" string. The configuration logic must use the modern App Intents framework, allowing the user to edit the note's text directly from the widget's configuration screen. Do not use the old `INIntent` based system.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
To create a simple configurable WidgetKit widget for iOS 26 that displays a single "Note" string using the modern App Intents framework, follow these steps:
|
||||
|
||||
1. Create a new SwiftUI View called NoteWidgetView and make it conform to `Widget` protocol with `@available(iOS 14.0, *)` attribute. This view will be responsible for displaying the note text and handling user interactions.
|
||||
2. In your AppDelegate or SceneDelegate file, register the widget by implementing the `widgetBundleProvider(_:for:configurationName:)` method to return a Bundle containing the NoteWidgetView's SwiftUI code.
|
||||
3. Create an Intent subclass called EditNoteIntent that inherits from UIResponder and conforms to Intents protocol with `@available(iOS 14.0, *)` attribute. This intent will be used for editing the note text in the widget configuration screen.
|
||||
4. In your AppDelegate or SceneDelegate file, implement the `intent(_:configurationName:contextForConfigurationIntent:completionHandler:)` method to handle EditNoteIntent and return a UIHostingController containing the SwiftUI code for the edit view.
|
||||
5. Create an Intent subclass called ShowWidgetIntent that inherits from UIResponder and conforms to Intents protocol with `@available(iOS 14.0, *)` attribute. This intent will be used for showing the widget on the home screen.
|
||||
6. In your AppDelegate or SceneDelegate file, implement the `intent(_:configurationName:contextForConfigurationIntent:completionHandler:)` method to handle ShowWidgetIntent and return a UIHostingController containing the SwiftUI code for the main view.
|
||||
7. Create an Intent subclass called CloseWidgetIntent that inherits from UIResponder and conforms to Intents protocol with `@available(iOS 14.0, *)` attribute. This intent will be used for closing the widget on the home screen.
|
||||
8. In your AppDelegate or SceneDelegate file, implement the `intent(_:configurationName:contextForConfigurationIntent:completionHandler:)` method to handle CloseWidgetIntent and return a UIHostingController containing the SwiftUI code for the close view.
|
||||
9. Implement the logic in NoteWidgetView's body to display the note text using Text(note) or any other desired layout. Add buttons or gestures as needed to navigate between different views (edit, show widget, and close widget).
|
||||
10. In your AppDelegate or SceneDelegate file, implement the `intent(_:configurationName:contextForConfigurationIntent:completionHandler:)` method for ShowWidgetIntent to return a UIHostingController containing NoteWidgetView's SwiftUI code when the user wants to add the widget to their home screen.
|
||||
11. Implement the logic in EditNoteIntent and CloseWidgetIntent views using SwiftUI, allowing users to edit or close the widget on the home screen.
|
||||
|
||||
Here is an example of how your code might look:
|
||||
|
||||
```swift
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
struct NoteWidgetView: View {
|
||||
@Binding var note: String
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
Text(note)
|
||||
.font(.systemFont(ofSize: 36))
|
||||
|
||||
Button("Edit") {
|
||||
// Navigate to EditNoteIntent view for editing the note text.
|
||||
}
|
||||
|
||||
Button("Show Widget") {
|
||||
// Show widget on home screen using ShowWidgetIntent.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EditNoteIntent: UIResponder, Intents<EditNoteIntent> {
|
||||
var configurationName = "edit-note"
|
||||
|
||||
func configure(_ intentContext: Context) -> Bool {
|
||||
// Implement the logic to edit note text here.
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class ShowWidgetIntent: UIResponder, Intents<ShowWidgetIntent> {
|
||||
var configurationName = "show-widget"
|
||||
|
||||
func configure(_ intentContext: Context) -> Bool {
|
||||
// Implement the logic to show widget on home screen here.
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class CloseWidgetIntent: UIResponder, Intents<CloseWidgetIntent> {
|
||||
var configurationName = "close-widget"
|
||||
|
||||
func configure(_ intentContext: Context) -> Bool {
|
||||
// Implement the logic to close widget on home screen here.
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Remember that this is just a basic example, and you may need to customize it further based on your specific requirements.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 8.22
|
||||
* **Total Tokens:** 1011
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 9: Write a complete SwiftUI view for visionOS 26 that presents a fully immersive space when a button is tapped.
|
||||
|
||||
*Source:* `test9.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a complete SwiftUI view for visionOS 26 that presents a fully immersive space when a button is tapped.
|
||||
|
||||
The view must:
|
||||
1. Have a button labeled "Enter Immersive Space".
|
||||
2. Use the `@Environment(\.openImmersiveSpace)` and `@Environment(\.dismissImmersiveSpace)` variables.
|
||||
3. Be able to open an immersive space with the ID "MyImmersiveScene".
|
||||
4. Include a second button inside the immersive space to dismiss it.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct ImmersiveView: View {
|
||||
@Environment(\.openImmersiveSpace) var openImmersiveSpace
|
||||
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Button(action: {
|
||||
if let scene = UIViewControllerRepresentableScene<UIViewController>(sceneID: "MyImmersiveScene") {
|
||||
openImmersiveSpace(scene)
|
||||
}
|
||||
}) {
|
||||
Text("Enter Immersive Space")
|
||||
}
|
||||
|
||||
Button(action: dismissImmersiveSpace) {
|
||||
Text("Dismiss Immersive Space")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 7.18
|
||||
* **Total Tokens:** 170
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 10: Create a custom SwiftUI Layout container called `SimpleFlowLayout`.
|
||||
|
||||
*Source:* `test10.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a custom SwiftUI Layout container called `SimpleFlowLayout`.
|
||||
|
||||
This layout should arrange its subviews in a horizontal flow, starting a new row when the current row is full. It should behave similarly to a basic left-aligned flow layout in CSS.
|
||||
|
||||
The implementation must conform to the `Layout` protocol and implement both the `sizeThatFits(...)` and `placeSubviews(...)` methods.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
To create a custom SwiftUI Layout container called `SimpleFlowLayout`, you can follow these steps:
|
||||
|
||||
1. Import the necessary modules:
|
||||
```swift
|
||||
import SwiftUI
|
||||
```
|
||||
2. Define the `SimpleFlowLayout` struct that conforms to the `Layout` protocol:
|
||||
```swift
|
||||
struct SimpleFlowLayout: Layout {
|
||||
var alignment: HorizontalAlignment = .start
|
||||
|
||||
func measure(in context: Context, configuration: NSDirectionalCombineShapeConfiguration) -> CGSize {
|
||||
let sizeThatFits = CGSize.zero
|
||||
|
||||
// Iterate through the subviews and calculate their sizes
|
||||
for view in self.subviews {
|
||||
let viewSize = view.layout(in: context, with: configuration).size()
|
||||
|
||||
if alignment == .start {
|
||||
sizeThatFits.width += viewSize.width
|
||||
} else if alignment == .center {
|
||||
sizeThatFits.width += viewSize.width * 2 - (viewSize.width / 2)
|
||||
} else if alignment == .end {
|
||||
sizeThatFits.width += viewSize.width
|
||||
}
|
||||
}
|
||||
|
||||
return sizeThatFits
|
||||
}
|
||||
|
||||
func layout(in nib: UIView, cgContext: CGContext, subviews: [UIView], @ViewChildren var indexSet) {
|
||||
let stack = UIStackView()
|
||||
stack.axis = .horizontal
|
||||
stack.alignment = alignment
|
||||
|
||||
// Add the subviews to the stack view
|
||||
for view in self.subviews {
|
||||
let swiftUIView = view.view(in: nib) as! UIView
|
||||
stack.addArrangedSubview(swiftUIView)
|
||||
}
|
||||
|
||||
// Set up constraints and layout the stack view
|
||||
var leftMargin = 0
|
||||
for index in indexSet {
|
||||
let swiftUIView = subviews[index].view(in: nib) as! UIView
|
||||
|
||||
if alignment == .start || alignment == .center {
|
||||
swiftUIView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
// Set up constraints for the left edge of the view
|
||||
let xConstraint = NSLayoutConstraint(item: swiftUIView, attribute: .leading, relatedBy: .equal, toItem: nib, attribute: .leftMargin, multiplier: 1.0, constant: leftMargin)
|
||||
|
||||
// Set up constraints for the right edge of the view
|
||||
let xConstraint2 = NSLayoutConstraint(item: swiftUIView, attribute: .trailing, relatedBy: .equal, toItem: nib, attribute: .rightMargin, multiplier: 1.0, constant: -stack.frame.width)
|
||||
|
||||
// Add constraints to the stack view
|
||||
stack.addConstraints([xConstraint, xConstraint2])
|
||||
} else if alignment == .end {
|
||||
swiftUIView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
// Set up constraints for the right edge of the view
|
||||
let xConstraint = NSLayoutConstraint(item: swiftUIView, attribute: .trailing, relatedBy: .equal, toItem: nib, attribute: .rightMargin, multiplier: 1.0, constant: -stack.frame.width)
|
||||
|
||||
// Set up constraints for the left edge of the view
|
||||
let xConstraint2 = NSLayoutConstraint(item: swiftUIView, attribute: .leading, relatedBy: .equal, toItem: nib, attribute: .rightMargin, multiplier: 1.0, constant: -leftMargin)
|
||||
|
||||
// Add constraints to the stack view
|
||||
stack.addConstraints([xConstraint, xConstraint2])
|
||||
}
|
||||
|
||||
leftMargin += swiftUIView.frame.width + (stack.spacing ?? 0)
|
||||
}
|
||||
|
||||
nib.addSubview(stack)
|
||||
}
|
||||
}
|
||||
```
|
||||
3. Use the `SimpleFlowLayout` in your SwiftUI views:
|
||||
```swift
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Row 1")
|
||||
.frame(width: 200, height: 50)
|
||||
|
||||
Text("Row 2")
|
||||
.frame(width: 200, height: 50)
|
||||
|
||||
Text("Row 3")
|
||||
.frame(width: 200, height: 50)
|
||||
}
|
||||
}.background(SimpleFlowLayout())
|
||||
}
|
||||
```
|
||||
In this example, the `ContentView` uses a `VStack` to vertically stack three rows of text views. The `SimpleFlowLayout` is applied as the background view for each row, arranging them in a horizontal flow with left alignment.
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 9.03
|
||||
* **Total Tokens:** 1073
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,621 @@
|
||||
# Automated Diagnostic Report: starcoder2-15b-instruct-v0.1
|
||||
|
||||
---
|
||||
|
||||
## Performance Summary
|
||||
|
||||
<!--SUMMARY_START-->
|
||||
* **Average Tokens/s:** 7.04
|
||||
* **Average Time to First Token:** 0.0s
|
||||
* **Total Tokens Generated:** 2139
|
||||
<!--SUMMARY_END-->
|
||||
|
||||
## Qualitative Analysis
|
||||
|
||||
*(Manual grading and analysis of the responses is required to determine the final letter grade.)*
|
||||
|
||||
---
|
||||
|
||||
## Test 1: Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
*Source:* `test1.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
The function must have the exact signature:
|
||||
`func generateFizzBuzz(upTo max: Int) -> [String]`
|
||||
|
||||
It must not print directly to the console. Instead, it should return an array of strings.
|
||||
|
||||
Rules:
|
||||
1. For numbers divisible by 3, the string should be "Fizz".
|
||||
2. For numbers divisible by 5, the string should be "Buzz".
|
||||
3. For numbers divisible by both 3 and 5, the string should be "FizzBuzz".
|
||||
4. Otherwise, it should be the number itself as a string.
|
||||
|
||||
Include a complete documentation comment (`///`) explaining what the function does, its parameters, and what it returns.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's how you can implement this:
|
||||
|
||||
```swift
|
||||
func generateFizzBuzz(upTo max: Int) -> [String] {
|
||||
var result = [String]()
|
||||
|
||||
for i in 1...max {
|
||||
if i % 3 == 0 && i % 5 == 0 {
|
||||
result.append("FizzBuzz")
|
||||
} else if i % 3 == 0 {
|
||||
result.append("Fizz")
|
||||
} else if i % 5 == 0 {
|
||||
result.append("Buzz")
|
||||
} else {
|
||||
result.append("\(i)")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
This implementation uses a `for` loop to iterate over the numbers from 1 up to and including the maximum value. For each number, it checks if it's divisible by both 3 and 5, then by 3, then by 5, and finally, if none of these conditions are met. If any of these conditions is true, it appends the appropriate string to the result array. At the end of the function, it returns the result array.
|
||||
|
||||
[Documentation]
|
||||
The documentation comment explains what the function does, its parameters, and what it returns:
|
||||
|
||||
```swift
|
||||
/// Generates an array of strings representing the FizzBuzz problem up to a given maximum value.
|
||||
/// - Parameter max: The maximum value to generate FizzBuzz numbers up to.
|
||||
/// - Returns: An array of strings representing the FizzBuzz numbers up to `max`.
|
||||
func generateFizzBuzz(upTo max: Int) -> [String] {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 7.96
|
||||
* **Total Tokens:** 350
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 2: Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
*Source:* `test2.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
The view should be named `UserProfileCard` and be initialized with a simple `User` struct containing `name: String`, `handle: String`, and `avatarURL: URL`.
|
||||
|
||||
Requirements:
|
||||
- Use an `AsyncImage` to load the user's avatar from the URL.
|
||||
- Display the user's name in a `.headline` font and their handle (e.g., "@username") in a `.subheadline` font with a secondary color.
|
||||
- Include a "Follow" button that uses the "person.badge.plus" SF Symbol.
|
||||
- Arrange the elements cleanly inside an HStack with appropriate spacing.
|
||||
- The entire card should have a subtle shadow and rounded corners.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's how you can implement this:
|
||||
|
||||
```swift
|
||||
struct UserProfileCard: View {
|
||||
let user: User
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
AsyncImage(url: user.avatarURL) { image in
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 64, height: 64)
|
||||
.clipShape(Circle())
|
||||
} placeholder: {
|
||||
Circle()
|
||||
.frame(width: 64, height: 64)
|
||||
.opacity(0.3)
|
||||
}
|
||||
|
||||
Text(user.name)
|
||||
.font(.headline)
|
||||
|
||||
Text("@\(user.handle)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Button {
|
||||
// Follow user
|
||||
} label: {
|
||||
Label("Follow", systemImage: "person.badge.plus")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(12)
|
||||
.shadow(color: Color(.systemGray4), radius: 8, x: 0, y: 4)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 8.88
|
||||
* **Total Tokens:** 256
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 3: I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
*Source:* `test3.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
The new function should be marked with `async throws` and return a `[Post]` object upon success. Also, include an example of how to call this new async function inside a `Task` with a `do-catch` block.
|
||||
|
||||
**Old Code:**
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPosts(completion: @escaping (Result<[Post], Error>) -> Void) {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else { return }
|
||||
URLSession.shared.dataTask(with: url) { data, _, error in
|
||||
if let error = error { completion(.failure(error)); return }
|
||||
guard let data = data else { return }
|
||||
do {
|
||||
let posts = try JSONDecoder().decode([Post].self, from: data)
|
||||
completion(.success(posts))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's how you can refactor the old code to use modern Swift Concurrency with async/await:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPosts() async throws -> [Post] {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else { throw URLError(.badURL) }
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(from: url)
|
||||
return try JSONDecoder().decode([Post].self, from: data)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new `fetchPosts()` function is marked with `async throws` to indicate that it's an asynchronous function that can throw errors. It returns a `[Post]` object upon successful completion.
|
||||
|
||||
To call the new async function inside a `Task` with a `do-catch` block, you can use the following code:
|
||||
|
||||
```swift
|
||||
Task {
|
||||
do {
|
||||
let posts = try await fetchPosts()
|
||||
print(posts)
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 8.4
|
||||
* **Total Tokens:** 257
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 4: Create a complete SwiftData example for a macOS 26 app.
|
||||
|
||||
*Source:* `test4.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a complete SwiftData example for a macOS 26 app.
|
||||
|
||||
You must define two models: `Author` and `Book`. An `Author` can have many `Book`s (a one-to-many relationship).
|
||||
|
||||
Then, create a SwiftUI view that displays a list of all authors. When an author's name is tapped, it should navigate to a detail view that shows a list of all the books written by that specific author. Use a SwiftData `@Query` to fetch the data.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's how you can define the two models and create the SwiftUI view:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
struct Author: Identifiable {
|
||||
var id = UUID()
|
||||
var name: String
|
||||
}
|
||||
|
||||
struct Book: Identifiable {
|
||||
var id = UUID()
|
||||
var title: String
|
||||
var authorID: UUID
|
||||
}
|
||||
|
||||
extension Author: SDModel {
|
||||
static let schema = SDSchema(
|
||||
Author.self,
|
||||
\.$id,
|
||||
\.$name
|
||||
)
|
||||
}
|
||||
|
||||
extension Book: SDModel {
|
||||
static let schema = SDSchema(
|
||||
Book.self,
|
||||
\.$id,
|
||||
\.$title,
|
||||
\.$authorID
|
||||
)
|
||||
}
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AuthorRow: View {
|
||||
var author: Author
|
||||
|
||||
var body: some View {
|
||||
Text(author.name)
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthorDetail: View {
|
||||
@Query var books: [Book]
|
||||
|
||||
var body: some View {
|
||||
List(books, id: \.id) { book in
|
||||
Text(book.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthorsList: View {
|
||||
@Query var authors: [Author]
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List(authors, id: \.id) { author in
|
||||
NavigationLink(destination: AuthorDetail(books: books(forAuthorID: author.id))) {
|
||||
AuthorRow(author: author)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 8.15
|
||||
* **Total Tokens:** 321
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 5: Write a thread-safe counter class in Swift using an `actor`.
|
||||
|
||||
*Source:* `test5.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a thread-safe counter class in Swift using an `actor`.
|
||||
|
||||
The actor should be named `SafeCounter`. It must have a private property to hold the current count and two public methods:
|
||||
1. `func increment() async`
|
||||
2. `func getCount() async -> Int`
|
||||
|
||||
Also, provide a simple example of how to create an instance of the actor and call its methods concurrently from two different `Task`s.
|
||||
```
|
||||
|
||||
### Response:
|
||||
**ERROR:** API request failed: HTTPConnectionPool(host='localhost', port=1234): Read timed out. (read timeout=180)
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** N/A
|
||||
* **Total Tokens:** N/A
|
||||
* **Time to First Token:** N/A
|
||||
* **Stop Reason:** N/A
|
||||
|
||||
---
|
||||
|
||||
## Test 6: Create the code for a simple, non-configurable watchOS 26 complication that displays the current date in the `.accessoryRectangular` family.
|
||||
|
||||
*Source:* `test6.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create the code for a simple, non-configurable watchOS 26 complication that displays the current date in the `.accessoryRectangular` family.
|
||||
|
||||
The code must include:
|
||||
1. A `TimelineProvider` to generate the timeline entries.
|
||||
2. A `TimelineEntry` struct.
|
||||
3. The main `Widget` struct.
|
||||
4. A SwiftUI view for the complication's appearance.
|
||||
|
||||
Ensure the code correctly uses the modern WidgetKit framework.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's an example of how you can implement a simple, non-configurable watchOS 26 complication that displays the current date in the `.accessoryRectangular` family:
|
||||
|
||||
```swift
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
struct Provider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
SimpleEntry(date: Date())
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
||||
let entry = SimpleEntry(date: Date())
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
let currentDate = Date()
|
||||
let refreshDate = Calendar.current.date(byAdding: .minute, value: 1, to: currentDate)!
|
||||
|
||||
let entry = SimpleEntry(date: currentDate)
|
||||
let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
|
||||
completion(timeline)
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
let date: Date
|
||||
}
|
||||
|
||||
struct SimpleComplicationEntryView : View {
|
||||
var entry: Provider.Entry
|
||||
|
||||
var body: some View {
|
||||
Text(entry.date, style: .time)
|
||||
.widgetURL(URL(string: "my-app://open-app"))
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct SimpleComplication: Widget {
|
||||
let kind: String = "SimpleComplication"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
||||
SimpleComplicationEntryView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName("Simple Complication")
|
||||
.description("A simple complication that displays the current date.")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 6.29
|
||||
* **Total Tokens:** 383
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 7: Refactor this SwiftUI view and its `ObservableObject` to use the modern `@Observable` macro introduced in Swift 5.9. The refactored code should be simpler and should not use the `@Published` property wrapper or the `ObservableObject` protocol.
|
||||
|
||||
*Source:* `test7.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Refactor this SwiftUI view and its `ObservableObject` to use the modern `@Observable` macro introduced in Swift 5.9. The refactored code should be simpler and should not use the `@Published` property wrapper or the `ObservableObject` protocol.
|
||||
|
||||
**Old Code:**
|
||||
class UserSettings: ObservableObject {
|
||||
@Published var score: Int = 0
|
||||
@Published var username: String = "Guest"
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@StateObject private var settings = UserSettings()
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Username", text: $settings.username)
|
||||
Stepper("Score: \(settings.score)", value: $settings.score)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's how you can refactor the code to use the `@Observable` macro:
|
||||
|
||||
```swift
|
||||
@Observable class UserSettings {
|
||||
var score: Int = 0
|
||||
var username: String = "Guest"
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@StateObject private var settings = UserSettings()
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Username", text: $settings.username)
|
||||
Stepper("Score: \(settings.score)", value: $settings.score)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 5.58
|
||||
* **Total Tokens:** 114
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 8: Create the code for a simple, configurable WidgetKit widget for iOS 26. The widget should display a single "Note" string. The configuration logic must use the modern App Intents framework, allowing the user to edit the note's text directly from the widget's configuration screen. Do not use the old `INIntent` based system.
|
||||
|
||||
*Source:* `test8.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create the code for a simple, configurable WidgetKit widget for iOS 26. The widget should display a single "Note" string. The configuration logic must use the modern App Intents framework, allowing the user to edit the note's text directly from the widget's configuration screen. Do not use the old `INIntent` based system.
|
||||
```
|
||||
|
||||
### Response:
|
||||
**ERROR:** API request failed: HTTPConnectionPool(host='localhost', port=1234): Read timed out. (read timeout=180)
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** N/A
|
||||
* **Total Tokens:** N/A
|
||||
* **Time to First Token:** N/A
|
||||
* **Stop Reason:** N/A
|
||||
|
||||
---
|
||||
|
||||
## Test 9: Write a complete SwiftUI view for visionOS 26 that presents a fully immersive space when a button is tapped.
|
||||
|
||||
*Source:* `test9.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Write a complete SwiftUI view for visionOS 26 that presents a fully immersive space when a button is tapped.
|
||||
|
||||
The view must:
|
||||
1. Have a button labeled "Enter Immersive Space".
|
||||
2. Use the `@Environment(\.openImmersiveSpace)` and `@Environment(\.dismissImmersiveSpace)` variables.
|
||||
3. Be able to open an immersive space with the ID "MyImmersiveScene".
|
||||
4. Include a second button inside the immersive space to dismiss it.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's how you can implement this:
|
||||
|
||||
```swift
|
||||
struct MyView: View {
|
||||
@Environment(\.openImmersiveSpace) var openImmersiveSpace
|
||||
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Button("Enter Immersive Space") {
|
||||
openImmersiveSpace("MyImmersiveScene")
|
||||
}
|
||||
|
||||
if isImmersiveSpaceOpen {
|
||||
Button("Exit Immersive Space") {
|
||||
dismissImmersiveSpace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 5.47
|
||||
* **Total Tokens:** 128
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
## Test 10: Create a custom SwiftUI Layout container called `SimpleFlowLayout`.
|
||||
|
||||
*Source:* `test10.txt`
|
||||
|
||||
### Prompt:
|
||||
```swift
|
||||
Create a custom SwiftUI Layout container called `SimpleFlowLayout`.
|
||||
|
||||
This layout should arrange its subviews in a horizontal flow, starting a new row when the current row is full. It should behave similarly to a basic left-aligned flow layout in CSS.
|
||||
|
||||
The implementation must conform to the `Layout` protocol and implement both the `sizeThatFits(...)` and `placeSubviews(...)` methods.
|
||||
```
|
||||
|
||||
### Response:
|
||||
|
||||
Here's how you can create a custom SwiftUI Layout container called `SimpleFlowLayout`:
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct SimpleFlowLayout: Layout {
|
||||
func sizeThatFits(proposal: ProposedViewSize, subviews: [Subview]) -> CGSize {
|
||||
var width = proposal.width
|
||||
var height = proposal.height
|
||||
|
||||
for subview in subviews {
|
||||
let subviewSize = subview.sizeThatFits(proposal)
|
||||
if width + subviewSize.width > proposal.width {
|
||||
width = 0
|
||||
height += subviewSize.height
|
||||
}
|
||||
|
||||
width += subviewSize.width
|
||||
}
|
||||
|
||||
return CGSize(width: width, height: height)
|
||||
}
|
||||
|
||||
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: [Subview]) {
|
||||
var x = bounds.minX
|
||||
var y = bounds.minY
|
||||
var rowHeight = 0.0
|
||||
|
||||
for subview in subviews {
|
||||
let subviewSize = subview.sizeThatFits(proposal)
|
||||
|
||||
if x + subviewSize.width > bounds.width {
|
||||
x = bounds.minX
|
||||
y += CGFloat(rowHeight)
|
||||
rowHeight = 0
|
||||
}
|
||||
|
||||
subview.place(at: CGRect(x: x, y: y, width: subviewSize.width, height: subviewSize.height))
|
||||
x += subviewSize.width
|
||||
rowHeight = max(rowHeight, subviewSize.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics:
|
||||
* **Tokens/s:** 5.55
|
||||
* **Total Tokens:** 330
|
||||
* **Time to First Token:** 0.0s
|
||||
* **Stop Reason:** stop
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
Write a Swift function that solves the FizzBuzz problem.
|
||||
|
||||
The function must have the exact signature:
|
||||
`func generateFizzBuzz(upTo max: Int) -> [String]`
|
||||
|
||||
It must not print directly to the console. Instead, it should return an array of strings.
|
||||
|
||||
Rules:
|
||||
1. For numbers divisible by 3, the string should be "Fizz".
|
||||
2. For numbers divisible by 5, the string should be "Buzz".
|
||||
3. For numbers divisible by both 3 and 5, the string should be "FizzBuzz".
|
||||
4. Otherwise, it should be the number itself as a string.
|
||||
|
||||
Include a complete documentation comment (`///`) explaining what the function does, its parameters, and what it returns.
|
||||
@@ -0,0 +1,5 @@
|
||||
Create a custom SwiftUI Layout container called `SimpleFlowLayout`.
|
||||
|
||||
This layout should arrange its subviews in a horizontal flow, starting a new row when the current row is full. It should behave similarly to a basic left-aligned flow layout in CSS.
|
||||
|
||||
The implementation must conform to the `Layout` protocol and implement both the `sizeThatFits(...)` and `placeSubviews(...)` methods.
|
||||
@@ -0,0 +1,10 @@
|
||||
Create a complete SwiftUI view for a user profile card.
|
||||
|
||||
The view should be named `UserProfileCard` and be initialized with a simple `User` struct containing `name: String`, `handle: String`, and `avatarURL: URL`.
|
||||
|
||||
Requirements:
|
||||
- Use an `AsyncImage` to load the user's avatar from the URL.
|
||||
- Display the user's name in a `.headline` font and their handle (e.g., "@username") in a `.subheadline` font with a secondary color.
|
||||
- Include a "Follow" button that uses the "person.badge.plus" SF Symbol.
|
||||
- Arrange the elements cleanly inside an HStack with appropriate spacing.
|
||||
- The entire card should have a subtle shadow and rounded corners.
|
||||
@@ -0,0 +1,23 @@
|
||||
I have this old networking code that uses a completion handler. Please refactor it to use modern Swift Concurrency with async/await.
|
||||
|
||||
The new function should be marked with `async throws` and return a `[Post]` object upon success. Also, include an example of how to call this new async function inside a `Task` with a `do-catch` block.
|
||||
|
||||
**Old Code:**
|
||||
struct Post: Codable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
func fetchPosts(completion: @escaping (Result<[Post], Error>) -> Void) {
|
||||
guard let url = URL(string: "https://api.example.com/posts") else { return }
|
||||
URLSession.shared.dataTask(with: url) { data, _, error in
|
||||
if let error = error { completion(.failure(error)); return }
|
||||
guard let data = data else { return }
|
||||
do {
|
||||
let posts = try JSONDecoder().decode([Post].self, from: data)
|
||||
completion(.success(posts))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
Create a complete SwiftData example for a macOS 26 app.
|
||||
|
||||
You must define two models: `Author` and `Book`. An `Author` can have many `Book`s (a one-to-many relationship).
|
||||
|
||||
Then, create a SwiftUI view that displays a list of all authors. When an author's name is tapped, it should navigate to a detail view that shows a list of all the books written by that specific author. Use a SwiftData `@Query` to fetch the data.
|
||||
@@ -0,0 +1,7 @@
|
||||
Write a thread-safe counter class in Swift using an `actor`.
|
||||
|
||||
The actor should be named `SafeCounter`. It must have a private property to hold the current count and two public methods:
|
||||
1. `func increment() async`
|
||||
2. `func getCount() async -> Int`
|
||||
|
||||
Also, provide a simple example of how to create an instance of the actor and call its methods concurrently from two different `Task`s.
|
||||
@@ -0,0 +1,9 @@
|
||||
Create the code for a simple, non-configurable watchOS 26 complication that displays the current date in the `.accessoryRectangular` family.
|
||||
|
||||
The code must include:
|
||||
1. A `TimelineProvider` to generate the timeline entries.
|
||||
2. A `TimelineEntry` struct.
|
||||
3. The main `Widget` struct.
|
||||
4. A SwiftUI view for the complication's appearance.
|
||||
|
||||
Ensure the code correctly uses the modern WidgetKit framework.
|
||||
@@ -0,0 +1,17 @@
|
||||
Refactor this SwiftUI view and its `ObservableObject` to use the modern `@Observable` macro introduced in Swift 5.9. The refactored code should be simpler and should not use the `@Published` property wrapper or the `ObservableObject` protocol.
|
||||
|
||||
**Old Code:**
|
||||
class UserSettings: ObservableObject {
|
||||
@Published var score: Int = 0
|
||||
@Published var username: String = "Guest"
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@StateObject private var settings = UserSettings()
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Username", text: $settings.username)
|
||||
Stepper("Score: \(settings.score)", value: $settings.score)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Create the code for a simple, configurable WidgetKit widget for iOS 26. The widget should display a single "Note" string. The configuration logic must use the modern App Intents framework, allowing the user to edit the note's text directly from the widget's configuration screen. Do not use the old `INIntent` based system.
|
||||
@@ -0,0 +1,7 @@
|
||||
Write a complete SwiftUI view for visionOS 26 that presents a fully immersive space when a button is tapped.
|
||||
|
||||
The view must:
|
||||
1. Have a button labeled "Enter Immersive Space".
|
||||
2. Use the `@Environment(\.openImmersiveSpace)` and `@Environment(\.dismissImmersiveSpace)` variables.
|
||||
3. Be able to open an immersive space with the ID "MyImmersiveScene".
|
||||
4. Include a second button inside the immersive space to dismiss it.
|
||||
Reference in New Issue
Block a user