TownieuseChatLogic.ts4 matches
7branchId: string | undefined;
8selectedFiles: string[];
9images: (string | null)[];
10soundEnabled: boolean;
11}
20// bearerToken,
21selectedFiles,
22images,
23soundEnabled,
24}: UseChatLogicProps) {
44branchId,
45selectedFiles,
46images: images
47.filter((img): img is string => {
48const isValid = typeof img === "string" && img.startsWith("data:");
49if (!isValid && img !== null) {
50console.warn(
51"Invalid image format:",
52img?.substring(0, 50) + "..."
53);
29- [x] File write as a code embed
30- [x] str_replace as a diff view
31- [x] make image drop area invisible and bigger
32- [x] Give it all the code (except maybe .txt files) as initial context (like cursor sonnet max)
33- [x] I seem to have lost the delete file tool and instructions, try to find them back in history or re-create?
55- [x] Create branch
56- [x] URL input + pathname
57- [x] Image upload controls
58- [x] Preview refresh button
59- [x] Audio controls
Towniesystem_prompt.txt2 matches
155156- **Redirects:** Use `return new Response(null, { status: 302, headers: { Location: "/place/to/redirect" }})` instead of `Response.redirect` which is broken
157- **Images:** Avoid external images or base64 images. Use emojis, unicode symbols, or icon fonts/libraries instead
158- **AI Image:** To inline generate an AI image use: `<img src="https://maxm-imggenurl.web.val.run/the-description-of-your-image" />`
159- **Storage:** DO NOT use the Deno KV module for storage
160- **Browser APIs:** DO NOT use the `alert()`, `prompt()`, or `confirm()` methods
Towniestyles.css11 matches
782background-color: var(--highlight);
783}
784.card-image {
785display: flex;
786align-items: center;
809}
810811.image-placeholder,
812.image-thumbnail {
813flex-shrink: 0;
814width: 40px;
817object-fit: cover;
818}
819.image-placeholder {
820background-color: var(--muted);
821}
828}
829830.image-row {
831display: flex;
832gap: var(--space-1);
833}
834.input-image {
835position: relative;
836border: 1px solid var(--muted);
837border-radius: 6px;
838}
839.remove-image-button {
840position: absolute;
841top: 0;
850opacity: 0;
851}
852.input-image:hover .remove-image-button {
853opacity: 1;
854}
855856.image-drop-overlay {
857position: fixed;
858top: 0;
867justify-content: center;
868}
869.image-drop-inner {
870padding: var(--space-2);
871background-color: var(--background);
880}
881882.transition, .input-box, .icon-button, .button, .remove-image-button {
883transition-property: color, background-color, border-color, opacity;
884transition-duration: 200ms;
Towniesend-message.ts12 matches
28}
2930const { messages, project, branchId, anthropicApiKey, selectedFiles, images } = await c.req.json();
3132// do we want to allow user-provided tokens still
63branch_id: branchId,
64val_id: project.id,
65num_images: images?.length || 0,
66model,
67});
95townie_usage_id: rowid,
96townie_our_api_token: our_api_token,
97townie_num_images: images?.length || 0,
98townie_selected_files_count: selectedFiles?.length || 0,
99},
113let coreMessages = convertToCoreMessages(messages);
114115// If there are images, we need to add them to the last user message
116if (images && Array.isArray(images) && images.length > 0) {
117// Find the last user message
118const lastUserMessageIndex = coreMessages.findIndex(
136};
137138// Add each image to the content array using the correct ImagePart format
139for (const image of images) {
140if (image && image.url) {
141// Extract mime type from data URL if available
142let mimeType = undefined;
143if (image.url.startsWith("data:")) {
144const matches = image.url.match(/^data:([^;]+);/);
145if (matches && matches.length > 1) {
146mimeType = matches[1];
149150newUserMessage.content.push({
151type: "image",
152image: image.url,
153mimeType,
154});
Townieschema.tsx2 matches
18price?: number;
19finish_reason?: string;
20num_images?: number;
21our_api_token: boolean;
22}
43price REAL,
44finish_reason TEXT,
45num_images INTEGER,
46our_api_token INTEGER NOT NULL,
47finish_timestamp INTEGER
Townierequests.ts3 matches
16price: number | null;
17finish_reason: string | null;
18num_images: number | null;
19our_api_token: number;
20}
191<th>Price</th>
192<th>Finish</th>
193<th>Images</th>
194<th>Our API</th>
195</tr>
215<td class="price">${formatPrice(row.price)}</td>
216<td>${row.finish_reason || '-'}</td>
217<td>${formatNumber(row.num_images)}</td>
218<td>${formatBoolean(row.our_api_token)}</td>
219</tr>
Towniequeries.tsx4 matches
114model,
115our_api_token,
116num_images,
117}: {
118bearerToken: string;
121model: string;
122our_api_token: boolean;
123num_images: number;
124}) {
125const user = await getUser(bearerToken);
133model,
134our_api_token,
135num_images
136) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
137`,
144model,
145our_api_token ? 1 : 0,
146num_images,
147],
148);
TownieProjectsRoute.tsx7 matches
43user: {
44username: string;
45profileImageUrl: string | null;
46};
47project: any;
49return (
50<div className="card">
51{project.imageUrl ? (
52<img src={project.imageUrl} className="card-image" />
53) : user.profileImageUrl ? (
54<div className="card-image">
55<img
56src={user.profileImageUrl}
57width="48"
58height="48"
61</div>
62) : (
63<div className="card-image placeholder" />
64)}
65<div className="card-body">
TownieInputBox.tsx46 matches
2import { useRef, useState, useEffect } from "react";
3import { PlusIcon, ArrowUpIcon, Square, XIcon } from "./icons.tsx";
4import { processFiles } from "../utils/images.ts";
56export function InputBox ({
11running,
12error,
13images,
14setImages,
15} : {
16value: string;
20running: boolean;
21error: any;
22images: (string|null)[];
23setImages: (images: (string|null)[]) => void;
24}) {
25const form = useRef(null);
57autoFocus={true}
58/>
59<ImageRow images={images} setImages={setImages} />
60<div className="toolbar">
61<UploadButton
62disabled={running}
63images={images}
64setImages={setImages}
65/>
66<div className="spacer" />
88}
8990export function ImageDropContainer ({
91images,
92setImages,
93running,
94children,
95}: {
96images: (string|null)[];
97setImages: (images: (string|null)[]) => void;
98running: boolean;
99children: React.ReactNode;
100}) {
101const dragging = useImageDrop({ images, setImages, running });
102103return (
105{children}
106{dragging && (
107<div className="image-drop-overlay">
108<div className="image-drop-inner">
109Drop images here to upload
110</div>
111</div>
115}
116117export function useImageDrop ({ images, setImages, running }: {
118images: (string|null)[];
119setImages(images: (string|null)[]) => void;
120running: boolean;
121}) {
143setDragging(false);
144if (e.dataTransfer?.files && !running) {
145processFiles(Array.from(e.dataTransfer.files), images, setImages);
146}
147}
164}
165166function ImageRow ({ images, setImages }: {
167images: (string|null)[];
168setImages: (images: (string|null)[]) => void;
169}) {
170return (
171<div className="image-row">
172{images.map((image, i) => (
173<Thumbnail
174key={i}
175image={image}
176onRemove={() => {
177setImages([
178...images.slice(0, i),
179...images.slice(i + 1),
180]);
181}}
186}
187188function Thumbnail ({ image, onRemove }: {
189image: string|null;
190onRemove: () => void;
191}) {
192if (!image) return null;
193194return (
195<div className="input-image">
196<img
197src={image}
198alt="User uploaded image"
199className="image-thumbnail"
200/>
201<button
202type="button"
203title="Remove image"
204className="remove-image-button"
205onClick={onRemove}
206>
212213function UploadButton ({
214images,
215setImages,
216disabled,
217}: {
218images: (string|null)[];
219setImages: (images: (string|null)[]) => void;
220disabled: boolean;
221}) {
226<button
227type="button"
228title="Upload image"
229disabled={disabled}
230onClick={e => {
234<PlusIcon />
235<div className="sr-only">
236Upload image
237</div>
238</button>
243onChange={e => {
244if (e.target.files) {
245processFiles(Array.from(e.target.files), images, setImages);
246}
247}}